kubernetes/k8s CSI 分析 - 容器存储接口分析
更多 k8s CSI 的分析,可以查看这篇博客https://xie.infoq.cn/article/4b1d3e32f124307a49cd9c1e3,以 ceph-csi 为例,做了详细的源码分析。
概述
kubernetes 的设计初衷是支持可插拔架构,从而利于扩展kubernetes
的功能。在此架构思想下,kubernetes
提供了 3 个特定功能的接口,分别是容器网络接口CNI
、容器运行时接口CRI
和容器存储接口CSI
。kubernetes
通过调用这几个接口,来完成相应的功能。
下面我们来对容器存储接口CSI
来做一下介绍与分析。
在本文中,会对CSI
是什么、为什么要有CSI
、CSI
系统架构做一下介绍,然后对CSI
所涉及的k8s
对象与组件进行了简单的介绍,以及k8s
对CSI
存储进行相关操作的流程分析,存储相关操作包括了存储创建、存储扩容、存储挂载、解除存储挂载以及存储删除操作。
CSI 是什么
CSI 是Container Storage Interface
(容器存储接口)的简写。
CSI 的目的是定义行业标准“容器存储接口”,使存储供应商(SP)能够开发一个符合 CSI 标准的插件并使其可以在多个容器编排(CO)系统中工作。CO 包括Cloud Foundry
, Kubernetes
, Mesos
等。
kubernetes 将通过CSI
接口来跟第三方存储厂商进行通信,来操作存储,从而提供容器存储服务。
为什么要有 CSI
其实在没有CSI
之前kubernetes
就已经提供了强大的存储卷插件系统,但是这些插件系统实现是kubernetes
代码的一部分,需要随kubernetes
组件二进制文件一起发布,这样就会存在一些问题。
(1)如果第三方存储厂商发现有问题需要修复或者优化,即使修复后也不能单独发布,需要与kubernetes
一起发布,对于k8s
本身而言,不仅要考虑自身的正常迭代发版,还需要考虑到第三方存储厂商的迭代发版,这里就存在双方互相依赖、制约的问题,不利于双方快速迭代;
(2)另外第三方厂商的代码跟kubernetes
代码耦合在一起,还会引起安全性、可靠性问题,还增加了kubernetes
代码的复杂度以及后期的维护成本等等。
基于以上问题,kubernetes
将存储体系抽象出了外部存储组件接口即CSI
,kubernetes
通过grpc
接口与第三方存储厂商的存储卷插件系统进行通信。
这样一来,对于第三方存储厂商来说,既可以单独发布和部署自己的存储插件,进行正常迭代,而又无需接触kubernetes
核心代码,降低了开发的复杂度。同时,对于kubernetes
来说,这样不仅降低了自身的维护成本,还能为用户提供更多的存储选项。
CSI 系统架构
这是一张 k8s csi 的系统架构图,图中所画的组件以及 k8s 对象,接下来会一一进行分析。
CSI 相关组件一般采用容器化部署,减少环境依赖。
涉及 k8s 对象
1. PersistentVolume
持久存储卷,集群级别资源,代表了存储卷资源,记录了该存储卷资源的相关信息。
回收策略
(1)retain:保留策略,当删除 pvc 的时候,保留 pv 与外部存储资源。
(2)delete:删除策略,当与 pv 绑定的 pvc 被删除的时候,会从 k8s 集群中删除 pv 对象,并执行外部存储资源的删除操作。
(3)resycle(已废弃)
pv 状态迁移
available --> bound --> released
2. PersistentVolumeClaim
持久存储卷声明,namespace 级别资源,代表了用户对于存储卷的使用需求声明。
示例:
pvc 状态迁移
pending --> bound
3. StorageClass
定义了创建 pv 的模板信息,集群级别资源,用于动态创建 pv。
示例:
4. VolumeAttachment
VolumeAttachment 记录了 pv 的相关挂载信息,如挂载到哪个 node 节点,由哪个 volume plugin 来挂载等。
AD Controller 创建一个 VolumeAttachment,而 External-attacher 则通过观察该 VolumeAttachment,根据其状态属性来进行存储的挂载和卸载操作。
示例:
5. CSINode
CSINode 记录了 csi plugin 的相关信息(如 nodeId、driverName、拓扑信息等)。
当 Node Driver Registrar 向 kubelet 注册一个 csi plugin 后,会创建(或更新)一个 CSINode 对象,记录 csi plugin 的相关信息。
示例:
涉及组件与作用
下面来介绍下涉及的组件与作用。
1. volume plugin
扩展各种存储类型的卷的管理能力,实现第三方存储的各种操作能力与 k8s 存储系统的结合。调用第三方存储的接口或命令,从而提供数据卷的创建/删除、attach/detach、mount/umount 的具体操作实现,可以认为是第三方存储的代理人。前面分析组件中的对于数据卷的创建/删除、attach/detach、mount/umount 操作,全是调用 volume plugin 来完成。
根据源码所在位置,volume plugin 分为 in-tree 与 out-of-tree。
in-tree
在 k8s 源码内部实现,和 k8s 一起发布、管理,更新迭代慢、灵活性差。
out-of-tree
代码独立于 k8s,由存储厂商实现,有 csi、flexvolume 两种实现。
csi plugin
csi plugin 分为 ControllerServer 与 NodeServer,各负责不同的存储操作。
external plugin
external plugin 包括了 external-provisioner、external-attacher、external-resizer、external-snapshotter 等,external plugin 辅助 csi plugin 组件,共同完成了存储相关操作。external plugin 负责 watch pvc、volumeAttachment 等对象,然后调用 volume plugin 来完成存储的相关操作。如 external-provisioner watch pvc 对象,然后调用 csi plugin 来创建存储,最后创建 pv 对象;external-attacher watch volumeAttachment 对象,然后调用 csi plugin 来做 attach/dettach 操作;external-resizer watch pvc 对象,然后调用 csi plugin 来做存储的扩容操作等。
Node-Driver-Registrar
Node-Driver-Registrar 组件负责实现 csi plugin(NodeServer)的注册,让 kubelet 感知 csi plugin 的存在。
组件部署方式
csi plugin controllerServer 与 external plugin 作为容器,使用 deployment 部署,多副本可实现高可用;而 csi plugin NodeServer 与 Node-Driver-Registrar 作为容器,使用 daemonset 部署,即每个 node 节点都有。
2. kube-controller-manager
PV controller
负责 pv、pvc 的绑定与生命周期管理(如创建/删除底层存储,创建/删除 pv 对象,pv 与 pvc 对象的状态变更)。
(1)in-tree:创建/删除底层存储、创建/删除 pv 对象的操作,由 PV controller 调用 volume plugin(in-tree)来完成。
(2)out-tree CSI:创建/删除底层存储、创建/删除 pv 对象的操作由 external-provisioner 与 csi plugin 共同来完成。
AD controller
AD Cotroller 全称 Attachment/Detachment 控制器,主要负责创建、删除 VolumeAttachment 对象,并调用 volume plugin 来做存储设备的 Attach/Detach 操作(将数据卷挂载到特定 node 节点上/从特定 node 节点上解除挂载),以及更新 node.Status.VolumesAttached 等。
不同的 volume plugin 的 Attach/Detach 操作逻辑有所不同,对于 csi plugin(out-tree volume plugin)来说,AD controller 的 Attach/Detach 操作只是修改 VolumeAttachment 对象的状态,而不会真正的将数据卷挂载到节点/从节点上解除挂载,真正的节点存储挂载/解除挂载操作由 kubelet 中 volume manager 调用 csi plugin 来完成。
3. kubelet
volume manager
主要是管理卷的 Attach/Detach(与 AD controller 作用相同,通过 kubelet 启动参数控制哪个组件来做该操作)、mount/umount 等操作。
对于 csi 来说,volume manager 的 Attach/Detach 操作只创建/删除 VolumeAttachment 对象,而不会真正的将数据卷挂载到节点/从节点上解除挂载;csi-attacer 组件也不会做挂载/解除挂载操作,只是更新 VolumeAttachment 对象,真正的节点存储挂载/解除挂载操作由 kubelet 中 volume manager 调用调用 csi plugin 来完成。
kubernetes 创建与挂载 volume(in-tree volume plugin)
先来看下 kubernetes 通过 in-tree volume plugin 来创建与挂载 volume 的流程
(1)用户创建pvc
;
(2)PV controller
watch 到pvc
的创建,寻找合适的pv
与之绑定。
(3)(4)当找不到合适的pv
时,将调用volume plugin
来创建 volume,并创建pv
对象,之后该pv
对象与pvc
对象绑定。
(5)用户创建挂载pvc
的pod
;
(6)kube-scheduler
watch 到pod
的创建,为其寻找合适的 node 调度。
(7)(8)pod
调度完成后,AD controller
/volume manager
watch 到pod
声明的 volume 没有进行attach
操作,将调用volume plugin
来做attach
操作。
(9)volume plugin
进行attach
操作,将 volume 挂载到pod
所在 node 节点,成为如/dev/vdb
的设备。
(10)(11)attach
操作完成后,volume manager
watch 到pod
声明的 volume 没有进行mount
操作,将调用volume plugin
来做mount
操作。
(12)volume plugin
进行mount
操作,将 node 节点上的第(9)步得到的/dev/vdb
设备挂载到指定目录。
kubernetes 创建与挂载 volume(out-of-tree volume plugin)
再来看下 kubernetes 通过 out-of-tree volume plugin 来创建与挂载 volume 的流程,以 csi-plugin 为例。
(1)用户创建pvc
;
(2)PV controller
watch 到pvc
的创建,寻找合适的pv
与之绑定。当寻找不到合适的pv
时,将更新pvc
对象,添加annotation
:volume.beta.kubernetes.io/storage-provisioner
,让external-provisioner
组件开始开始创建存储与pv
对象的操作。
(3)external-provisioner
组件 watch 到pvc
的创建/更新事件,判断annotation
:volume.beta.kubernetes.io/storage-provisioner
的值,即判断是否是自己来负责做创建操作,是则调用csi-plugin ControllerServer
来创建存储,并创建pv
对象(这里的pv
对象使用了提前绑定特性,将pvc
信息填入了pv
对象的spec.claimRef
属性)。
(4)PV controller
将上一步创建的pv
与pvc
绑定。
(5)用户创建挂载pvc
的pod
;
(6)kube-scheduler
watch 到pod
的创建,为其寻找合适的node
调度。
(7)(8)pod
调度完成后,AD controller
/volume manager
watch 到pod
声明的 volume 没有进行attach
操作,将调用csi-attacher
来做attach
操作(实际上只是创建volumeAttachement
对象)。
(9)external-attacher
组件 watch 到volumeAttachment
对象的新建,调用csi-plugin
进行attach
操作(如果volume plugin
是ceph-csi
,external-attacher
组件 watch 到volumeAttachment
对象的新建后,只是修改该对象的状态属性,不会做attach
操作,真正的attach
操作由kubelet
中的volume manager
调用volume plugin
ceph-csi
来完成)。
(10)csi-plugin ControllerServer
进行attach
操作,将 volume 挂载到pod
所在node
节点,成为如/dev/vdb
的设备。
(11)(12)attach
操作完成后,volume manager
watch 到pod
声明的 volume 没有进行mount
操作,将调用csi-mounter
来做mount
操作。
(13)csi-mounter
调用csi-plugin NodeServer
进行mount
操作,将 node 节点上的第(10)步得到的/dev/vdb
设备挂载到指定目录。
kubernetes 存储相关操作流程具体分析(out-of-tree volume plugin,以 csi plugin:ceph-csi 为例)
下面来看下kubernetes
通过ceph-csi volume plugin
来创建/删除、挂载/解除挂载 ceph 存储的流程。
1. 存储创建
流程图
流程分析
(1)用户创建pvc
对象;
(2)pv controller
监听pvc
对象,寻找现存的合适的pv
对象,与pvc
对象绑定。当找不到现存合适的pv
对象时,将更新pvc
对象,添加annotation
:volume.beta.kubernetes.io/storage-provisioner
,让external-provisioner
组件开始开始创建存储与pv
对象的操作;当找到时,将pvc
与pv
绑定,结束操作。
(3)external-provisioner
组件监听到pvc
的新增事件,判断pvc
的annotation
:volume.beta.kubernetes.io/storage-provisioner
的值,即判断是否是自己来负责做创建操作,是则调用ceph-csi
组件进行存储的创建;
(4)ceph-csi
组件调用 ceph 创建底层存储;
(5)底层存储创建完成后,external-provisioner
根据存储信息,拼接pv
对象,创建pv
对象(这里的pv
对象使用了提前绑定特性,将pvc
信息填入了pv
对象的spec.claimRef
属性);
(6)pv controller
监听pvc
对象,将第(5)步创建的pv
对象,与pvc
对象绑定。
2. 存储扩容
流程图
流程分析
(1)修改pvc
对象,修改申请存储大小(pvc.spec.resources.requests.storage
);
(2)修改成功后,external-resizer
监听到该pvc
的update
事件,发现pvc.Spec.Resources.Requests.storgage
比pvc.Status.Capacity.storgage
大,于是调ceph-csi
组件进行 controller 端扩容;
(3)ceph-csi
组件调用 ceph 存储,进行底层存储扩容;
(4)底层存储扩容完成后,ceph-csi
组件更新pv
对象的.Spec.Capacity.storgage
的值为扩容后的存储大小;
(5)kubelet
的volume manager
在reconcile()
调谐过程中发现pv.Spec.Capacity.storage
大于pvc.Status.Capacity.storage
,于是调ceph-csi
组件进行 node 端扩容;
(6)ceph-csi
组件对 node 上存储对应的文件系统扩容;
(7)扩容完成后,kubelet
更新pvc.Status.Capacity.storage
的值为扩容后的存储大小。
3. 存储挂载
流程图
kubelet 启动参数--enable-controller-attach-detach
,该启动参数设置为 true
表示启用 Attach/Detach controller
进行Attach/Detach
操作,同时禁用 kubelet
执行 Attach/Detach
操作(默认值为 true
)。实际上Attach/Detach
操作就是创建/删除VolumeAttachment
对象。
(1)kubelet
启动参数--enable-controller-attach-detach=true
,Attach/Detach controller
进行Attach/Detach
操作。
(2)kubelet
启动参数--enable-controller-attach-detach=false
,kubelet
端volume manager
进行Attach/Detach
操作。
流程分析
(1)用户创建一个挂载了pvc
的pod
;
(2)AD controller
或volume manager
中的reconcile()
发现有volume
未执行attach
操作,于是进行attach
操作,即创建VolumeAttachment
对象;
(3)external-attacher
组件list/watch
VolumeAttachement
对象,更新VolumeAttachment.status.attached=true
;
(4)AD controller
更新node
对象的.Status.VolumesAttached
属性值,将该volume
记为attached
;
(5)kubelet
中的volume manager
获取node.Status.VolumesAttached
属性值,发现volume
已被标记为attached
;
(6)于是volume manager
中的reconcile()
调用ceph-csi
组件的NodeStageVolume
与NodePublishVolume
完成存储的挂载。
4. 解除存储挂载
流程图
(1)kubelet
启动参数--enable-controller-attach-detach=true
,Attach/Detach controller
进行Attach/Detach
操作。
(2)kubelet
启动参数--enable-controller-attach-detach=false
,kubelet
端volume manager
进行Attach/Detach
操作。
流程分析
(1)用户删除声明了pvc
的pod
;
(2)AD controller
或volume manager
中的reconcile()
发现有volume
未执行dettach
操作,于是进行dettach
操作,即删除VolumeAttachment
对象;
(3)AD controller
或volume manager
等待VolumeAttachment
对象删除成功;
(4)AD controller
更新node
对象的.Status.VolumesAttached
属性值,将标记为attached
的该volume
从属性值中去除;
(5)kubelet
中的volume manager
获取node.Status.VolumesAttached
属性值,找不到相关的volume
信息;
(6)于是volume manager
中的reconcile()
调用ceph-csi
组件的NodeUnpublishVolume
与NodeUnstageVolume
完成存储的解除挂载操作。
5. 删除存储
流程图
流程分析
(1)用户删除pvc
对象;
(2)pv controller
发现与pv
绑定的pvc
对象被删除,于是更新pv
的状态为released
;
(3)external-provisioner
watch 到pv
更新事件,并检查pv
的状态是否为released
,以及回收策略是否为delete
;
(4)确认了pv
对象的状态以及回收策略之后,接下来external-provisioner
组件会调用ceph-csi
的DeleteVolume
来删除存储;
(5)ceph-csi
组件的DeleteVolume
方法,调用 ceph 集群命令,删除底层存储;
(6)删除底层存储后,external-provisioner
组件删除pv
对象。
总结
CSI 即Container Storage Interface
(容器存储接口)。
为了解决第三方存储厂商的存储卷插件代码集成到 kubernetes 代码中所带来的各种问题,kubernetes
将存储体系抽象出了外部存储组件接口即CSI
,kubernetes
通过grpc
接口与第三方存储厂商的存储卷插件系统进行通信,来操作存储,从而提供容器存储服务。
这样一来,对于第三方存储厂商来说,既可以单独发布和部署自己的存储插件,进行正常迭代,而又无需接触kubernetes
核心代码,降低了开发的复杂度。同时,对于kubernetes
来说,这样不仅降低了自身的维护成本,还能为用户提供更多的存储选项。
最后,再来回顾一下 kubernetes CSI 的架构。
更多 k8s CSI 的分析,可以查看这篇博客https://xie.infoq.cn/article/4b1d3e32f124307a49cd9c1e3,以 ceph-csi 为例,做了详细的源码分析。
版权声明: 本文为 InfoQ 作者【良凯尔】的原创文章。
原文链接:【http://xie.infoq.cn/article/5cd26c1b24c5665820411bb5a】。文章转载请联系作者。
评论