云原生架构-kubernetes存储原理
Kubernetes 默认情况下就提供了主流的存储卷接入方案,我们可以执行命令 kubectl explain pod.spec.volumes
查看到支持的各种存储卷,另外在Kubernetes中,volume的使用方式类似于虚拟机的磁盘,需要给pod(即一个逻辑上的虚拟机)挂一个磁盘,然后该pod里的进程(容器)才能通过volumeMounts的方式使用挂载磁盘。pod容器内的进程能够看到的文件系统由两部分组成:一部分是Docker像文件系统,另一部分是零或多个volume。每个容器都会单独指定每个volume在其内部的挂载点,即pod资源文件的volumeMounts属性.这也印证了nod内的容器是共享这个volume的,目前主要有 CSI
和 FlexVolume
两种机制,开发者可以根据自己的存储类型实现不同的存储插件接入到 Kubernetes 中去,其中 CSI
是现在也是以后主流的方式,所以当然我们的重点也会是 CSI
的使用介绍。
kubectl explain pod.spec.volumes
# kubectl explain pod.spec.volumes
KIND: Pod
VERSION: v1
RESOURCE: volumes <[]Object>
DESCRIPTION:
List of volumes that can be mounted by containers belonging to the pod.
More info: https://kubernetes.io/docs/concepts/storage/volumes
Volume represents a named volume in a pod that may be accessed by any
container in the pod.
FIELDS:
awsElasticBlockStore <Object>
AWSElasticBlockStore represents an AWS Disk resource that is attached to a
host machine and then exposed to the pod. More info:
https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore
azureDisk <Object>
AzureDisk represents an Azure Data Disk mount on the host and bind mount to
the pod.
azureFile <Object>
AzureFile represents an Azure File Service mount on the host and bind mount
to the pod.
cephfs <Object>
CephFS represents a Ceph FS mount on the host that shares a pod's lifetime
cinder <Object>
Cinder represents a cinder volume attached and mounted on kubelets host
More info: https://examples.k8s.io/mysql-cinder-pd/README.md
configMap <Object>
ConfigMap represents a configMap that should populate this volume
csi <Object>
CSI (Container Storage Interface) represents storage that is handled by an
external CSI driver (Alpha feature).
downwardAPI <Object>
DownwardAPI represents downward API about the pod that should populate this
volume
emptyDir <Object>
EmptyDir represents a temporary directory that shares a pod's lifetime.
More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
fc <Object>
FC represents a Fibre Channel resource that is attached to a kubelet's host
machine and then exposed to the pod.
flexVolume <Object>
FlexVolume represents a generic volume resource that is
using an exec based plugin.
flocker <Object>
Flocker represents a Flocker volume attached to a kubelet's host machine.
This depends on the Flocker control service being running
gcePersistentDisk <Object>
GCEPersistentDisk represents a GCE Disk resource that is attached to a
host machine and then exposed to the pod. More info:
https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk
gitRepo <Object>
GitRepo represents a git repository at a particular revision. DEPRECATED:
GitRepo is deprecated. To provision a container with a git repo, mount an
EmptyDir into an InitContainer that clones the repo using git, then mount
the EmptyDir into the Pod's container.
glusterfs <Object>
Glusterfs represents a Glusterfs mount on the host that shares a pod's
More info: https://examples.k8s.io/volumes/glusterfs/README.md
hostPath <Object>
HostPath represents a pre-existing file or directory on the host machine
that is directly exposed to the container. This is generally used for
system agents or other privileged things that are allowed to see the host
Most containers will NOT need this. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath
iscsi <Object>
ISCSI represents an ISCSI Disk resource that is attached to a kubelet's
host machine and then exposed to the pod. More info:
https://examples.k8s.io/volumes/iscsi/README.md
name <string> -required-
name. Must be a DNS_LABEL and unique within the pod. More info:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
nfs <Object>
NFS represents an NFS mount on the host that shares a pod's lifetime More
info: https://kubernetes.io/docs/concepts/storage/volumes#nfs
persistentVolumeClaim <Object>
PersistentVolumeClaimVolumeSource represents a reference to a
PersistentVolumeClaim in the same namespace. More info:
https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
photonPersistentDisk <Object>
PhotonPersistentDisk represents a PhotonController persistent disk attached
and mounted on kubelets host machine
portworxVolume <Object>
PortworxVolume represents a portworx volume attached and mounted on
kubelets host machine
projected <Object>
Items for all in one resources secrets, configmaps, and downward API
qcloudCbs <Object>
QcloudCbs represents a qcloud cbs data disk mount on the host and bind
mount to the pod
quobyte <Object>
Quobyte represents a Quobyte mount on the host that shares a pod's lifetime
rbd <Object>
RBD represents a Rados Block Device mount on the host that shares a pod's
More info: https://examples.k8s.io/volumes/rbd/README.md
scaleIO <Object>
ScaleIO represents a ScaleIO persistent volume attached and mounted on
Kubernetes nodes.
secret <Object>
Secret represents a secret that should populate this volume. More info:
https://kubernetes.io/docs/concepts/storage/volumes#secret
storageos <Object>
StorageOS represents a StorageOS volume attached and mounted on Kubernetes
nodes.
vsphereVolume <Object>
VsphereVolume represents a vSphere volume attached and mounted on kubelets
host machine
一、存储架构
Volume Plugins
支持这套标准以后,K8S和存储提供者之间将彻底解耦,终极目标是将存储的所有的部件作为sidecar container运行在K8S上(当前K8s 1.8版本设计还没有完全做到,需要一个兼容的发展周期),而不再作为K8S部件运行在host上。
存储提供的扩展接口, 包含了各类存储提供者的plugin实现。
实现自定义的Plugins 可以通过FlexVolume(K8s 1.8版本,目前算是过度方案)
kubernetes 1.9以后可能推荐CSI(Container Storage Interface)用方式来实现。
Volume Manager
Kubelet会监听到调度到该节点上的pod声明,会把pod缓存到Pod Manager中,VolumeManager通过Pod Manager获取PV/PVC的状态,并进行分析出具体的attach/detach、mount/umount, 操作然后调用plugin进行相应的业务处理
运行在kubelet 里让存储Ready的部件,主要是mount/unmount(attach/detach可选)
pod调度到这个node上后才会有卷的相应操作,所以它的触发端是kubelet(严格讲是kubelet里的pod manager),根据Pod Manager里pod spec里申明的存储来触发卷的挂载操作
PV/PVC Controller
运行在Master上的部件,主要做provision/delete
PV Controller和K8S其它组件一样监听API Server中的资源更新,对于卷管理主要是监听PV,PVC, SC三类资源,当监听到这些资源的创建、删除、修改时,PV Controller经过判断是需要做创建、删除、绑定、回收等动作。
Attach/Detach Controller
运行在Master上,主要做一些块设备(block device)的attach/detach(eg:rbd,cinder块设备需要在mount之前先挂载到主机上,看源码看哪那些实现了Attah接口)
非必须controller: 为了在attach卷上支持plugin headless形态,Controller Manager提供配置可以禁用。
它的核心职责就是当API Server中,有卷声明的pod与node间的关系发生变化时,需要决定是通过调用存储插件将这个pod关联的卷attach到对应node的主机(或者虚拟机)上,还是将卷从node上detach掉.
K8s挂载卷的基本过程
Volume Manager等待设备挂载完成,将卷挂载到节点指定目录(mount)
/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/vol-xxxxxxxxxxxxxxxxx
Kubelet在被告知设备准备好后启动Pod中的容器,利用Docker –v等参数将已经挂载到本地 的卷映射到容器中(volume mapping)
用户创建Pod包含一个PVC
Pod被分配到节点NodeA
Kubelet等待Volume Manager准备设备
PV controller调用相应Volume Plugin(in-tree或者out-of-tree)创建持久化卷并在系统中创建 PV对象以及其与PVC的绑定(Provision)
Attach/Detach controller或者Volume Manager通过Volume Plugin实现块设备挂载(Attach)
源码分析
kubelet管理volume的方式基于两个不同的状态:
DesiredStateOfWorld:预期中,pod对volume的使用情况,简称预期状态。当pod.yaml定制好volume,并提交成功,预期状态就已经确定.
ActualStateOfWorld:实际中,pod对voluem的使用情况,简称实际状态。实际状态是kubelet的后台线程监控的结果.
vm.desiredStateOfWorldPopulator.Run方法根据从apiserver同步到的pod信息,来更新DesiredStateOfWorld。另外一个方法vm.reconciler.Run,是预期状态和实际状态的协调者,它负责将实际状态调整成与预期状态。预期状态的更新实现,以及协调者具体如何协调.
如下所示,我们创建了一个 nfs 类型的 PV 资源对象:(volume.yaml)
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/k8s # 指定nfs的挂载点
server: 10.151.30.11 # 指定nfs服务地址
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
我们知道用户真正使用的是 PVC,而要使用 PVC 的前提就是必须要先和某个符合条件的 PV 进行一一绑定,比如存储容器、访问模式,以及 PV 和 PVC 的 storageClassName 字段必须一样,这样才能够进行绑定,当 PVC 和 PV 绑定成功后就可以直接使用这个 PVC 对象了:(pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: test-volumes
spec:
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs-pvc
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
subPath: test-volumes
mountPath: "/usr/share/nginx/html"
直接创建上面的资源对象即可:
$ kubectl apply -f volume.yaml
$ kubectl apply -f pod.yaml
我们只是在 volumes 中指定了我们上面创建的 PVC 对象,当这个 Pod 被创建之后, kubelet 就会把这个 PVC 对应的这个 NFS 类型的 Volume(PV)挂载到这个 Pod 容器中的目录中去。前面我们也提到了这样的话对于普通用户来说完全就不用关心后面的具体存储在 NFS 还是 Ceph 或者其他了,只需要直接使用 PVC 就可以了,因为真正的存储是需要很多相关的专业知识的,这样就完全职责分离解耦了。
普通用户直接使用 PVC 没有问题,但是也会出现一个问题,那就是当普通用户创建一个 PVC 对象的时候,这个时候系统里面并没有合适的 PV 来和它进行绑定,因为 PV 大多数情况下是管理员给我们创建的,这个时候启动 Pod 肯定就会失败了,如果现在管理员如果去创建一个对应的 PV 的话,PVC 和 PV 当然就可以绑定了,然后 Pod 也会自动的启动成功,这是因为在 Kubernetes 中有一个专门处理持久化存储的控制器 Volume Controller,这个控制器下面有很多个控制循环,其中一个就是用于 PV 和 PVC 绑定的 PersistentVolumeController。
PersistentVolumeController 会不断地循环去查看每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与未绑定的 PVC 进行绑定,这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态。而所谓将一个 PV 与 PVC 进行“绑定”
,其实就是将这个 PV 对象的名字,填在了 PVC 对象的 spec.volumeName
字段上。
PV 和 PVC 绑定上了,那么又是如何将容器里面的数据进行持久化的呢,前面我们学习过 Docker 的 Volume 挂载,其实就是将一个宿主机上的目录和一个容器里的目录绑定挂载在了一起,具有持久化功能当然就是指的宿主机上面的这个目录了,当容器被删除或者在其他节点上重建出来以后,这个目录里面的内容依然存在,所以一般情况下实现持久化是需要一个远程存储的,比如 NFS、Ceph 或者云厂商提供的磁盘等等。所以接下来需要做的就是持久化宿主机目录这个过程。
当 Pod 被调度到一个节点上后,节点上的 kubelet 组件就会为这个 Pod 创建它的 Volume 目录,默认情况下 kubelet 为 Volume 创建的目录在 kubelet 工作目录下面:
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
比如上面我们创建的 Pod 对应的 Volume 目录完整路径为:
/var/lib/kubelet/pods/d4fcdb11-baf7-43d9-8d7d-3ede24118e08/volumes/kubernetes.io~nfs/nfs-pv
“
要获取 Pod 的唯一标识 uid,可通过命令
kubectl get pod pod名 -o jsonpath={.metadata.uid}
获取。”
然后就需要根据我们的 Volume 类型来决定需要做什么操作了,比如上节课我们用的 Ceph RBD,那么 kubelet 就需要先将 Ceph 提供的 RBD 挂载到 Pod 所在的宿主机上面,这个阶段在 Kubernetes 中被称为 Attach 阶段。Attach 阶段完成后,为了能够使用这个块设备,kubelet 还要进行第二个操作,即:格式化这个块设备,然后将它挂载到宿主机指定的挂载点上。这个挂载点,也就是上面我们提到的 Volume 的宿主机的目录。将块设备格式化并挂载到 Volume 宿主机目录的操作,在 Kubernetes 中被称为 Mount 阶段。上节课我们使用 Ceph RBD 持久化的 Wordpress 的 MySQL 数据,我们可以查看对应的 Volume 信息:
$ kubectl get pods -o wide -l app=wordpress
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
wordpress-5b886cf59b-dv2zt 1/1 Running 0 20d 10.244.1.158 ydzs-node1 <none> <none>
wordpress-mysql-b9ddd6d4c-pjhbt 1/1 Running 0 20d 10.244.4.70 ydzs-node4 <none> <none>
我们可以看到 MySQL 运行在 node4 节点上,然后可以在该节点上查看 Volume 信息,Pod 对应的 uid 可以通过如下命令获取:
$ kubectl get pod wordpress-mysql-b9ddd6d4c-pjhbt -o jsonpath={.metadata.uid}
3f84af87-9f58-4c69-9e38-5ef234498133
$ ls /var/lib/kubelet/pods/3f84af87-9f58-4c69-9e38-5ef234498133/volumes/kubernetes.io~csi/pvc-c8861c23-c03d-47aa-96f6-73c4d4093109/
mount vol_data.json
然后通过如下命令可以查看 Volume 的持久化信息:
$ findmnt /var/lib/kubelet/pods/3f84af87-9f58-4c69-9e38-5ef234498133/volumes/kubernetes.io~csi/pvc-c8861c23-c03d-47aa-96f6-73c4d4093109/mount
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/3f84af87-9f58-4c69-9e38-5ef234498133/volumes/kubernetes.io~csi/pvc-c8861c23-c03d-47aa-96f6-73c4d4093109/mount /dev/rbd0 ext4 rw,relatime,
可以看到这里的 Volume 是挂载到 /dev/rbd0
这个设备上面的,通过 df
命令也是可以看到的:
$ df -h |grep dev
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 0 3.9G 0% /dev/shm
/dev/vda3 18G 4.7G 13G 27% /
/dev/vda1 497M 158M 340M 32% /boot
/dev/vdb1 197G 24G 164G 13% /data
/dev/rbd0 20G 160M 20G 1% /var/lib/kubelet/pods/3f84af87-9f58-4c69-9e38-5ef234498133/volumes/kubernetes.io~csi/pvc-c8861c23-c03d-47aa-96f6-73c4d4093109/mount
这里我们就经过了 Attach
和 Mount
两个阶段完成了 Volume 的持久化。但是对于上面我们使用的 NFS 就更加简单了, 因为 NFS 存储并没有一个设备需要挂载到宿主机上面,所以这个时候 kubelet 就会直接进入第二个 Mount
阶段,相当于直接在宿主机上面执行如下的命令:
$ mount -t nfs 10.151.30.11:/data/k8s /var/lib/kubelet/pods/d4fcdb11-baf7-43d9-8d7d-3ede24118e08/volumes/kubernetes.io~nfs/nfs-pv
同样可以在测试的 Pod 所在节点查看 Volume 的挂载信息:
$ findmnt /var/lib/kubelet/pods/d4fcdb11-baf7-43d9-8d7d-3ede24118e08/volumes/kubernetes.io~nfs/nfs-pv
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/d4fcdb11-baf7-43d9-8d7d-3ede24118e08/volumes/kubernetes.io~nfs/nfs-pv
10.151.30.11:/data/k8s nfs4 rw,relatime,
我们可以看到这个 Volume 被挂载到了 NFS(10.151.30.11:/data/k8s)下面,以后我们在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。
这样在经过了上面的两个阶段过后,我们就得到了一个持久化的宿主机上面的 Volume 目录了,接下来 kubelet 只需要把这个 Volume 目录挂载到容器中对应的目录即可,这样就可以为 Pod 里的容器挂载这个持久化的 Volume 了,这一步其实也就相当于执行了如下所示的命令:
$ docker run -v /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>:/<容器内的目标目录> 我的镜像 ...
整个存储的架构可以用下图来说明:
PV Controller:负责 PV/PVC 的绑定,并根据需求进行数据卷的 Provision/Delete 操作
AD Controller:负责存储设备的 Attach/Detach 操作,将设备挂载到目标节点
Volume Manager:管理卷的 Mount/Unmount 操作、卷设备的格式化等操作
Volume Plugin:扩展各种存储类型的卷管理能力,实现第三方存储的各种操作能力和 Kubernetes 存储系统结合
我们上面使用的 NFS 就属于 In-Tree 这种方式,而上节课使用的 Ceph RBD 就是 Out-Of-Tree 的方式,而且是使用的是 CSI 插件。下面我们再来了解下 FlexVolume
和 CSI
两种插件方式。
二、存储插件FlexVolume
部署脚本假设所需要的驱动二进制文件,且已经被至于部署镜像的/$DRIVER目录下:启动脚本将二进制driver重名名为.driver,再拷贝到<plugindir>/<vendor~driver>/.driver目录下,接着使用mv将其重命名为driver(确保驱动安装的原子性),最后进入死循环确保容器活着。
我们可以查看到 Pod 的本地持久化目录是被 mount 到了 NFS 上面,证明上面我们的 FlexVolume 插件是正常的。
“
当我们要去真正的 mount NFS 的时候,就是通过 kubelet 调用 VolumePlugin,然后直接执行命令
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs mount <mount dir> <json param>
来完成的,就相当于平时我们在宿主机上面手动挂载 NFS 的方式一样的,所以存储插件 nfs 是一个可执行的二进制文件或者 shell 脚本都是可以的。”
三、容器存储接口 CSI
既然已经有了 FlexVolume 插件了,为什么还需要 CSI 插件呢?上面我们使用 FlexVolume 插件的时候可以看出 FlexVolume 插件实际上相当于就是一个普通的 shell 命令,类似于平时我们在 Linux 下面执行的 ls
命令一样,只是返回的信息是 JSON 格式的数据,并不是我们通常认为的一个常驻内存的进程,而 CSI 是一个更加完善、编码更加方便友好的一种存储插件扩展方式。
CSI 是由来自 Kubernetes、Mesos、 Cloud Foundry 等社区成员联合制定的一个行业标准接口规范,旨在将任意存储系统暴露给容器化应用程序。CSI 规范定义了存储提供商实现 CSI 兼容插件的最小操作集合和部署建议,CSI 规范的主要焦点是声明插件必须实现的接口。
在 Kubernetes 上整合 CSI 插件的整体架构如下图所示:
Kubernetes CSI 存储体系主要由两部分组成:
Kubernetes 外部组件:包含 Driver registrar、External provisioner、External attacher 三部分,这三个组件是从 Kubernetes 原本的 in-tree 存储体系中剥离出来的存储管理功能,实际上是 Kubernetes 中的一种外部 controller ,它们 watch kubernetes 的 API 资源对象,根据 watch 到的状态来调用下面提到的第二部分的 CSI 插件来实现存储的管理和操作。这部分是 Kubernetes 团队维护的,插件开发者完全不必关心其实现细节。
Driver registra:用于将插件注册到 kubelet 的 sidecar 容器,并将驱动程序自定义的 NodeId 添加到节点的 Annotations 上,通过与 CSI 上面的 Identity 服务进行通信调用 CSI 的 GetNodeId 方法来完成该操作。
External provisioner:用于 watch Kubernetes 的 PVC 对象并调用 CSI 的 CreateVolume 和 DeleteVolume 操作。
External attacher:用于 Attach/Detach 阶段,通过 watch Kubernetes 的 VolumeAttachment 对象并调用 CSI 的 ControllerPublish 和 ControllerUnpublish 操作来完成对应的 Volume 的 Attach/Detach。而 Volume 的 Mount/Unmount 阶段并不属于外部组件,当真正需要执行 Mount 操作的时候,kubelet 会去直接调用下面的 CSI Node 服务来完成 Volume 的 Mount/UnMount 操作。
CSI 存储插件: 这部分正是开发者需要实现的 CSI 插件部分,都是通过 gRPC 实现的服务,一般会用一个二进制文件对外提供服务,主要包含三部分:CSI Identity、CSI Controller、CSI Node。
CSI Identity — 主要用于负责对外暴露这个插件本身的信息,确保插件的健康状态。
service Identity {
// 返回插件的名称和版本
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
// 返回这个插件的包含的功能,比如非块存储类型的 CSI 插件不需要实现 Attach 功能,GetPluginCapabilities 就可以在返回中标注这个 CSI 插件不包含 Attach 功能
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
// 插件插件是否正在运行
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}CSI Controller - 主要实现 Volume 管理流程当中的 Provision 和 Attach 阶段,Provision 阶段是指创建和删除 Volume 的流程,而 Attach 阶段是指把存储卷附着在某个节点或脱离某个节点的流程,另外只有块存储类型的 CSI 插件才需要 Attach 功能。
CSI Node — 负责控制 Kubernetes 节点上的 Volume 操作。
service Node {
// 在节点上初始化存储卷(格式化),并执行挂载到Global目录
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
// umount 存储卷在节点上的 Global 目录
rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}
// 在节点上将存储卷的 Global 目录挂载到 Pod 的实际挂载目录
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
// unmount 存储卷在节点上的 Pod 挂载目录
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}
// 获取节点上Volume挂载文件系统统计信息(总空间、可用空间等)
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {}
// 获取节点的唯一 ID
rpc NodeGetId (NodeGetIdRequest)
returns (NodeGetIdResponse) {
option deprecated = true;
}
// 返回节点插件的能力
rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {}
// 获取节点的一些信息
rpc NodeGetInfo (NodeGetInfoRequest)
returns (NodeGetInfoResponse) {}
}
只需要实现上面的接口就可以实现一个 CSI 插件了。虽然 Kubernetes 并未规定 CSI 插件的打包安装,但是提供了以下建议来简化我们在 Kubernetes 上容器化 CSI Volume 驱动程序的部署方案,具体的方案介绍可以查看 CSI 规范介绍文档 https://github.com/kubernetes/community
container storage interface deploy
按照上图的推荐方案,CSI Controller 部分以 StatefulSet 或者 Deployment 方式部署,CSI Node 部分以 DaemonSet 方式部署。因为这两部分实现在同一个 CSI 插件程序中,因此只需要把这个 CSI 插件与 External Components 以容器方式部署在同一个 Pod中,把这个 CSI 插件与 Driver registrar 以容器方式部署在 DaemonSet 的 Pod 中,即可完成 CSI 的部署。
前面我们使用的 Rook 部署的 Ceph 集群就是实现了 CSI 插件的:
$ kubectl get pods -n rook-ceph |grep plugin
csi-cephfsplugin-2s9d5 3/3 Running 0 21d
csi-cephfsplugin-fgp4v 3/3 Running 0 17d
csi-cephfsplugin-fv5nx 3/3 Running 0 21d
csi-cephfsplugin-mn8q4 3/3 Running 0 17d
csi-cephfsplugin-nf6h8 3/3 Running 0 21d
csi-cephfsplugin-provisioner-56c8b7ddf4-68h6d 4/4 Running 0 21d
csi-cephfsplugin-provisioner-56c8b7ddf4-rq4t6 4/4 Running 0 21d
csi-cephfsplugin-xwnl4 3/3 Running 0 21d
csi-rbdplugin-7r88w 3/3 Running 0 21d
csi-rbdplugin-95g5j 3/3 Running 0 21d
csi-rbdplugin-bnzpr 3/3 Running 0 21d
csi-rbdplugin-dvftb 3/3 Running 0 21d
csi-rbdplugin-jzmj2 3/3 Running 0 17d
csi-rbdplugin-provisioner-6ff4dd4b94-bvtss 5/5 Running 0 21d
csi-rbdplugin-provisioner-6ff4dd4b94-lfn68 5/5 Running 0 21d
csi-rbdplugin-trxb4 3/3 Running 0 17d
这里其实是实现了 RBD 和 CephFS 两种 CSI,用 DaemonSet 在每个节点上运行了一个包含 Driver registra
容器的 Pod,当然和节点相关的操作比如 Mount/Unmount 也是在这个 Pod 里面执行的,其他的比如 Provision、Attach 都是在另外的 csi-rbdplugin-provisioner-xxx
Pod 中执行的。