动态存储管理实战: GlusterFS
本节以GlusterFS为例,从定义StorageClass、创建GlusterFS和Heketi服务、用户申请PVC到创建Pod使用存储资源,对StorageClass和动态资源分配进行详细说明,进一步剖析Kubernetes的存储机制。(ps: 第26篇有详细的部署过程,此处不上传操作图片了)
准备工作
为了能够使用GlusterFS,首先在计划用于GlusterFS的各Node上安装GlusterFS客户端:
1
2 1yum install -y glusterfs glusterfs-fuse
2
GlusterFS管理服务容器需要以特权模式运行,在kube-apiserver的启动参数中增加:
1
2 1--allow-privileged=true
2
给要部署GlusterFS管理服务的节点打上“storagenode=glusterfs”的标签,是为了将GlusterFS容器定向部署到安装了GlusterFS的Node上:
1
2
3
4 1kubectl label node k8s-node-1 storagenode=glusterfs
2kubectl label node k8s-node-2 storagenode=glusterfs
3kubectl label node k8s-node-3 storagenode=glusterfs
4
创建GlusterFS管理服务容器集群
GlusterFS管理服务容器以DaemonSet的方式进行部署,确保在每个Node上都运行一个GlusterFS管理服务。glusterfs-daemonset.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 1kind: DaemonSet
2apiVersion: extensions/v1beta1
3metadata:
4 name: glusterfs
5 labels:
6 glusterfs: daemonset
7 annotations:
8 description: GlusterFS DaemonSet
9 tags: glusterfs
10spec:
11 template:
12 metadata:
13 name: glusterfs
14 labels:
15 glusterfs: pod
16 glusterfs-node: pod
17 spec:
18 nodeSelector:
19 storagenode: glusterfs
20 hostNetwork: true
21 containers:
22 - image: gluster/gluster-centos:latest
23 name: glusterfs
24 imagePullPolicy: IfNotPresent
25 volumeMounts:
26 - name: glusterfs-heketi
27 mountPath: "/var/lib/heketi"
28 - name: glusterfs-run
29 mountPath: "/run"
30 - name: glusterfs-lvm
31 mountPath: "/run/lvm"
32 - name: glusterfs-etc
33 mountPath: "/etc/glusterfs"
34 - name: glusterfs-logs
35 mountPath: "/var/log/glusterfs"
36 - name: glusterfs-config
37 mountPath: "/var/lib/glusterd"
38 - name: glusterfs-dev
39 mountPath: "/dev"
40 - name: glusterfs-misc
41 mountPath: "/var/lib/misc/glusterfsd"
42 - name: glusterfs-cgroup
43 mountPath: "/sys/fs/cgroup"
44 readOnly: true
45 - name: glusterfs-ssl
46 mountPath: "/etc/ssl"
47 readOnly: true
48 securityContext:
49 capabilities: {}
50 privileged: true
51 readinessProbe:
52 timeoutSeconds: 3
53 initialDelaySeconds: 60
54 exec:
55 command:
56 - "/bin/bash"
57 - "-c"
58 - systemctl status glusterd.service
59 livenessProbe:
60 timeoutSeconds: 3
61 initialDelaySeconds: 60
62 exec:
63 command:
64 - "/bin/bash"
65 - "-c"
66 - systemctl status glusterd.service
67 volumes:
68 - name: glusterfs-heketi
69 hostPath:
70 path: "/var/lib/heketi"
71 - name: glusterfs-run
72 - name: glusterfs-lvm
73 hostPath:
74 path: "/run/lvm"
75 - name: glusterfs-etc
76 hostPath:
77 path: "/etc/glusterfs"
78 - name: glusterfs-logs
79 hostPath:
80 path: "/var/log/glusterfs"
81 - name: glusterfs-config
82 hostPath:
83 path: "/var/lib/glusterd"
84 - name: glusterfs-dev
85 hostPath:
86 path: "/dev"
87 - name: glusterfs-misc
88 hostPath:
89 path: "/var/lib/misc/glusterfsd"
90 - name: glusterfs-cgroup
91 hostPath:
92 path: "/sys/fs/cgroup"
93 - name: glusterfs-ssl
94 hostPath:
95 path: "/etc/ssl"
96
1
2
3 1kubectl apply -f glusterfs-daemonset.yaml
2kubectl get pod
3
创建Heketi服务
Heketi 是一个提供RESTful API管理GlusterFS卷的框架,并能够在OpenStack、Kubernetes、OpenShift等云平台上实现动态存储资源供应,支持GlusterFS多集群管理,便于管理员对GlusterFS进行操作。
在部署Heketi服务之前,需要为它创建一个ServiceAccount对象:
1
2
3
4
5
6 1apiVersion: v1
2kind: ServiceAccount
3metadata:
4 name: heketi-service-account
5
6
1
2 1kubectl apply -f heketi-service-account.yaml
2
部署Heketi服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 1---
2kind: Service
3apiVersion: v1
4metadata:
5 name: deploy-heketi
6 labels:
7 glusterfs: heketi-service
8 deploy-heketi: support
9 annotations:
10 description: Exposes Heketi Service
11spec:
12 selector:
13 name: deploy-heketi
14 ports:
15 - name: deploy-heketi
16 port: 8080
17 targetPort: 8080
18---
19kind: Deployment
20apiVersion: extensions/v1beta1
21metadata:
22 name: deploy-heketi
23 labels:
24 glusterfs: heketi-deployment
25 deploy-heketi: heket-deployment
26 annotations:
27 description: Defines how to deploy Heketi
28spec:
29 replicas: 1
30 template:
31 metadata:
32 name: deploy-heketi
33 labels:
34 glusterfs: heketi-pod
35 name: deploy-heketi
36 spec:
37 serviceAccountName: heketi-service-account
38 containers:
39 - image: heketi/heketi
40 imagePullPolicy: IfNotPresent
41 name: deploy-heketi
42 env:
43 - name: HEKETI_EXECUTOR
44 value: kubernetes
45 - name: HEKETI_FSTAB
46 value: "/var/lib/heketi/fstab"
47 - name: HEKETI_SNAPSHOT_LIMIT
48 value: '14'
49 - name: HEKETI_KUBE_GLUSTER_DAEMONSET
50 value: "y"
51 ports:
52 - containerPort: 8080
53 volumeMounts:
54 - name: db
55 mountPath: "/var/lib/heketi"
56 readinessProbe:
57 timeoutSeconds: 3
58 initialDelaySeconds: 3
59 httpGet:
60 path: "/hello"
61 port: 8080
62 livenessProbe:
63 timeoutSeconds: 3
64 initialDelaySeconds: 30
65 httpGet:
66 path: "/hello"
67 port: 8080
68 volumes:
69 - name: db
70 hostPath:
71 path: "/heketi-data"
72
73
需要注意的是,Heketi的DB数据需要持久化保存,建议使用hostPath或其他共享存储进行保存:
1
2 1kubectl apply -f heketi-deployment-svc.yaml
2
为Heketi设置GlusterFS集群
在Heketi能够管理GlusterFS集群之前,首先要为其设置GlusterFS集群的信息。可以用一个topology.json配置文件来完成各个GlusterFS节点和设备的定义。Heketi要求在一个GlusterFS集群中至少有3个节点。在topology.json配置文件hostnames字段的manage上填写主机名,在storage上填写IP地址,devices要求为未创建文件系统的裸设备(可以有多块盘),以供Heketi自动完成PV(Physical Volume)、VG(Volume Group)和LV(Logical Volume)的创建。topology.json文件的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 1{
2 "clusters": [
3 {
4 "nodes": [
5 {
6 "node": {
7 "hostnames": {
8 "manage": [
9 "k8s-master2"
10 ],
11 "storage": [
12 "20.0.40.52"
13 ]
14 },
15 "zone": 1
16 },
17 "devices": [
18 "/dev/sdb1"
19 ]
20 },
21 {
22 "node": {
23 "hostnames": {
24 "manage": [
25 "k8s-master3"
26 ],
27 "storage": [
28 "20.0.40.53"
29 ]
30 },
31 "zone": 1
32 },
33 "devices": [
34 "/dev/sdb1"
35 ]
36 },
37 {
38 "node": {
39 "hostnames": {
40 "manage": [
41 "k8s-node3"
42 ],
43 "storage": [
44 "20.0.40.56"
45 ]
46 },
47 "zone": 1
48 },
49 "devices": [
50 "/dev/sdb1"
51 ]
52 }
53 ]
54 }
55 ]
56}
57
进入Heketi容器,使用命令行工具heketi-cli完成GlusterFS集群的创建(此处不在过多描述,详细参考第26篇)。
经过这个操作,Heketi完成了GlusterFS集群的创建,同时在GlusterFS集群的各个节点的/dev/sdb盘上成功创建了PV和VG。
查看Heketi的topology信息,可以看到Node和Device的详细信息,包括磁盘空间的大小和剩余空间。此时,Volume和Brick还未创建:
定义StorageClass
准备工作已经就绪,集群管理员现在可以在Kubernetes集群中定义一个StorageClass了。storageclass-gluster-heketi.yaml配置文件的内容如下:
1
2
3
4
5
6
7
8
9 1apiVersion: storage.k8s.ip/v1
2kind: StorageClass
3metadata:
4 name: gluster-heketi
5provisioner: kubernetes.io/glusterfs
6parameters:
7 resturl: "http://20.0.40.53:8080" #填写宿主机ip,不要填写clusterip
8 restauthenabled: "false"
9
Provisioner参数必须被设置为“kubernetes.io/glusterfs”。
resturl的地址需要被设置为API Server所在主机可以访问到的Heketi服务的某个地址,可以使用服务ClusterIP+端口号、容器IP地址+端口号,或将服务映射到物理机,使用物理机IP+NodePort。
创建这个StorageClass资源对象:
1
2 1kubectl apply -f storageclass-glusterfs-heketi.yaml
2
定义PVC
现在,用户可以申请一个PVC了。例如,一个用户申请一个1GiB空间的共享存储资源,StorageClass使用“gluster-heketi”,未定义任何Selector,说明使用动态资源供应模式:
1
2
3
4
5
6
7
8
9
10
11
12 1kind: PersistentVolumeClaim
2apiVersion: v1
3metadata:
4 name: pvc-gluster-heketi
5spec:
6 storageClassName: gluster-heketi
7 accessModes:
8 - ReadWriteOnce
9 resources:
10 requests:
11 storage: 1Gi
12
PVC的定义一旦生成,系统便将触发Heketi进行相应的操作,主要为在GlusterFS集群上创建brick,再创建并启动一个Volume。整个过程可以在Heketi的日志中查到:
查看PVC的状态,可见其已经为Bound(已绑定):kubectl get pvc
查看PV,可见系统自动创建的PV: kubecatl get pv
查看该PV的详细信息,可以看到其容量、引用的StorageClass等信息都已正确设置,状态也为Bound,回收策略则为默认的Delete。同时Gluster的Endpoint和Path也由Heketi自动完成了设置: kubectl describe pv <pv-pvname>
至此,一个可供Pod使用的PVC就创建成功了。接下来Pod就能通过Volume的设置将这个PVC挂载到容器内部进行使用了。
Pod使用PVC的存储资源
在Pod中使用PVC定义的存储资源非常容易,只需设置一个Volume,其类型为persistentVolumeClaim,即可轻松引用一个PVC。下例中使用一个busybox容器验证对PVC的使用,注意Pod需要与PVC属于同一个Namespace:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1apiVersion: v1
2kind: Pod
3metadata:
4 name: pod-use-pvc
5spec:
6 containers:
7 - name: pod-use-pvc
8 image: busybox
9 imagePullPolucy: IfNotPresent
10 command:
11 - sleep
12 - "3600"
13 volumeMounts:
14 - name: gluster-volume
15 mounthPath: "/pv-data"
16 readOnly: false
17 volumes:
18 - name: gluster-volume
19 persistentVolumeClain:
20 claimName: pvc-gluster-heketi
21
1
2 1kubectl apply -f pod-use-pvc.yaml
2
进入容器pod-use-pvc,在/pv-data目录下创建一些文件。可以验证文件a和b在GlusterFS集群中是否正确生成。
至此,使用Kubernetes最新的动态存储供应模式,配合StorageClass和Heketi共同搭建基于GlusterFS的共享存储就完成了。有兴趣的读者可以继续尝试StorageClass的其他设置,例如调整GlusterFS的Volume类型、修改PV的回收策略等。
在使用动态存储供应模式的情况下,相对于静态模式的优势至少包括如下两点。
(1)管理员无须预先创建大量的PV作为存储资源。
(2)用户在申请PVC时无法保证容量与预置PV的容量完全匹配。从Kubernetes 1.6版本开始,建议用户优先考虑使用StorageClass的动态存储供应模式进行存储管理
CSI存储机制详解
Kubernetes从1.9版本开始引入容器存储接口Container Storage Interface(CSI)机制,用于在Kubernetes和外部存储系统之间建立一套标准的存储管理接口,通过该接口为容器提供存储服务。CSI到Kubernetes 1.10版本升级为Beta版,到Kubernetes 1.13版本升级为GA版,已逐渐成熟。
CSI的设计背景
Kubernetes通过PV、PVC、Storageclass已经提供了一种强大的基于插件的存储管理机制,但是各种存储插件提供的存储服务都是基于一种被称为“in-true”(树内)的方式提供的,这要求存储插件的代码必须被放进Kubernetes的主干代码库中才能被Kubernetes调用,属于紧耦合的开发模式。这种“in-tree”方式会带来一些问题:
◎ 存储插件的代码需要与Kubernetes的代码放在同一代码库中,并与Kubernetes的二进制文件共同发布;
◎ 存储插件代码的开发者必须遵循Kubernetes的代码开发规范;
◎ 存储插件代码的开发者必须遵循Kubernetes的发布流程,包括添加对Kubernetes存储系统的支持和错误修复;
◎ Kubernetes社区需要对存储插件的代码进行维护,包括审核、测试等工作;
◎ 存储插件代码中的问题可能会影响Kubernetes组件的运行,并且很难排查问题;
◎ 存储插件代码与Kubernetes的核心组件(kubelet和kube-controller-manager)享有相同的系统特权权限,可能存在可靠性和安全性问题。
Kubernetes已有的Flex Volume插件机制试图通过为外部存储暴露一个基于可执行程序(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存储插件的关键组件和部署架构
下图描述了Kubernetes CSI存储插件的关键组件和推荐的容器化部署架构。
其中主要包括两种组件:CSI Controller和CSI Node。
**CSI Controller **
CSI Controller的主要功能是提供存储服务视角对存储资源和存储卷进行管理和操作。在Kubernetes中建议将其部署为单实例Pod,可以使用StatefulSet或Deployment控制器进行部署,设置副本数量为1,保证为一种存储插件只运行一个控制器实例。
在这个Pod内部署两个容器,如下所述。
(1)与Master(kube-controller-manager)通信的辅助sidecar容器。
在sidecar容器内又可以包含external-attacher和external-provisioner两个容器,它们的功能分别如下。
◎ external-attacher:监控VolumeAttachment资源对象的变更,触发针对CSI端点的ControllerPublish和ControllerUnpublish操作。
◎ external-provisioner:监控PersistentVolumeClaim资源对象的变更,触发针对CSI端点的CreateVolume和DeleteVolume操作。
(2)CSI Driver存储驱动容器,由第三方存储提供商提供,需要实现上述接口。
这两个容器通过本地Socket(Unix Domain Socket,UDS),并使用gPRC协议进行通信。sidecar容器通过Socket调用CSI Driver容器的CSI接口,CSI Driver容器负责具体的存储卷操作。
CSI Node
CSI Node的主要功能是对主机(Node)上的Volume进行管理和操作。在Kubernetes中建议将其部署为DaemonSet,在每个Node上都运行一个Pod。
在这个Pod中部署以下两个容器:
(1)与kubelet通信的辅助sidecar容器node-driver-registrar,主要功能是将存储驱动注册到kubelet中;
(2)CSI Driver存储驱动容器,由第三方存储提供商提供,主要功能是接收kubelet的调用,需要实现一系列与Node相关的CSI接口,例如NodePublishVolume接口(用于将Volume挂载到容器内的目标路径)、NodeUnpublishVolume接口(用于从容器中卸载Volume),等等。
node-driver-registrar容器与kubelet通过Node主机的一个hostPath目录下的unix socket进行通信。CSI Driver容器与kubelet通过Node主机的另一个hostPath目录下的unix socket进行通信,同时需要将kubelet的工作目录(默认为/var/lib/kubelet)挂载给CSI Driver容器,用于为Pod进行Volume的管理操作(包括mount、umount等)。
CSI存储插件的使用示例:
下面以csi-hostpath插件为例,对如何部署CSI插件、用户如何使用CSI插件提供的存储资源进行详细说明。
(1)设置Kubernetes服务启动参数。为kube-apiserver、kube-controller-manager和kubelet服务的启动参数添加:
1
2
3 1--feature-gates=VolumeSnapshotDataSource=true,CSINodeInfo=true,CSIDriverRegistry=true
2
3
这3个特性开关是Kubernetes从1.12版本引入的Alpha版功能,CSINodeInfo和CSIDriverRegistry需要手工创建其相应的CRD资源对象。
Kubernetes 1.10版本所需的CSIPersistentVolume和MountPropagation特性开关已经默认启用,KubeletPluginsWatcher特性开关也在Kubernetes 1.12版本中默认启用,无须在命令行参数中指定。
(2)创建CSINodeInfo和CSIDriverRegistry CRD资源对象。
csidriver.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 1apiVersion: apiextensions.k8s.io/v1beta1
2kind: CustomResourceDefinition
3metadata:
4 name: csidrivers.csi.storage.k8s.io
5 labels:
6 addonmanager.kubernetes.io/mode: Reconcile
7spec:
8 group: csi.storage.k8s.io
9 names:
10 kind: CSIDriver
11 plural: csidrivers
12 scope: Cluster
13 validation:
14 openAPIV3Schema:
15 properties:
16 spec:
17 description: Specification of the CSI Driver.
18 properties:
19 attachRequired:
20 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.
21 type: boolean
22 podInfoOnMountVersion:
23 description: Indicates this CSI volume driver requires additional pod
24 information (like podName, podUID, etc.) during mount operations.
25 type: string
26 version: v1alpha1
27
csinodeinfo.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 1apiVersion: apiextensions.k8s.io/v1beta1
2kind: CustomResourceDefinition
3metadata:
4 name: csinodeinfos.csi.storage.k8s.io
5 labels:
6 addonmanager.kubernetes.io/mode: Reconcile
7spec:
8 group: csi.storage.k8s.io
9 names:
10 kind: CSINodeInfo
11 plural: csinodeinfos
12 scope: Cluster
13 validation:
14 openAPIV3Schema:
15 properties:
16 spec:
17 description: Specification of CSINodeInfo
18 properties:
19 drivers:
20 description: List of CSI drivers running on the node and their specs.
21 type: array
22 items:
23 properties:
24 name:
25 description: The CSI driver that this object refers to.
26 type: string
27 nodeID:
28 description: The node from the driver point of view.
29 type: string
30 topologyKeys:
31 description: List of keys supported by the driver.
32 items:
33 type: string
34 type: array
35 status:
36 description: Status of CSINodeInfo
37 properties:
38 drivers:
39 description: List of CSI drivers running on the node and their statuses.
40 type: array
41 items:
42 properties:
43 name:
44 description: The CSI driver that this object refers to.
45 type: string
46 available:
47 description: Whether the CSI driver is installed.
48 type: boolean
49 volumePluginMechanism:
50 description: Indicates to external components the required mechanism
51 to use for any in-tree plugins replaced by this driver.
52 pattern: in-tree|csi
53 type: string
54 version: v1alpha1
55
使用kubectl apply命令完成创建:
1
2
3 1kubectl apply -f csidriver.yaml
2kubectl apply -f csinodeinfo.yaml
3
(3)创建csi-hostpath存储插件相关组件,包括csi-hostpath-attacher、csi-hostpathprovisioner和csi-hostpathplugin(其中包含csi-node-driver-registrar和hostpathplugin)。其中为每个组件都配置了相应的RBAC权限控制规则,对于安全访问Kubernetes资源对象非常重要。
csi-hostpath-attacher.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 1---
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5 name: csi-attacher
6 # replace with non-default namespace name
7 namespace: default
8---
9kind: ClusterRole
10apiVersion: rbac.authorization.k8s.io/v1
11metadata:
12 name: external-attacher-runner
13rules:
14 - apiGroups: [""]
15 resources: ["persistentvolumes"]
16 verbs: ["get", "list", "watch", "update"]
17 - apiGroups: [""]
18 resources: ["nodes"]
19 verbs: ["get", "list", "watch"]
20 - apiGroups: ["csi.storage.k8s.io"]
21 resources: ["csinodeinfos"]
22 verbs: ["get", "list", "watch"]
23 - apiGroups: ["storage.k8s.io"]
24 resources: ["volumeattachments"]
25 verbs: ["get", "list", "watch", "update"]
26---
27kind: ClusterRoleBinding
28apiVersion: rbac.authorization.k8s.io/v1
29metadata:
30 name: csi-attacher-role
31subjects:
32 - kind: ServiceAccount
33 name: csi-attacher
34 # replace with non-default namespace name
35 namespace: default
36roleRef:
37 kind: ClusterRole
38 name: external-attacher-runner
39 apiGroup: rbac.authorization.k8s.io
40---
41kind: Role
42apiVersion: rbac.authorization.k8s.io/v1
43metadata:
44 # replace with non-default namespace name
45 namespace: default
46 name: external-attacher-cfg
47rules:
48- apiGroups: [""]
49 resources: ["configmaps"]
50 verbs: ["get", "watch", "list", "delete", "update", "create"]
51---
52kind: RoleBinding
53apiVersion: rbac.authorization.k8s.io/v1
54metadata:
55 name: csi-attacher-role-cfg
56 # replace with non-default namespace name
57 namespace: default
58subjects:
59 - kind: ServiceAccount
60 name: csi-attacher
61 # replace with non-default namespace name
62 namespace: default
63roleRef:
64 kind: Role
65 name: external-attacher-cfg
66 apiGroup: rbac.authorization.k8s.io
67
68---
69kind: Service
70apiVersion: v1
71metadata:
72 name: csi-hostpath-attacher
73 labels:
74 app: csi-hostpath-attacher
75spec:
76 selector:
77 app: csi-hostpath-attacher
78 ports:
79 - name: dummy
80 port: 12345
81---
82kind: StatefulSet
83apiVersion: apps/v1
84metadata:
85 name: csi-hostpath-attacher
86spec:
87 serviceName: "csi-hostpath-attacher"
88 replicas: 1
89 selector:
90 matchLabels:
91 app: csi-hostpath-attacher
92 template:
93 metadata:
94 labels:
95 app: csi-hostpath-attacher
96 spec:
97 serviceAccountName: csi-attacher
98 containers:
99 - name: csi-attacher
100 image: quay.io/k8scsi/csi-attacher:v1.0.1
101 imagePullPolicy: IfNotPresent
102 args:
103 - --v=5
104 - --csi-address=$(ADDRESS)
105 env:
106 - name: ADDRESS
107 value: /csi/csi.sock
108 volumeMounts:
109 - mountPath: /csi
110 name: socket-dir
111 volumes:
112 - hostPath:
113 path: /var/lib/kubelet/plugins/csi-hostpath
114 type: DirectoryOrCreate
115 name: socket-dir
116
117
118
csi-hostpath-provisioner.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 1# csi-hostpath-provisioner
2---
3apiVersion: v1
4kind: ServiceAccount
5metadata:
6 name: csi-provisioner
7 # replace with non-default namespace name
8 namespace: default
9---
10kind: ClusterRole
11apiVersion: rbac.authorization.k8s.io/v1
12metadata:
13 name: external-provisioner-runner
14rules:
15 - apiGroups: [""]
16 resources: ["secrets"]
17 verbs: ["get", "list"]
18 - apiGroups: [""]
19 resources: ["persistentvolumes"]
20 verbs: ["get", "list", "watch", "create", "delete"]
21 - apiGroups: [""]
22 resources: ["persistentvolumeclaims"]
23 verbs: ["get", "list", "watch", "update"]
24 - apiGroups: ["storage.k8s.io"]
25 resources: ["storageclasses"]
26 verbs: ["get", "list", "watch"]
27 - apiGroups: [""]
28 resources: ["events"]
29 verbs: ["list", "watch", "create", "update", "patch"]
30 - apiGroups: ["snapshot.storage.k8s.io"]
31 resources: ["volumesnapshots"]
32 verbs: ["get", "list"]
33 - apiGroups: ["snapshot.storage.k8s.io"]
34 resources: ["volumesnapshotcontents"]
35 verbs: ["get", "list"]
36 - apiGroups: ["csi.storage.k8s.io"]
37 resources: ["csinodeinfos"]
38 verbs: ["get", "list", "watch"]
39 - apiGroups: [""]
40 resources: ["nodes"]
41 verbs: ["get", "list", "watch"]
42---
43kind: ClusterRoleBinding
44apiVersion: rbac.authorization.k8s.io/v1
45metadata:
46 name: csi-provisioner-role
47subjects:
48 - kind: ServiceAccount
49 name: csi-provisioner
50 # replace with non-default namespace name
51 namespace: default
52roleRef:
53 kind: ClusterRole
54 name: external-provisioner-runner
55 apiGroup: rbac.authorization.k8s.io
56---
57kind: Role
58apiVersion: rbac.authorization.k8s.io/v1
59metadata:
60 # replace with non-default namespace name
61 namespace: default
62 name: external-provisioner-cfg
63rules:
64- apiGroups: [""]
65 resources: ["endpoints"]
66 verbs: ["get", "watch", "list", "delete", "update", "create"]
67---
68kind: RoleBinding
69apiVersion: rbac.authorization.k8s.io/v1
70metadata:
71 name: csi-provisioner-role-cfg
72 # replace with non-default namespace name
73 namespace: default
74subjects:
75 - kind: ServiceAccount
76 name: csi-provisioner
77 # replace with non-default namespace name
78 namespace: default
79roleRef:
80 kind: Role
81 name: external-provisioner-cfg
82 apiGroup: rbac.authorization.k8s.io
83
84---
85kind: Service
86apiVersion: v1
87metadata:
88 name: csi-hostpath-provisioner
89 labels:
90 app: csi-hostpath-provisioner
91spec:
92 selector:
93 app: csi-hostpath-provisioner
94 ports:
95 - name: dummy
96 port: 12345
97---
98kind: StatefulSet
99apiVersion: apps/v1
100metadata:
101 name: csi-hostpath-provisioner
102spec:
103 serviceName: "csi-hostpath-provisioner"
104 replicas: 1
105 selector:
106 matchLabels:
107 app: csi-hostpath-provisioner
108 template:
109 metadata:
110 labels:
111 app: csi-hostpath-provisioner
112 spec:
113 serviceAccountName: csi-provisioner
114 containers:
115 - name: csi-provisioner
116 image: quay.io/k8scsi/csi-provisioner:v1.0.1
117 imagePullPolicy: IfNotPresent
118 args:
119 - "--provisioner=csi-hostpath"
120 - "--csi-address=$(ADDRESS)"
121 - "--connection-timeout=15s"
122 env:
123 - name: ADDRESS
124 value: /csi/csi.sock
125 volumeMounts:
126 - mountPath: /csi
127 name: socket-dir
128 volumes:
129 - hostPath:
130 path: /var/lib/kubelet/plugins/csi-hostpath
131 type: DirectoryOrCreate
132 name: socket-dir
133
csi-hostpathplugin.yaml的内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 1---
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5 name: csi-node-sa
6 # replace with non-default namespace name
7 namespace: default
8---
9kind: ClusterRole
10apiVersion: rbac.authorization.k8s.io/v1
11metadata:
12 name: driver-registrar-runner
13rules:
14 - apiGroups: [""]
15 resources: ["events"]
16 verbs: ["get", "list", "watch", "create", "update", "patch"]
17 # The following permissions are only needed when running
18 # driver-registrar without the --kubelet-registration-path
19 # parameter, i.e. when using driver-registrar instead of
20 # kubelet to update the csi.volume.kubernetes.io/nodeid
21 # annotation. That mode of operation is going to be deprecated
22 # and should not be used anymore, but is needed on older
23 # Kubernetes versions.
24 # - apiGroups: [""]
25 # resources: ["nodes"]
26 # verbs: ["get", "update", "patch"]
27---
28kind: ClusterRoleBinding
29apiVersion: rbac.authorization.k8s.io/v1
30metadata:
31 name: csi-driver-registrar-role
32subjects:
33 - kind: ServiceAccount
34 name: csi-node-sa
35 # replace with non-default namespace name
36 namespace: default
37roleRef:
38 kind: ClusterRole
39 name: driver-registrar-runner
40 apiGroup: rbac.authorization.k8s.io
41
42---
43kind: DaemonSet
44apiVersion: apps/v1
45metadata:
46 name: csi-hostpathplugin
47spec:
48 selector:
49 matchLabels:
50 app: csi-hostpathplugin
51 template:
52 metadata:
53 labels:
54 app: csi-hostpathplugin
55 spec:
56 serviceAccountName: csi-node-sa
57 hostNetwork: true
58 containers:
59 - name: driver-registrar
60 image: quay.io/k8scsi/csi-node-driver-registrar:v1.0.1
61 imagePullPolicy: IfNotPresent
62 args:
63 - --v=5
64 - --csi-address=/csi/csi.sock
65 - --kubelet-registration-path=/var/lib/kubelet/plugins/csi-hostpath/csi.sock
66 env:
67 - name: KUBE_NODE_NAME
68 valueFrom:
69 fieldRef:
70 apiVersion: v1
71 fieldPath: spec.nodeName
72 volumeMounts:
73 - mountPath: /csi
74 name: socket-dir
75 - mountPath: /registration
76 name: registration-dir
77 - name: hostpath
78 image: quay.io/k8scsi/hostpathplugin:v1.0.1
79 imagePullPolicy: IfNotPresent
80 args:
81 - "--v=5"
82 - "--endpoint=$(CSI_ENDPOINT)"
83 - "--nodeid=$(KUBE_NODE_NAME)"
84 env:
85 - name: CSI_ENDPOINT
86 value: unix:///csi/csi.sock
87 - name: KUBE_NODE_NAME
88 valueFrom:
89 fieldRef:
90 apiVersion: v1
91 fieldPath: spec.nodeName
92 securityContext:
93 privileged: true
94 volumeMounts:
95 - mountPath: /csi
96 name: socket-dir
97 - mountPath: /var/lib/kubelet/pods
98 mountPropagation: Bidirectional
99 name: mountpoint-dir
100 volumes:
101 - hostPath:
102 path: /var/lib/kubelet/plugins/csi-hostpath
103 type: DirectoryOrCreate
104 name: socket-dir
105 - hostPath:
106 path: /var/lib/kubelet/pods
107 type: DirectoryOrCreate
108 name: mountpoint-dir
109 - hostPath:
110 path: /var/lib/kubelet/plugins_registry
111 type: Directory
112 name: registration-dir
113
114
使用kubectl apply命令完成创建:
1
2
3
4 1kubectl apply -f csi-hostpath-attacher.yaml
2kubectl apply -f csi-hostpath-provisioner.yaml
3kubectl apply -f csi-hostpathplugin.yaml
4
至此就完成了CSI存储插件的部署。
(4)应用容器使用CSI存储。应用程序如果希望使用CSI存储插件提供的存储服务,则仍然使用Kubernetes动态存储管理机制。首先通过创建StorageClass和PVC为应用容器准备存储资源,然后容器就可以挂载PVC到容器内的目录进行使用了。
创建一个StorageClass,provisioner为CSI存储插件的类型,在本例中为csi-hostpath:
1
2
3
4
5
6
7
8 1apiVersion: storage.k8s.io/v1
2kind: StorageClass
3metadata:
4 name: csi-hostpath-sc
5provisioner: csi-hostpath
6reclaimPolicy: Delete
7volumeBindingMode: Immediate
8
1
2 1kubectl apply -f csi-storageclass.yaml
2
创建一个PVC,引用刚刚创建的StorageClass,申请存储空间为1GiB:
1
2
3
4
5
6
7
8
9
10
11
12 1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4 name: csi-pvc
5spec:
6 accessModes:
7 - ReadWriteOnce
8 resources:
9 requests:
10 storage: 1Gi
11 storageClassName: csi-hostpath-sc
12
1
2 1kubectl apply -f csi-pvc.yaml
2
查看PVC和系统自动创建的PV,状态为Bound,说明创建成功:
1
2 1kubectl get pv,pvc
2
最后,在应用容器的配置中使用该PVC:
csi-app.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1kind: Pod
2apiVersion: v1
3metadata:
4 name: my-csi-app
5spec:
6 containers:
7 - name: my-csi-app
8 image: busybox
9 imagePullPolicy: IfNotPresent
10 command: ["sleep", "1000000"]
11 volumeMounts:
12 - mountPath: "/data"
13 name: my-csi-volume
14 volumes:
15 - name: my-csi-volume
16 persistentVolumeClaim:
17 claimName: csi-pvc
18
1
2 1kubectl apply -f csi-app.yaml
2
1
2 1kubecatl get pods
2
在Pod创建成功之后,应用容器中的/data目录使用的就是CSI存储插件提供的存储。
我们通过kubelet的日志可以查看到Volume挂载的详细过程。
每种CSI存储插件都提供了容器镜像,与external-attacher、external-provisioner、node-driver-registrar等sidecar辅助容器共同完成存储插件系统的部署,每个插件的部署配置详见官网https://kubernetes-csi.github.io/docs/drivers.html中的链接。
Kubernetes从1.12版本开始引入存储卷快照(Volume Snapshots)功能,通过新的CRD自定义资源对象VolumeSnapshotContent、VolumeSnapshot和VolumeSnapshotClass进行管理。VolumeSnapshotContent定义从当前PV创建的快照,类似于一个新的PV;VolumeSnapshot定义需要绑定某个快照的请求,类似于PVC的定义;VolumeSnapshotClass用于屏蔽VolumeSnapshotContent的细节,类似于StorageClass的功能。
下面是一个VolumeSnapshotContent的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 1apiVersion: snapshot.storage.k8s.io/v1alpha1
2kind: VolumeSnapshotContent
3metadata:
4 name: new-snapshot-content-test
5spec:
6 snapshotClassName: csi-hostpath-snapclass
7 source:
8 name: pvc-test
9 kind: PersistentVolumeClaim
10 volumeSnapshotSource:
11 csiVolumeSnapshotSource:
12 driver: csi-hostpath
13 restoreSize: 10Gi
14
下面是一个VolumeSnapshot的例子:
1
2
3
4
5
6
7
8
9
10 1apiVersion: snapshot.storage.k8s.io/v1alpha1
2kind: VolumeSnapshot
3metadata:
4 name: new-snapshot-test
5spec:
6 snapshotClassName: csi-hostpath-snapclass
7 source:
8 name: pvc-test
9 kind: PersistentVolumeClaim
10
后续要进一步完善的工作如下。
(1)将以下Alpha版功能更新到Beta版:
◎ Raw Block类型的Volumes;
◎ 拓扑感知,Kubernetes理解和影响CSI卷的配置位置(如zone、region 等)的能力;
◎ 完善基于CRD的扩展功能(如Skip attach、Pod info on mount等)。
(2)完善对本地短暂卷(Local Ephemeral Volume)的支持。
(3)将Kubernetes“in-tree”存储卷插件迁移到CSI。
小结:
本节内容到此结束,内容有点多,慢慢消化。