一 CSI存储机制
1.1 CSI简介
Kubernetes从1.9版本开始引入容器存储接口Container Storage Interface(CSI)机制,用于在Kubernetes和外部存储系统之间建立一套标准的存储管理接口,通过该接口为容器提供存储服务。
1.2 CSI的设计背景
Kubernetes通过PV、PVC、Storageclass已经提供了一种强大的基于插件的存储管理机制,但是各种存储插件提供的存储服务都是基于一种被称为“in-true”(树内)的方式提供的,这要求存储插件的代码必须被放进Kubernetes的主干代码库中才能被Kubernetes调用,属于紧耦合的开发模式。这种“in-tree”方式会带来一些问题:
- 存储插件的代码需要与Kubernetes的代码放在同一代码库中,并与Kubernetes的二进制文件共同发布;
- 存储插件代码的开发者必须遵循Kubernetes的代码开发规范;
- 存储插件代码的开发者必须遵循Kubernetes的发布流程,包括添加对Kubernetes存储系统的支持和错误修复;
- Kubernetes社区需要对存储插件的代码进行维护,包括审核、测试等工作;
- 存储插件代码中的问题可能会影响Kubernetes组件的运行,并且很难排查问题;
- 存储插件代码与Kubernetes的核心组件(kubelet和kubecontroller-manager)享有相同的系统特权权限,可能存在可靠性和安全性问题。
Kubernetes已有的FlexVolume插件机制试图通过为外部存储暴露一个基于可执行程序(exec)的API来解决这些问题。尽管它允许第三方存储提供商在Kubernetes核心代码之外开发存储驱动,但仍然有两个问题没有得到很好的解决:
- 部署第三方驱动的可执行文件仍然需要宿主机的root权限,存在安全隐患;
- 存储插件在执行mount、attach这些操作时,通常需要在宿主机上安装一些第三方工具包和依赖库,使得部署过程更加复杂,例如部署Ceph时需要安装rbd库,部署GlusterFS时需要安装mount.glusterfs库,等等。
基于以上这些问题和考虑,Kubernetes逐步推出与容器对接的存储接口标准,存储提供方只需要基于标准接口进行存储插件的实现,就能使用Kubernetes的原生存储机制为容器提供存储服务。这套标准被称为CSI(容器存储接口)。
在CSI成为Kubernetes的存储供应标准之后,存储提供方的代码就能和Kubernetes代码彻底解耦,部署也与Kubernetes核心组件分离,显然,存储插件的开发由提供方自行维护,就能为Kubernetes用户提供更多的存储功能,也更加安全可靠。
基于CSI的存储插件机制也被称为“out-of-tree”(树外)的服务提供方式,是未来Kubernetes第三方存储插件的标准方案。
二 CSI架构
2.1 CSI存储组件/部署架构
KubernetesCSI存储插件的关键组件和推荐的容器化部署架构:
其中主要包括两种组件:CSI Controller和CSI Node。
2.2 CSI Controller
CSI Controller的主要功能是提供存储服务视角对存储资源和存储卷进行管理和操作。在Kubernetes中建议将其部署为单实例Pod,可以使用StatefulSet或Deployment控制器进行部署,设置副本数量为1,保证为一种存储插件只运行一个控制器实例。
在这个Pod内部署两个容器:
- 与Master(kube-controller-manager)通信的辅助sidecar容器。在sidecar容器内又可以包含external-attacher和external-provisioner两个容器,它们的功能分别如下。
- external-attacher:监控VolumeAttachment资源对象的变更,触发针对CSI端点的ControllerPublish和ControllerUnpublish操作。
- external-provisioner:监控PersistentVolumeClaim资源对象的变更,触发针对CSI端点的CreateVolume和DeleteVolume操作。
- CSI Driver存储驱动容器,由第三方存储提供商提供,需要实现上述接口。
这两个容器通过本地Socket(Unix DomainSocket,UDS),并使用gPRC协议进行通信。
sidecar容器通过Socket调用CSI Driver容器的CSI接口,CSI Driver容器负责具体的存储卷操作。
2.3 CSI Node
CSI Node的主要功能是对主机(Node)上的Volume进行管理和操作。在Kubernetes中建议将其部署为DaemonSet,在每个Node上都运行一个Pod。
在这个Pod中部署以下两个容器:
- 与kubelet通信的辅助sidecar容器node-driver-registrar,主要功能是将存储驱动注册到kubelet中;
- CSI Driver存储驱动容器,由第三方存储提供商提供,主要功能是接收kubelet的调用,需要实现一系列与Node相关的CSI接口,例如NodePublishVolume接口(用于将Volume挂载到容器内的目标路径)、NodeUnpublishVolume接口(用于从容器中卸载Volume),等等。
node-driver-registrar容器与kubelet通过Node主机的一个hostPath目录下的unixsocket进行通信。CSI Driver容器与kubelet通过Node主机的另一个hostPath目录下的unixsocket进行通信,同时需要将kubelet的工作目录(默认为/var/lib/kubelet)挂载给CSIDriver容器,用于为Pod进行Volume的管理操作(包括mount、umount等)。
三 CSI插件使用实践
3.1 实验说明
以csi-hostpath插件为例,演示部署CSI插件、用户使用CSI插件提供的存储资源。
3.2 开启特性
设置Kubernetes服务启动参数,为kube-apiserver、kubecontroller-manager和kubelet服务的启动参数添加。
[root@k8smaster01 ~]# vi /etc/kubernetes/manifests/kube-apiserver.yaml
…… - --allow-privileged=true - --feature-gates=CSIPersistentVolume=true - --runtime-config=storage.k8s.io/v1alpha1=true ……
[root@k8smaster01 ~]# vi /etc/kubernetes/manifests/kube-controller-manager.yaml
…… - --feature-gates=CSIPersistentVolume=true ……
[root@k8smaster01 ~]# vi /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
# Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --feature-gates=CSIPersistentVolume=true" ……
[root@k8smaster01 ~]# systemctl daemon-reload
[root@k8smaster01 ~]# systemctl restart kubelet.service
3.3 创建CRD资源对象
创建CSINodeInfo和CSIDriverRegistry CRD资源对象:
[root@k8smaster01 ~]# vi csidriver.yaml
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: csidrivers.csi.storage.k8s.io labels: addonmanager.kubernetes.io/mode: Reconcile spec: group: csi.storage.k8s.io names: kind: CSIDriver plural: csidrivers scope: Cluster validation: openAPIV3Schema: properties: spec: description: Specification of the CSI Driver. properties: attachRequired: description: Indicates this CSI volume driver requires an attach operation,and that Kubernetes should call attach and wait for any attach operationto complete before proceeding to mount. type: boolean podInfoOnMountVersion: description: Indicates this CSI volume driver requires additional pod information (like podName, podUID, etc.) during mount operations. type: string version: v1alpha1
[root@k8smaster01 ~]# vi csinodeinfo.yaml
apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: csinodeinfos.csi.storage.k8s.io labels: addonmanager.kubernetes.io/mode: Reconcile spec: group: csi.storage.k8s.io names: kind: CSINodeInfo plural: csinodeinfos scope: Cluster validation: openAPIV3Schema: properties: spec: description: Specification of CSINodeInfo properties: drivers: description: List of CSI drivers running on the node and their specs. type: array items: properties: name: description: The CSI driver that this object refers to. type: string nodeID: description: The node from the driver point of view. type: string topologyKeys: description: List of keys supported by the driver. items: type: string type: array status: description: Status of CSINodeInfo properties: drivers: description: List of CSI drivers running on the node and their statuses. type: array items: properties: name: description: The CSI driver that this object refers to. type: string available: description: Whether the CSI driver is installed. type: boolean volumePluginMechanism: description: Indicates to external components the required mechanism to use for any in-tree plugins replaced by this driver. pattern: in-tree|csi type: string version: v1alpha1
[root@k8smaster01 ~]# kubectl apply -f csidriver.yaml
[root@k8smaster01 ~]# kubectl apply -f csinodeinfo.yaml
3.4 创建相应RBAC
[root@k8smaster01 ~]# git clone https://github.com/kubernetes-csi/drivers
[root@k8smaster01 ~]# cd drivers/deploy/hostpath/
[root@k8smaster01 hostpath]# vi csi-hostpath-attacher-rbac.yaml
--- apiVersion: v1 kind: ServiceAccount metadata: name: csi-attacher # replace with non-default namespace name namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: external-attacher-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "update"] - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch"] - apiGroups: ["csi.storage.k8s.io"] resources: ["csinodeinfos"] verbs: ["get", "list", "watch"] - apiGroups: ["storage.k8s.io"] resources: ["volumeattachments"] verbs: ["get", "list", "watch", "update"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-attacher-role subjects: - kind: ServiceAccount name: csi-attacher # replace with non-default namespace name namespace: default roleRef: kind: ClusterRole name: external-attacher-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: # replace with non-default namespace name namespace: default name: external-attacher-cfg rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "watch", "list", "delete", "update", "create"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-attacher-role-cfg # replace with non-default namespace name namespace: default subjects: - kind: ServiceAccount name: csi-attacher # replace with non-default namespace name namespace: default roleRef: kind: Role name: external-attacher-cfg apiGroup: rbac.authorization.k8s.io
[root@k8smaster01 hostpath]# vi csi-hostpath-provisioner-rbac.yaml
--- apiVersion: v1 kind: ServiceAccount metadata: name: csi-provisioner # replace with non-default namespace name namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: external-provisioner-runner rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list"] - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["list", "watch", "create", "update", "patch"] - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshots"] verbs: ["get", "list"] - apiGroups: ["snapshot.storage.k8s.io"] resources: ["volumesnapshotcontents"] verbs: ["get", "list"] - apiGroups: ["csi.storage.k8s.io"] resources: ["csinodeinfos"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-provisioner-role subjects: - kind: ServiceAccount name: csi-provisioner # replace with non-default namespace name namespace: default roleRef: kind: ClusterRole name: external-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: # replace with non-default namespace name namespace: default name: external-provisioner-cfg rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "watch", "list", "delete", "update", "create"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-provisioner-role-cfg # replace with non-default namespace name namespace: default subjects: - kind: ServiceAccount name: csi-provisioner # replace with non-default namespace name namespace: default roleRef: kind: Role name: external-provisioner-cfg apiGroup: rbac.authorization.k8s.io
[root@k8smaster01 hostpath]# vi csi-hostpathplugin-rbac.yaml
--- apiVersion: v1 kind: ServiceAccount metadata: name: csi-driver-registrar # replace with non-default namespace name namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: driver-registrar-runner rules: - apiGroups: [""] resources: ["events"] verbs: ["get", "list", "watch", "create", "update", "patch"] # The following permissions are only needed when running # driver-registrar without the --kubelet-registration-path # parameter, i.e. when using driver-registrar instead of # kubelet to update the csi.volume.kubernetes.io/nodeid # annotation. That mode of operation is going to be deprecated # and should not be used anymore, but is needed on older # Kubernetes versions. # - apiGroups: [""] # resources: ["nodes"] # verbs: ["get", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-driver-registrar-role subjects: - kind: ServiceAccount name: csi-driver-registrar # replace with non-default namespace name namespace: default roleRef: kind: ClusterRole name: driver-registrar-runner apiGroup: rbac.authorization.k8s.io
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpath-attacher-rbac.yaml
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpath-provisioner-rbac.yaml
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpathplugin-rbac.yaml
3.5 正式部署
[root@k8smaster01 ~]# cd drivers/deploy/hostpath/
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpath-attacher.yaml
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpath-provisioner.yaml
[root@k8smaster01 hostpath]# kubectl create -f csi-hostpathplugin.yaml
提示:如上相应yaml建议修改镜像源为国内:
gcr.io ----> gcr.azk8s.cn (国内)
quay.io ----> quay.azk8s.cn (国内)
四 测试使用
4.1 确认验证
[root@k8smaster01 ~]# kubectl get pods
4.2 创建StorageClass
[root@k8smaster01 ~]# vi drivers/examples/hostpath/csi-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-hostpath-sc
provisioner: csi-hostpath
reclaimPolicy: Delete
volumeBindingMode: Immediate
[root@k8smaster01 ~]# kubectl create -f drivers/examples/hostpath/csi-storageclass.yaml
4.3 创建PVC
[root@k8smaster01 ~]# vi drivers/examples/hostpath/csi-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: csi-hostpath-sc
[root@k8smaster01 ~]# kubectl create -f drivers/examples/hostpath/csi-pvc.yaml
[root@k8smaster01 ~]# kubectl get pvc
[root@k8smaster01 ~]# kubectl get pv
4.4 创建应用
[root@k8smaster01 ~]# vi drivers/examples/hostpath/csi-app.yaml
kind: Pod apiVersion: v1 metadata: name: my-csi-app spec: containers: - name: my-frontend image: busybox volumeMounts: - mountPath: "/data" name: my-csi-volume command: [ "sleep", "1000000" ] volumes: - name: my-csi-volume persistentVolumeClaim: claimName: csi-pvc
[root@k8smaster01 ~]# kubectl create -f drivers/examples/hostpath/csi-app.yaml
[root@k8smaster01 ~]# kubectl get pods
提示:更多CSI插件示例参考:https://feisky.gitbooks.io/kubernetes/plugins/csi.html。
CSI官方文档:https://kubernetes-csi.github.io/docs/