存储插件
存储插件
目录
[TOC]
实验环境
实验环境:1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,1个master节点,2个node节点k8sversion:v1.22.2containerd:"status":"<Success/Failure/Not supported>","message":"<Reason for success/failure>","device":"<Path to the device attached. This field is valid only for attach &waitforattach call-outs>""volumeName":"<Cluster wide unique name of the volume. Valid only for getvolumename call-out>""attached":<True/False(Returntrueifvolumeisattachedonthenode.Validonlyforisattachedcall-out)>"capabilities":<OnlyincludedaspartoftheInitresponse>{"attach":<True/False(Returntrueifthedriverimplementsattachanddetach)>}}
比如我们来实现一个 NFS 的 FlexVolume 插件,最简单的方式就是写一个脚本,然后实现 init、mount、unmount 3个命令即可,然后按照上面的 JSON 格式返回数据,最后把这个脚本放在节点的 FlexVolume 插件目录下面即可。
📍 案例演示:FlexVolume测试(测试成功)
将上面脚本命名成 nfs,放置到 node1 节点对应的插件下面: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs
,并设置权限为 700:
[root@node1 ~]#mkdir -p /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs[root@node1 ~]#vim /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs#将上面脚本写进去[root@node1 ~]#chmod 700 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs# 安装 jq 工具[root@node1 ~]#yum -y install https:[root@node1 ~]#yum install jq -y
🍀 这个时候我们部署一个应用到 node1 节点上,并用 flexVolume
来持久化容器中的数据(当然也可以通过定义 flexvolume 类型的 PV、PVC 来使用),如下所示:
# 01-test-flexvolume.yamlapiVersion:v1kind:Podmetadata:name:test-flexvolumespec:nodeSelector:kubernetes.io/hostname:node1volumes:- name:testflexVolume:driver:"ydzs/nfs"# 定义插件类型,根据这个参数在对应的目录下面找到插件的可执行文件fsType:"nfs"# 定义存储卷文件系统类型options:# 定义所有与存储相关的一些具体参数server:"172.29.9.51"share:"var/lib/k8s/data/"#注意这里:不需要加斜杠的,因为之前shell脚本那里加了。containers:- name:webimage:nginxports:- containerPort:80volumeMounts:- name:testsubPath:testflexvolumemountPath:/usr/share/nginx/html
其中 flexVolume.driver
就是插件目录 ydzs~nfs
对应的 ydzs/nfs
名称,flexVolume.options
中根据上面的 nfs 脚本可以得知里面配置的是 NFS 的 Server 地址和挂载目录路径。
🍀 直接创建上面的资源对象:
$kubectlapply-f01-test-flexvolume.yamlpod/test-flexvolumecreated[root@master1 ~]#kubectl get po -owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATEStest-flexvolume1/1Running029s10.244.1.9node1<none><none>[root@master1 ~]#kubectl exec -it test-flexvolume mount |greptestkubectlexec[POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.172.29.9.51:/var/lib/k8s/data/testflexvolumeon/usr/share/nginx/htmltypenfs4(rw,relatime,vers=4.1,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=172.29.9.52,local_lock=none,addr=172.29.9.51)
同样我们可以查看到 Pod 的本地持久化目录是被 mount 到了 NFS 上面,证明上面我们的 FlexVolume 插件是正常的。
🍀 测试
[root@master1 ~]#echo "test flexvolume storage">/var/lib/k8s/data/testflexvolume/index.html[root@master1 ~]#curl 10.244.1.9testflexvolumestorage
调用
当我们要去真正的 mount NFS 的时候,就是通过 kubelet 调用
VolumePlugin
,然后直接执行命令/usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs mount <mount dir><json param>
来完成的,就相当于平时我们在宿主机上面手动挂载 NFS 的方式一样的,所以存储插件 nfs 是一个可执行的二进制文件或者 shell 脚本都是可以的。
实验结束,完美。😘
3、CSI
怎么感觉理解起来还是有一定难度的:
既然已经有了 FlexVolume
插件了,为什么还需要 CSI
插件呢?上面我们使用 FlexVolume 插件的时候可以看出 FlexVolume 插件实际上相当于就是一个普通的 shell 命令,类似于平时我们在 Linux 下面执行的 ls
命令一样,只是返回的信息是 JSON 格式的数据,并不是我们通常认为的一个常驻内存的进程,而 CSI 是一个更加完善、编码更加方便友好的一种存储插件扩展方式。
CSI是 Container Storage Interface
的简称,旨在能为容器编排引擎和存储系统间建立一套标准的存储调用接口,通过该接口能为容器编排引擎提供存储服务。在 CSI 之前,K8S 里提供存储服务基本上是通过 in-tree
的方式来提供,如下图:
这种方式需要将存储提供者的代码逻辑放到 K8S 的代码库中运行,调用引擎与插件间属于强耦合,这种方式会带来一些问题:
- 存储插件需要一同随K8S发布
- K8S社区需要对存储插件的测试、维护负责
- 存储插件的问题有可能会影响K8S部件正常运行
- 存储插件享有K8S部件同等的特权存在安全隐患
- 存储插件开发者必须遵循K8S社区的规则开发代码
基于这些问题和挑战,CO(Container Orchestrator) 厂商提出 Container Storage Interface 用来定义容器存储标准,它独立于 Kubernetes Storage SIG,由 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
:一个 Sidecar 容器,向 Kubernetes 注册 CSI Driver,添加 Drivers 的一些信息External provisioner
:也是一个 Sidecar 容器,watch Kubernetes 的 PVC对象,调用对应 CSI 的 Volume创建、删除等操作External attacher
:一个 Sidecar 容器,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) {}rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)returns (GetPluginCapabilitiesResponse) {}rpc Probe (ProbeRequest)returns (ProbeResponse) {}}
CSI Controller
- 主要实现 Volume 管理流程当中的 Provision 和 Attach 阶段,Provision 阶段是指创建和删除 Volume 的流程,而 Attach 阶段是指把存储卷附着在某个节点或脱离某个节点的流程,另外只有块存储类型的 CSI 插件才需要 Attach 功能。
service Controller {rpc CreateVolume (CreateVolumeRequest)returns (CreateVolumeResponse) {}rpc DeleteVolume (DeleteVolumeRequest)returns (DeleteVolumeResponse) {}rpc ControllerPublishVolume (ControllerPublishVolumeRequest)returns (ControllerPublishVolumeResponse) {}rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)returns (ControllerUnpublishVolumeResponse) {}rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)returns (ValidateVolumeCapabilitiesResponse) {}rpc ListVolumes (ListVolumesRequest)returns (ListVolumesResponse) {}rpc GetCapacity (GetCapacityRequest)returns (GetCapacityResponse) {}rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)returns (ControllerGetCapabilitiesResponse) {}rpc CreateSnapshot (CreateSnapshotRequest)returns (CreateSnapshotResponse) {}rpc DeleteSnapshot (DeleteSnapshotRequest)returns (DeleteSnapshotResponse) {}rpc ListSnapshots (ListSnapshotsRequest)returns (ListSnapshotsResponse) {}}
CSI Node
— 负责控制 Kubernetes 节点上的 Volume 的相关功能。其中 Volume 的挂载被分成了 NodeStageVolume 和 NodePublishVolume 两个阶段。NodeStageVolume 接口主要是针对块存储类型的 CSI 插件而提供的,块设备在 "Attach"阶段被附着在 Node 上后,需要挂载至 Pod 对应目录上,但因为块设备在 linux 上只能 mount 一次,而在 kubernetes volume 的使用场景中,一个 volume 可能被挂载进同一个 Node 上的多个 Pod 实例中,所以这里提供了 NodeStageVolume 这个接口,使用这个接口把块设备格式化后先挂载至 Node 上的一个临时全局目录,然后再调用 NodePublishVolume 使用 linux 中的bind mount
技术把这个全局目录挂载进 Pod 中对应的目录上。
service Node {rpc NodeStageVolume (NodeStageVolumeRequest)returns (NodeStageVolumeResponse) {}rpc NodeUnstageVolume (NodeUnstageVolumeRequest)returns (NodeUnstageVolumeResponse) {}rpc NodePublishVolume (NodePublishVolumeRequest)returns (NodePublishVolumeResponse) {}rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)returns (NodeUnpublishVolumeResponse) {}rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)returns (NodeGetVolumeStatsResponse) {}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:csi-cephfsplugin-2s9d53/3Running021dcsi-cephfsplugin-fgp4v3/3Running017dcsi-cephfsplugin-fv5nx3/3Running021dcsi-cephfsplugin-mn8q43/3Running017dcsi-cephfsplugin-nf6h83/3Running021dcsi-cephfsplugin-provisioner-56c8b7ddf4-68h6d4/4Running021dcsi-cephfsplugin-provisioner-56c8b7ddf4-rq4t64/4Running021dcsi-cephfsplugin-xwnl43/3Running021dcsi-rbdplugin-7r88w3/3Running021dcsi-rbdplugin-95g5j3/3Running021dcsi-rbdplugin-bnzpr3/3Running021dcsi-rbdplugin-dvftb3/3Running021dcsi-rbdplugin-jzmj23/3Running017dcsi-rbdplugin-provisioner-6ff4dd4b94-bvtss5/5Running021dcsi-rbdplugin-provisioner-6ff4dd4b94-lfn685/5Running021dcsi-rbdplugin-trxb43/3Running017d
这里其实是实现了 RBD 和 CephFS两种 CSI,用 DaemonSet 在每个节点上运行了一个包含 Driver registra
容器的 Pod,当然和节点相关的操作比如 Mount/Unmount 也是在这个 Pod 里面执行的,其他的比如 Provision、Attach 都是在另外的 csi-rbdplugin-provisioner-xxx
Pod 中执行的。
4、测试
现在我们来测试通过 CSI 的形式使用 NFS 存储,当然首先我们需要在 Kubernetes 集群中安装 NFS CSI 的驱动,https:➜cdcsi-driver-nfs#注意:这里要把deploy下面的2个yaml文件里的镜像仓库修改为老师的仓库地址;➜/yaml/2022.2.22-45.存储插件-实验代码/csi-driver-nfs-edited$ ./deploy/install-driver.shmasterlocaluselocaldeployInstallingNFSCSIdriver,version:master...serviceaccount/csi-nfs-controller-sacreatedclusterrole.rbac.authorization.k8s.io/nfs-external-provisioner-rolecreatedclusterrolebinding.rbac.authorization.k8s.io/nfs-csi-provisioner-bindingcreatedcsidriver.storage.k8s.io/nfs.csi.k8s.iocreateddeployment.apps/csi-nfs-controllercreateddaemonset.apps/csi-nfs-nodecreatedNFSCSIdriverinstalledsuccessfully.