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