本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
具有存储容量跟踪的临时卷:增强版的 EmptyDir
有些应用程序需要额外的存储,但并不关心数据是否在重启后持久存储。例如,缓存服务通常受内存大小限制,可以将不常用数据移动到比内存慢的存储中,对整体性能影响很小。其他应用程序则期望某些只读输入数据以文件的形式存在,例如配置数据或密钥。
Kubernetes 已经支持多种此类临时卷,但其功能仅限于 Kubernetes 内部实现的功能。
CSI 临时卷使得通过提供轻量级本地卷的 CSI 驱动程序扩展 Kubernetes 成为可能。这些卷注入任意状态,例如配置、密钥、身份、变量或类似信息。CSI 驱动程序必须经过修改才能支持此 Kubernetes 功能,即,正常的、符合标准 CSI 驱动程序将无法工作,并且根据设计,此类卷应可在为 Pod 选择的任何节点上使用。
这对于在节点上消耗大量资源的卷,或仅在某些节点上可用的特殊存储来说是存在问题的。因此,Kubernetes 1.19 引入了两个新的 Alpha 功能,用于在概念上更像 EmptyDir
卷的卷:
新方法的优点是:
- 存储可以是本地的或网络附加的。
- 卷可以具有固定大小,应用程序永远无法超出该大小。
- 适用于任何支持持久卷供应和(对于容量跟踪)实现 CSI
GetCapacity
调用的 CSI 驱动程序。 - 根据驱动程序和参数,卷可能具有一些初始数据。
- 支持所有典型的卷操作(快照、调整大小、未来的存储容量跟踪等)。
- 这些卷可用于任何接受 Pod 或卷规范的应用程序控制器。
- Kubernetes 调度程序本身选择合适的节点,即不再需要实现和配置调度程序扩展器和变更 Webhook。
这使得通用临时卷成为多种用例的合适解决方案:
使用场景
持久内存作为 memcached 的 DRAM 替代品
memcached 的最新版本增加了对使用持久内存 (PMEM) 而非标准 DRAM 的支持。通过某个应用程序控制器部署 memcached 时,通用临时卷使得可以从 CSI 驱动程序(例如PMEM-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 中启动一个 Kubernetes 1.19 集群,并启用所有两个 Alpha 功能。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 和邮件列表联系。