本文发布已超过一年。旧文章可能包含过时内容。请检查页面信息自发布以来是否已失效或不准确。
具有存储容量跟踪功能的临时卷:强化版 EmptyDir
有些应用需要额外的存储,但并不关心数据是否跨重启持久存储。例如,缓存服务通常受内存大小限制,可以将不常用数据移动到比内存慢但对整体性能影响很小的存储中。其他应用则期望文件中存在一些只读输入数据,例如配置数据或密钥。
Kubernetes 已经支持几种此类临时卷 (ephemeral volumes),但它们的功能仅限于 Kubernetes 内部实现的部分。
CSI 临时卷使得通过提供轻量级本地卷的 CSI 驱动来扩展 Kubernetes 成为可能。这些卷可以注入任意状态,例如配置、秘密、身份、变量或类似信息。CSI 驱动必须进行修改才能支持此 Kubernetes 功能,即正常的、符合标准的 CSI 驱动将无法工作,并且根据设计,此类卷应可在为 Pod 选择的任何节点上使用。
这对于消耗节点大量资源或仅在某些节点上可用的特殊存储卷来说是个问题。因此,Kubernetes 1.19 引入了两个新的 Alpha 功能,用于那些在概念上更类似于 EmptyDir
卷的卷:
新方法的优势在于:
- 存储可以是本地的或网络附加的。
- 卷可以具有固定大小,应用永远无法超出此大小。
- 适用于任何支持持久卷供给并(用于容量跟踪)实现 CSI
GetCapacity
调用的 CSI 驱动。 - 卷可能包含一些初始数据,具体取决于驱动和参数。
- 支持所有典型的卷操作(快照、扩容、未来的存储容量跟踪等)。
- 这些卷可用于接受 Pod 或卷规范的任何应用控制器。
- Kubernetes 调度器本身会选择合适的节点,也就是说,不再需要实现和配置调度器扩展器和修改性 Webhook。
这使得通用临时卷适用于多种用例:
用例
持久内存 (Persistent Memory) 作为 memcached 的 DRAM 替代品
memcached 的最新版本增加了对使用持久内存 (Persistent Memory, PMEM) 而非标准 DRAM 的支持。通过应用控制器部署 memcached 时,通用临时卷使得可以从 PMEM-CSI 等 CSI 驱动请求特定大小的 PMEM 卷。
本地 LVM 存储作为暂存空间
处理超出 RAM 大小数据集的应用可以请求具有普通 Kubernetes EmptyDir
卷无法满足的性能特征或大小的本地存储。例如,TopoLVM 就是为此目的而编写的。
对含数据卷的只读访问
供给卷可能会得到一个非空卷:
此类卷可以以只读方式挂载。
工作原理
通用临时卷
通用临时卷背后的关键思想是,一个新的卷源,即所谓的EphemeralVolumeSource
,包含创建卷声明(历史上称为持久卷声明,PVC)所需的所有字段。kube-controller-manager
中的一个新控制器等待嵌入此类卷源的 Pod,然后为该 Pod 创建一个 PVC。对于 CSI 驱动部署来说,该 PVC 看上去与其他 PVC 一样,因此不需要特殊支持。
只要这些 PVC 存在,它们就可以像任何其他卷声明一样使用。特别是,它们可以在卷克隆或快照中作为数据源引用。PVC 对象还保存卷的当前状态。
自动创建的 PVC 的命名是确定性的:名称是 Pod 名称和卷名称的组合,中间用连字符 (-
) 连接。这种确定性命名使得与 PVC 交互更容易,因为一旦知道 Pod 名称和卷名称,就不必搜索它。缺点是名称可能已经被使用。Kubernetes 会检测到这种情况,并阻止 Pod 启动。
为确保卷随 Pod 一起删除,控制器将 Pod 设置为卷声明的所有者。当 Pod 被删除时,正常的垃圾回收机制也会删除该声明,从而删除卷。
声明通过常规存储类机制选择存储驱动。虽然支持立即绑定和延迟绑定(又称 WaitForFirstConsumer
)的存储类,但对于临时卷来说,使用 WaitForFirstConsumer
更合理:这样 Pod 调度可以在选择节点时同时考虑节点利用率和存储可用性。这就是另一个新功能发挥作用的地方。
存储容量跟踪
通常,Kubernetes 调度器没有关于 CSI 驱动可能在哪里创建卷的信息。它也无法直接与 CSI 驱动对话以获取该信息。因此,它会尝试不同的节点,直到找到一个可以提供所有卷的节点(延迟绑定),或者完全由驱动选择位置(立即绑定)。
新的CSIStorageCapacity
Alpha API 允许将必要的信息存储在 etcd 中,供调度器使用。与对通用临时卷的支持不同,部署 CSI 驱动时必须启用存储容量跟踪:必须告知 external-provisioner
发布它通过常规 GetCapacity
调用从 CSI 驱动获取的容量信息。
当 Kubernetes 调度器需要为一个使用延迟绑定的未绑定卷的 Pod 选择节点,并且 CSI 驱动部署通过设置 CSIDriver.storageCapacity
标志启用了此功能时,调度器会自动过滤掉没有足够存储容量的节点。这适用于通用临时卷和持久卷,但不适用于 CSI 临时卷,因为后者的参数对 Kubernetes 来说是透明的。
通常,使用立即绑定的卷会在调度 Pod 之前创建,其位置由存储驱动选择。因此,external-provisioner 的默认配置会跳过立即绑定的存储类,因为这些信息无论如何都不会被使用。
由于 Kubernetes 调度器必须根据可能过时的信息进行操作,因此无法确保在创建卷时容量仍然可用。不过,无需重试即可成功创建的机会应该会更高。
安全性
CSIStorageCapacity
CSIStorageCapacity 对象是带有命名空间的。将每个 CSI 驱动部署在其自己的命名空间中,并按照建议将 CSIStorageCapacity 的 RBAC 权限限制在该命名空间时,数据的来源总是很清楚。但是,Kubernetes 不会检查这一点,而且驱动通常安装在同一个命名空间中,因此最终驱动程序被期望行为端正,不发布不正确的数据。
通用临时卷
如果用户有创建 Pod 的权限(直接或间接),那么即使他们没有创建卷声明的权限,也可以创建通用临时卷。这是因为 RBAC 权限检查是应用于创建 PVC 的控制器,而不是原始用户。这是一个根本性的变化,在允许不受信任的用户无权创建卷的集群中启用此功能之前,必须考虑这一点。
示例
PMEM-CSI 中的一个特殊分支包含了在 QEMU VM 中启动启用两个 Alpha 功能的 Kubernetes 1.19 集群所需的所有更改。PMEM-CSI 驱动代码保持不变,只更新了部署文件。
在一台合适的机器上(Linux,非 root 用户可以使用 Docker - 参见 PMEM-CSI 文档中的QEMU 和 Kubernetes 部分),以下命令将启动一个集群并安装 PMEM-CSI 驱动:
git clone --branch=kubernetes-1-19-blog-post https://github.com/intel/pmem-csi.git
cd pmem-csi
export TEST_KUBERNETES_VERSION=1.19 TEST_FEATURE_GATES=CSIStorageCapacity=true,GenericEphemeralVolume=true TEST_PMEM_REGISTRY=intel
make start && echo && test/setup-deployment.sh
如果一切顺利,输出将包含以下使用说明:
The test cluster is ready. Log in with [...]/pmem-csi/_work/pmem-govm/ssh.0, run
kubectl once logged in. Alternatively, use kubectl directly with the
following env variable:
KUBECONFIG=[...]/pmem-csi/_work/pmem-govm/kube.config
secret/pmem-csi-registry-secrets created
secret/pmem-csi-node-secrets created
serviceaccount/pmem-csi-controller created
...
To try out the pmem-csi driver ephemeral volumes:
cat deploy/kubernetes-1.19/pmem-app-ephemeral.yaml |
[...]/pmem-csi/_work/pmem-govm/ssh.0 kubectl create -f -
CSIStorageCapacity 对象不适合人类阅读,因此需要进行一些后处理。以下 Golang 模板通过示例使用的存储类过滤所有对象,并打印名称、拓扑和容量:
kubectl get \
-o go-template='{{range .items}}{{if eq .storageClassName "pmem-csi-sc-late-binding"}}{{.metadata.name}} {{.nodeTopology.matchLabels}} {{.capacity}}
{{end}}{{end}}' \
csistoragecapacities
csisc-2js6n map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker2] 30716Mi
csisc-sqdnt map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker1] 30716Mi
csisc-ws4bv map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker3] 30716Mi
一个单独对象的内容如下:
kubectl describe csistoragecapacities/csisc-6cw8j
Name: csisc-sqdnt
Namespace: default
Labels: <none>
Annotations: <none>
API Version: storage.k8s.io/v1alpha1
Capacity: 30716Mi
Kind: CSIStorageCapacity
Metadata:
Creation Timestamp: 2020-08-11T15:41:03Z
Generate Name: csisc-
Managed Fields:
...
Owner References:
API Version: apps/v1
Controller: true
Kind: StatefulSet
Name: pmem-csi-controller
UID: 590237f9-1eb4-4208-b37b-5f7eab4597d1
Resource Version: 2994
Self Link: /apis/storage.k8s.io/v1alpha1/namespaces/default/csistoragecapacities/csisc-sqdnt
UID: da36215b-3b9d-404a-a4c7-3f1c3502ab13
Node Topology:
Match Labels:
pmem-csi.intel.com/node: pmem-csi-pmem-govm-worker1
Storage Class Name: pmem-csi-sc-late-binding
Events: <none>
现在我们创建一个包含一个通用临时卷的示例应用。pmem-app-ephemeral.yaml
文件内容如下:
# This example Pod definition demonstrates
# how to use generic ephemeral inline volumes
# with a PMEM-CSI storage class.
kind: Pod
apiVersion: v1
metadata:
name: my-csi-app-inline-volume
spec:
containers:
- name: my-frontend
image: intel/pmem-csi-driver-test:v0.7.14
command: [ "sleep", "100000" ]
volumeMounts:
- mountPath: "/data"
name: my-csi-volume
volumes:
- name: my-csi-volume
ephemeral:
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
storageClassName: pmem-csi-sc-late-binding
如上述使用说明所示创建后,我们得到一个额外的 Pod 和 PVC:
kubectl get pods/my-csi-app-inline-volume -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-csi-app-inline-volume 1/1 Running 0 6m58s 10.36.0.2 pmem-csi-pmem-govm-worker1 <none> <none>
kubectl get pvc/my-csi-app-inline-volume-my-csi-volume
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-csi-app-inline-volume-my-csi-volume Bound pvc-c11eb7ab-a4fa-46fe-b515-b366be908823 4Gi RWO pmem-csi-sc-late-binding 9m21s
该 PVC 由 Pod 拥有
kubectl get -o yaml pvc/my-csi-app-inline-volume-my-csi-volume
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
pv.kubernetes.io/bind-completed: "yes"
pv.kubernetes.io/bound-by-controller: "yes"
volume.beta.kubernetes.io/storage-provisioner: pmem-csi.intel.com
volume.kubernetes.io/selected-node: pmem-csi-pmem-govm-worker1
creationTimestamp: "2020-08-11T15:44:57Z"
finalizers:
- kubernetes.io/pvc-protection
managedFields:
...
name: my-csi-app-inline-volume-my-csi-volume
namespace: default
ownerReferences:
- apiVersion: v1
blockOwnerDeletion: true
controller: true
kind: Pod
name: my-csi-app-inline-volume
uid: 75c925bf-ca8e-441a-ac67-f190b7a2265f
...
最终,pmem-csi-pmem-govm-worker1
的存储容量信息也会更新。
csisc-2js6n map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker2] 30716Mi
csisc-sqdnt map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker1] 26620Mi
csisc-ws4bv map[pmem-csi.intel.com/node:pmem-csi-pmem-govm-worker3] 30716Mi
如果另一个应用需要超过 26620Mi 的容量,Kubernetes 调度器将不再选择 pmem-csi-pmem-govm-worker1
。
下一步
这两个功能仍在开发中。在 Alpha 审查过程中已经提出了一些悬而未决的问题。这两个增强提案记录了迁移到 Beta 版所需的工作以及已经考虑并拒绝的替代方案:
您的反馈对于推动这项开发至关重要。SIG-Storage 定期开会 并可以通过 Slack 和邮件列表 联系到。