随着自研上云的深入,越来越多的有状态服务对于在 TKE 集群中使用云上存储能力的需求也越来越强烈。
目前 腾讯云容器服务 TKE(Tencent Kubernetes Engine)已支持在 TKE 集群中的应用使用多种存储服务,包括 云硬盘 CBS、文件存储 CFS 以及 对象存储 COS。TKE 通过两种存储插件(In-Tree 和 CSI)来支持上述能力,用户可以通过云控制台很方便地选择存储类型并创建对应的 PV/PVC。但仍然会有一些问题困扰着大家,比如:TKE 集群中是否支持扩容 CBS 云盘;如果集群跨可用区,如何避免集群中频繁出现挂载(attach)失败;TKE 中是否支持快照功能;我的应用应该选择哪种类型存储;In-Tree 和 CSI 都支持 CBS,二者有和区别,是否能把之前使用 In-Tree 插件创建的云盘转变为 CSI 插件管理等。
对于 TKE 存储的相关问题,这里会详细介绍。接下来,我们先概览下 Kubernetes 持久化存储的流程
这里对 Kubernetes 持久化存储的流程做个概览,不深入各个组件。
创建一个使用了持久化存储的 pod 的流程包含以下步骤:
kubernetes-sigs/sig-storage-lib-external-provisioner
实现了一个 external provisioner,则与 out-of-tree 插件相同;如果是 in-tree 插件,且插件直接实现了相应的创删接口,则 PV Controller 直接调用 in-tree 插件的实现完成创建 PV。ControllerPublishVolume
)来 attach。/var/lib/kubelet/plugins
下),第二步是将 bind mount 刚才的 global mount path到/var/lib/kubelet/pods/${pod_UUID}/volumes
下。(Provision -> Attach -> Mount; Unmount -> Detach -> Delete)
随着 Kubernetes 社区发展,TKE 先后支持了 In-Tree 和 CSI 两种存储插件。二者在功能上的主要区别在于 In-Tree 存储插件仅支持在 TKE 集群使用 CBS,而 CSI 支持使用 CBS、CFS、COS。
类型 | 支持CBS | 支持CFS | 支持COS | 参考 |
---|---|---|---|---|
In-Tree | √ | × | × | |
CSI | √ | √ | √ | https://github.com/TencentCloud/kubernetes-csi-tencentcloud |
kubernetes 早期只支持以 In-Tree 的方式扩展存储插件,也就是插件在 Kubernetes 代码中实现。
In-Tree 插件名为cloud.tencent.com/qcloud-cbs
,所以也可称为 QcloudCbs,在 TKE 集群中有个默认的名为cbs
的 StorageClass。
NAME PROVISIONER AGE
cbs (default) cloud.tencent.com/qcloud-cbs 48m
In-Tree 插件只实现了使用 CBS 的能力,其主要特性有:
腾讯云存储 | 静态数据卷 | 动态数据卷 | 拓扑感知 | 调度器感知节点 maxAttachLimit |
---|---|---|---|---|
腾讯云硬盘(CBS) | 支持两种使用方式: | 支持 | 支持。pod 调度后,在同一个可用区创建 volume。避免 CBS 跨可用区无法使用。 | 支持。云服务器(cvm)可以挂载的云硬盘(cbs)是有上限的。调度器调度 pod 时过滤掉超过最大可挂载 CBS 数量的节点。 |
下面简单了解下 In-Tree 插件 QcloudCbs 的架构图,了解各相关组件分别完成何种工作。
上图是包含 TKE In-Tree 存储插件的 Kubernetes 存储架构图。图中绿色部分,皆属于 In-Tree 插件 QcloudCbs 的实现范畴。
由上述的 Kubernetes持久化存储流程 可知要动态使用一个 cbs pv,主要有三个过程:provision、attach、mount,而这三个过程是由不同组件负责的:
kubernetes-sigs/sig-storage-lib-external-provisioner
实现的一个 external provisioner,来 provision 和 delete volume。PV Controller 在这种模式下虽然不去 provision/delete volume,但是还是会参与处理(比如 PV 和 PVC 的绑定)。MaxQcloudCbsVolumeCount
,该策略主要实现调度器感知节点 maxAttachLimit特性。而 Scheduler 原生的一个 predicate 策略:NoVolumeZoneConflictPred
,是用来把 pod 调度到已有 PV 所在 zone 的节点,这可以避免云盘跨可用区挂载的问题;对于新建 PV 的话,避免云盘跨可用区挂载问题则由拓扑感知特性完成。CSI 是 Kubernetes 社区扩展卷的标准和推荐方式。TKE 的 CSI 插件包含 CBS、CFS、COS 三个 driver,本节重点介绍 CBS CSI driver,并与 QcloudCbs 进行对比。3个 driver 的静态 pv 和动态 pv 的支持情况如下表所示:
腾讯云存储 | 静态数据卷 | 动态数据卷 |
---|---|---|
云硬盘(CBS) | 支持 | 支持 |
文件存储(CFS) | 支持 | 支持 |
对象存储(COS) | 支持 | 不支持 |
存储插件 | 静态数据卷 | 动态数据卷 | 拓扑感知 | 调度器感知节点maxAttachLimit | 卷在线扩容 | 卷快照&恢复 |
---|---|---|---|---|---|---|
CBS CSI | √ | √ | √ | √ | √ | √ |
QcloudCbs(In-Tree) | √ | √ | √ | √ | × | × |
CSI 原理参考上图。要实现一个 CSI driver,一般需要实现以下 3 个 gRPC services(CSI Controller Service 可选):
CSI Identity Services:提供 driver 信息(drivername,版本等)
CSI Controller Services
(可选):controller 负责创删卷、attach/detach、扩容、快照等。涉及的方法如下:
CSI Node Services
:负责向节点注册 driver,mount/unmount。涉及的方法如下:
在我们实现之外,kuberntes Team 还提供了多个外部组件,用于沟通 k8s 原生组件(apiserver、controller manager、kubelet)与自己实现的 CSI driver。
PersistentVolumeClaim
(PVC)对象,调用 driver 的CreateVolume/DeleteVolume
VolumeAttachment
对象,调用 driver 的Controller[Publish|Unpublish]Volume
PersistentVolumeClaim
对象,调用 driver 的ControllerExpandVolume
VolumeSnapshot
和VolumeSnapshotContent
CRD 对象,external-snapshotter watch VolumeSnapshotContent
对象。调用 driver 的CreateSnapshot/DeleteSnapshot/ListSnapshots
NodeGetInfo
获取 driver 信息,然后使用 kubelet 插件注册机制注册 driver。CBS CSI 使用社区推荐部署方式,包含两个 workload:
NodePlugin
,由 CBS CSI Driver 和 node-driver-registrar 两个容器组成。负责向节点注册 driver,并提供 mount 的能力。Controller
。由 driver 和多个 sidecar(external-provisioner、external-attacher、external-resizer、external-snapshotter、snapshot-controller)一起构成,提供创删卷、attach/detach、扩容、快照等能力/var/lib/kubelet/plugins
的子目录下;之后 bind mount 阶段(NodePublishVolume)的 target path 是/var/lib/kubelet/pods
。所以我们为这两个目录都设置了挂载传播(模式为Bidirectional
)provisioner:
cbs csi 的安装请参见 cbs csi 文档,我们也已经在腾讯云控制台支持扩展组件安装。
本节最佳实践均以 cbs csi 插件为例,相应版本要求也是针对 cbs csi 插件。
cbs 云盘不支持跨可用区挂载到节点,所以在跨可用区的集群中推荐通过拓扑感知特性来避免跨可用区挂载的问题。
使用方式很简单,在 storageclass 中设置 volumeBindingMode
为WaitForFirstConsumer
,然后使用该 storageClass 即可。intree 和 csi 插件均支持。
kind: StorageClass
metadata:
name: cbs-topo
parameters:
type: cbs
provisioner: com.tencent.cloud.csi.cbs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
拓扑感知调度需要多个 k8s 组件配合完成,包括 scheduler、pv controller、external-provisioner。流程为:
WaitForFirstConsumer
,即不会马上处理该pvc的创建事件,等待 scheduler 处理;volume.kubernetes.io/selected-node: 10.0.0.72
volume.kubernetes.io/selected-node
),根据 nodeName 获取 Node 对象,传入到 provisioner 中。failure-domain.beta.kubernetes.io/zone
),之后在对应 zone 创建 pv,从而达到和 pod 相同可用区的效果,避免云盘和 node 在不同可用区而无法挂载。TKE 支持在线扩容 PV,对应的云盘及文件系统,即不需要重启 pod 即可完成扩容。但,为了确保文件系统的稳定性,还是推荐先让云盘文件系统处于未 mount 情况下。为此,我们将提供两种扩容方式:
在 storageclass 中设置allowVolumeExpansion
为true
:
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: cbs-csi-expand
parameters:
diskType: CLOUD_PREMIUM
provisioner: com.tencent.cloud.csi.cbs
reclaimPolicy: Delete
volumeBindingMode: Immediate
1、确认扩容前 pv 和文件系统状态,大小均为 20G
$ kubectl exec ivantestweb-0 df /usr/share/nginx/html
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vdd 20511312 45036 20449892 1% /usr/share/nginx/html
$ kubectl get pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c 20Gi RWO Delete Bound default/www1-ivantestweb-0 cbs-csi 20h
2、执行以下命令修改 PVC 对象中的容量,扩容至 30G
$ kubectl patch pvc www1-ivantestweb-0 -p '{"spec":{"resources":{"requests":{"storage":"30Gi"}}}}'
执行后稍等片刻,可以发现 pv 和文件系统已经扩容至 30G:
$ kubectl exec ivantestweb-0 df /usr/share/nginx/html
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vdd 30832548 44992 30771172 1% /usr/share/nginx/html
$ kubectl get pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c 30Gi RWO Delete Bound default/www1-ivantestweb-0 cbs-csi 20h
1、确认扩容前 pv 和文件系统状态,大小均为 30G
$ kubectl exec ivantestweb-0 df /usr/share/nginx/html
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vdd 30832548 44992 30771172 1% /usr/share/nginx/html
$ kubectl get pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c 30Gi RWO Delete Bound default/www1-ivantestweb-0 cbs-csi 20h
2、使用下面命令给 PV 对象打标签,打一个非法 zone,旨在下一步重启 pod 后 pod 无法调度到某个节点上
$ kubectl label pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c failure-domain.beta.kubernetes.io/zone=nozone
3、重启 pod。重启后由于 pod 对应的 pv 的标签表明的是非法 zone,pod 会处于 Pending 状态
$ kubectl delete pod ivantestweb-0
$ kubectl get pod ivantestweb-0
NAME READY STATUS RESTARTS AGE
ivantestweb-0 0/1 Pending 0 25s
$ kubectl describe pod ivantestweb-0
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 40s (x3 over 2m3s) default-scheduler 0/1 nodes are available: 1 node(s) had no available volume zone.
4、修改 PVC 对象中的容量,扩容至 40G
kubectl patch pvc www1-ivantestweb-0 -p '{"spec":{"resources":{"requests":{"storage":"40Gi"}}}}'
5、去掉 PV 对象之前打的标签,这样 pod 就能调度成功了。
$ kubectl label pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c failure-domain.beta.kubernetes.io/zone-persistentvolume/pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c labeled
稍等片刻,pod running,对应的 pv 和文件系统也扩容成功,从 30G 扩容到 40G 了
$ kubectl get pod ivantestweb-0
NAME READY STATUS RESTARTS AGE
ivantestweb-0 1/1 Running 0 17m
$ kubectl get pv pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c 40Gi RWO Delete Bound default/www1-ivantestweb-0 cbs-csi 20h
$ kubectl get pvc www1-ivantestweb-0
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www1-ivantestweb-0 Bound pvc-e193201e-6f6d-48cf-b96d-ccc09225cf9c 40Gi RWO cbs-csi 20h
$ kubectl exec ivantestweb-0 df /usr/share/nginx/html
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/vdd 41153760 49032 41088344 1% /usr/share/nginx/html
1、使用下面 yaml,创建VolumeSnapshotClass
对象
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotClass
metadata:
name: cbs-snapclass
driver: com.tencent.cloud.csi.cbs
deletionPolicy: Delete
创建后显示:
$ kubectl get volumesnapshotclass
NAME DRIVER DELETIONPOLICY AGE
cbs-snapclass com.tencent.cloud.csi.cbs Delete 17m
2、使用下面 yaml,创建
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
metadata:
name: new-snapshot-demo
spec:
volumeSnapshotClassName: cbs-snapclass
source:
persistentVolumeClaimName: csi-pvc
创建后稍等片刻,volumesnapshot 和 volumesnapshotcontent 对象都创建成功,READYTOUSE
为 true:
$ kubectl get volumesnapshot
NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE
new-snapshot-demo true www1-ivantestweb-0 10Gi cbs-snapclass snapcontent-ea11a797-d438-4410-ae21-41d9147fe610 22m 22m
$ kubectl get volumesnapshotcontent
NAME READYTOUSE RESTORESIZE DELETIONPOLICY DRIVER VOLUMESNAPSHOTCLASS VOLUMESNAPSHOT AGE
snapcontent-ea11a797-d438-4410-ae21-41d9147fe610 true 10737418240 Delete com.tencent.cloud.csi.cbs cbs-snapclass new-snapshot-demo 22m
具体快照 id 在 volumesnapshotcontent 对象中,status.snapshotHandle
(snap-e406fc9m),可以根据这个快照 id 在腾讯云控制台确认快照是否存在
$ kubectl get volumesnapshotcontent snapcontent-ea11a797-d438-4410-ae21-41d9147fe610 -oyaml
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshotContent
metadata:
creationTimestamp: "2020-11-04T08:58:39Z"
finalizers:
- snapshot.storage.kubernetes.io/volumesnapshotcontent-bound-protection
name: snapcontent-ea11a797-d438-4410-ae21-41d9147fe610
resourceVersion: "471437790"
selfLink: /apis/snapshot.storage.k8s.io/v1beta1/volumesnapshotcontents/snapcontent-ea11a797-d438-4410-ae21-41d9147fe610
uid: 70d0390b-79b8-4276-aa79-a32e3bdef3d6
spec:
deletionPolicy: Delete
driver: com.tencent.cloud.csi.cbs
source:
volumeHandle: disk-7z32tin5
volumeSnapshotClassName: cbs-snapclass
volumeSnapshotRef:
apiVersion: snapshot.storage.k8s.io/v1beta1
kind: VolumeSnapshot
name: new-snapshot-demo
namespace: default
resourceVersion: "471418661"
uid: ea11a797-d438-4410-ae21-41d9147fe610
status:
creationTime: 1604480319000000000
readyToUse: true
restoreSize: 10737418240
snapshotHandle: snap-e406fc9m
1、我们在 3.2.1 中创建的VolumeSnapshot
的对象名为new-snapshot-demo
,使用下面 yaml 来从快照恢复一个卷
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restore-test
spec:
storageClassName: cbs-csi
dataSource:
name: new-snapshot-demo
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
发现 restore 的 pvc 已经创建出来,diskid 也在 pv 中(disk-gahz1kw1)
$ kubectl get pvc restore-test
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
restore-test Bound pvc-80b98084-29a3-4a38-a96c-2f284042cf4f 10Gi RWO cbs-csi 97s
$ kubectl get pv pvc-80b98084-29a3-4a38-a96c-2f284042cf4f -oyaml
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: com.tencent.cloud.csi.cbs
creationTimestamp: "2020-11-04T12:08:25Z"
finalizers:
- kubernetes.io/pv-protection
name: pvc-80b98084-29a3-4a38-a96c-2f284042cf4f
resourceVersion: "474676883"
selfLink: /api/v1/persistentvolumes/pvc-80b98084-29a3-4a38-a96c-2f284042cf4f
uid: 5321df93-5f21-4895-bafc-71538d50293a
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: restore-test
namespace: default
resourceVersion: "474675088"
uid: 80b98084-29a3-4a38-a96c-2f284042cf4f
csi:
driver: com.tencent.cloud.csi.cbs
fsType: ext4
volumeAttributes:
diskType: CLOUD_PREMIUM
storage.kubernetes.io/csiProvisionerIdentity: 1604478835151-8081-com.tencent.cloud.csi.cbs
volumeHandle: disk-gahz1kw1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.com.tencent.cloud.csi.cbs/zone
operator: In
values:
- ap-beijing-2
persistentVolumeReclaimPolicy: Delete
storageClassName: cbs-csi
volumeMode: Filesystem
status:
phase: Bound
联系客服