5、StorageClass详解
StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定,实现了动态的资源供应。基于StorageClass的动态资源供应模式将逐步成为云平台的标准存储配置模式。
StorageClass的定义主要包括名称、后端存储的提供者(provisioner)和后端存储的相关参数配置。StorageClass一旦被创建出来,则将无法修改。如需更改,则只能删除原StorageClass的定义重建。下例定义了一个名为standard的StorageClass,提供者为aws-ebs,其参数设置了一个type,值为gp2:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard # StorageClass 的名称(集群中的默认存储类通常取名为 standard)
provisioner: kubernetes.io/aws-ebs # 动态卷供应商,指定使用 AWS EBS 插件
parameters:
type: gp2 # EBS 卷类型参数,gp2 表示通用型 SSD (General Purpose SSD)
5.1、StorageClass的关键配置参数
5.1.1、提供者(Provisioner)
描述存储资源的提供者,也可以看作后端存储驱动。目前Kubernetes支持的Provisioner都以“ kubernetes.io/”为开头,用户也可以使用自定义的后端存储提供者。为了符合StorageClass的用法,自定义Provisioner需要符合存储卷的开发规范,详见https://github.com/kubernetes/community/blob/master/contributors/design-proposals/volume-provisioning.md的说明。
5.1.2、参数(Parameters
后端存储资源提供者的参数设置,不同的Provisioner包括不同的参数设置。某些参数可以不显示设定,Provisioner将使用其默认值。
接下来通过几种常见的Provisioner对StorageClass的定义进行详细说明。
-
1)AWS EBS存储卷
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: slow # StorageClass 的名称(此处虽然叫 slow,但实际配置了高性能 io1 类型) provisioner: kubernetes.io/aws-ebs # 动态卷供应商,指定使用 AWS EBS 插件 parameters: type: io1 # EBS 卷类型参数,io1 表示 provisioned IOPS SSD(适用于高吞吐量、低延迟的关键业务数据库) zone: us-east-1d # 指定动态创建云硬盘所在的 AWS 可用区 (Availability Zone) iopsPerGB: "10" # 每 GB 盘容量所分配的 IOPS 比例(io1 类型专属参数,用于计算最大 IOPS)参数说明如下(详细说明请参考AWS EBS文档)。
- type:可选项为io1,gp2,sc1,st1,默认值为gp2。
- zone:AWS zone的名称。
- iopsPerGB:仅用于io1类型的Volume,意为每秒每GiB的I/O操作数量。
- encrypted:是否加密。
- kmsKeyId:加密时的Amazon Resource Name。
-
2)GCE PD存储卷
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: slow # StorageClass 的名称(此处命名为 slow,代表标准性能存储) provisioner: kubernetes.io/gce-pd # 动态卷供应商,指定使用 Google Compute Engine (GCE) 磁盘插件 parameters: type: pd-standard # 磁盘类型参数,pd-standard 表示标准机械硬盘(HDD),适用于吞吐量要求不高、成本敏感的场景 zone: us-central1-a # 指定动态创建云硬盘所在的具体 GCP 可用区 (Availability Zone)参数说明如下(详细说明请参考GCE文档)。
- type:可选项为pd-standard、pd-ssd,默认值为pd-standard。
- zone:GCE zone名称。
-
3)GlusterFS存储卷
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: slow # StorageClass 的名称 provisioner: kubernetes.io/glusterfs # 动态卷供应商,指定使用 GlusterFS 插件(通常结合 Heketi 管理器) parameters: resturl: "http://127.0.0.1:8081" # Heketi REST API 的服务地址,用于自动化动态卷的创建与删除 clusterid: "630372ccdc720a92c681fb928f27b53f" # Heketi 管理的 GlusterFS 集群唯一标识符 (Cluster ID) restauthenabled: "true" # 是否启用 Heketi REST API 的身份认证 restuser: "admin" # 用于访问 Heketi REST API 的用户名 secretNamespace: "default" # 存储访问 Heketi 所需凭据的 Kubernetes Secret 所在的命名空间 secretName: "heketi-secret" # 存储 Heketi 认证密码的 Kubernetes Secret 名称 gidMin: "40000" # 动态分配卷时的最小 GID(组 ID),用于控制访问权限 gidMax: "50000" # 动态分配卷时的最大 GID(组 ID) volumetype: "replicate:3" # 卷类型配置,此处设置为 3 副本模式 (replicate:3),提供高可用和数据冗余参数说明如下(详细说明请参考GlusterFS和Heketi的文档)。
- resturl:Gluster REST服务(Heketi)的URL地址,用于自动完成GlusterFSvolume的设置。
- restauthenabled:是否对Gluster REST服务启用安全机制。
- restuser:访问Gluster REST服务的用户名。
- secretNamespace和secretName:保存访问Gluster REST服务密码的Secret资源对象名。
- clusterid:GlusterFS的Cluster ID。
- gidMin和gidMax:StorageClass的GID范围,用于动态资源供应时为PV设置的GID。
- volumetype:设置GlusterFS的内部Volume类型,例如replicate:3(Replicate类型,3份副本);disperse:4:2(Disperse类型,数据4份,冗余两份;“none”(Distribute类型)。
-
4)OpenStack Cinder存储卷
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gold # StorageClass 的名称(此处命名为 gold,通常用于代表高性能或优选存储层) provisioner: kubernetes.io/cinder # 动态卷供应商,指定使用 OpenStack Cinder 插件 parameters: type: fast # 存储后端类型参数(例如对应 OpenStack 中配置的 SSD 或高性能卷类型) availability: nova # 指定 OpenStack 卷所在的可用区(通常与 Compute 服务的可用域相对应,如 nova)参数说明如下。
- type:Cinder的VolumeType,默认值为空。
- availability:Availability Zone,默认值为空。
其他Provisioner的StorageClass相关参数设置请参考它们各自的配置手册。
5.2、设置默认的StorageClass
要在系统中设置一个默认的StorageClass,则首先需要启用名为DefaultStorageClass的admission controller,即在kube-apiserver的命令行参数–admission-control中增加:
--admission-control=...,DefaultStorageClass
然后,在StorageClass的定义中设置一个annotation:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gold # StorageClass 的名称(此处命名为 gold,代表黄金/高性能存储层)
annotations:
storageclass.beta.kubernetes.io/is-default-class: "true" # 关键注解:将此存储类设为集群默认存储类。当用户创建 PVC 时若未显式指定 storageClassName,将自动使用该存储类
provisioner: kubernetes.io/gce-pd # 动态卷供应商,指定使用 Google Compute Engine (GCE) 磁盘插件
parameters:
type: pd-ssd # 磁盘类型参数,pd-ssd 表示高性能固态硬盘 (SSD),提供极高的 IOPS 和低延迟
通过kubectl create命令创建成功后,查看StorageClass列表,可以看到名为gold的StorageClass被标记为default:
# kubectl get sc
NAME TYPE
gold (default) kubernetes.io/gce-pd
6、动态存储管理实战:GlusterFS
本节以GlusterFS为例,从定义StorageClass、创建GlusterFS和Heketi服务、用户申请PVC到创建Pod使用存储资源,对StorageClass和动态资源分配进行详细说明,进一步剖析Kubernetes的存储机制。
6.1、准备工作
为了能够使用GlusterFS,首先在计划用于GlusterFS的各Node上安装GlusterFS客户端:
# yum install glusterfs glusterfs-fuse
GlusterFS管理服务容器需要以特权模式运行,在kube-apiserver的启动参数中增加:
--allow-privileged=true
给要部署GlusterFS管理服务的节点打上“storagenode=glusterfs”的标签,是为了将GlusterFS容器定向部署到安装了GlusterFS的Node上:
# kubectl label node k8s-node-1 storagenode=glusterfs
# kubectl label node k8s-node-2 storagenode=glusterfs
# kubectl label node k8s-node-3 storagenode=glusterfs
6.2、创建GlusterFS管理服务容器集群
GlusterFS管理服务容器以DaemonSet的方式进行部署,确保在每个Node上都运行一个GlusterFS管理服务。glusterfs-daemonset.yaml的内容如下:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: glusterfs # DaemonSet 名称
labels:
glusterfs: daemonset # 标签
annotations:
description: GlusterFS DaemonSet
tags: glusterfs
spec:
selector:
matchLabels:
glusterfs-node: pod # 现代化 apps/v1 必须的 selector 配置,用于匹配管理的 Pod
template:
metadata:
name: glusterfs
labels:
glusterfs-node: pod # Pod 标签,与上面的 matchLabels 相对应
spec:
hostNetwork: true # 允许 Pod 使用宿主机的网络命名空间
nodeSelector:
storagenode: glusterfs # 节点选择器:仅在带有 storagenode=glusterfs 标签的节点上部署守护进程
containers:
- image: gluster/gluster-centos:latest
name: glusterfs
volumeMounts:
- name: glusterfs-heketi
mountPath: "/var/lib/heketi"
- name: glusterfs-run
mountPath: "/run"
- name: glusterfs-lvm
mountPath: "/run/lvm"
- name: glusterfs-etc
mountPath: "/etc/glusterfs"
- name: glusterfs-logs
mountPath: "/var/log/glusterfs"
- name: glusterfs-config
mountPath: "/var/lib/glusterd"
- name: glusterfs-dev
mountPath: "/dev"
- name: glusterfs-misc
mountPath: "/var/lib/misc/glusterfsd"
- name: glusterfs-cgroup
mountPath: "/sys/fs/cgroup"
readOnly: true
- name: glusterfs-ssl
mountPath: "/etc/ssl"
readOnly: true
securityContext:
capabilities: {}
privileged: true # 赋予特权模式,以便容器内部能够操作底层存储与挂载
readinessProbe:
timeoutSeconds: 3
initialDelaySeconds: 60
exec:
command:
- "/bin/bash"
- "-c"
- systemctl status glusterd.service # 探针:检查 glusterd 服务状态
livenessProbe:
timeoutSeconds: 3
initialDelaySeconds: 60
exec:
command:
- "/bin/bash"
- "-c"
- systemctl status glusterd.service # 存活探针
volumes:
- name: glusterfs-heketi
hostPath:
path: "/var/lib/heketi"
- name: glusterfs-run
hostPath:
path: "/run"
- name: glusterfs-lvm
hostPath:
path: "/run/lvm"
- name: glusterfs-etc
hostPath:
path: "/etc/glusterfs"
- name: glusterfs-logs
hostPath:
path: "/var/log/glusterfs"
- name: glusterfs-config
hostPath:
path: "/var/lib/glusterd"
- name: glusterfs-dev
hostPath:
path: "/dev"
- name: glusterfs-misc
hostPath:
path: "/var/lib/misc/glusterfsd"
- name: glusterfs-cgroup
hostPath:
path: "/sys/fs/cgroup"
- name: glusterfs-ssl
hostPath:
path: "/etc/ssl"
# kubectl create -f glusterfs-daemonset.yaml
daemonset "glusterfs" created
# kubectl get po
NAME READY STATUS RESTARTS AGE
glusterfs-k2src 1/1 Running 0 1m
glusterfs-q32z2 1/1 Running 0 1m
6.3、创建Heketi服务
Heketi是一个提供RESTful API管理GlusterFS卷的框架,并能够在OpenStack、Kubernetes、OpenShift等云平台上实现动态存储资源供应,支持GlusterFS多集群管理,便于管理员对GlusterFS进行操作。下图简单描述了Heketi的作用。

在部署Heketi服务之前,需要为它创建一个ServiceAccount对象:
apiVersion: v1
kind: ServiceAccount
metadata:
name: heketi-service-account # 服务账号名称,通常用于赋予 Heketi 访问 Kubernetes API 的权限
kubectl create -f heketi-service-account.yaml
部署Heketi服务:
# heketi-deployment-svc.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-heketi
labels:
glusterfs: heketi-deployment
deploy-heketi: heketi-deployment
annotations:
description: Defines how to deploy Heketi
spec:
replicas: 1
selector:
matchLabels:
name: deploy-heketi # 现代化 apps/v1 必须的 selector 配置
template:
metadata:
name: deploy-heketi
labels:
name: deploy-heketi
glusterfs: heketi-pod
spec:
serviceAccountName: heketi-service-account # 关联前面创建的 ServiceAccount
containers:
- image: heketi/heketi
name: deploy-heketi
env:
- name: HEKETI_EXECUTOR
value: kubernetes
- name: HEKETI_FSTAB
value: "/var/lib/heketi/fstab"
- name: HEKETI_SNAPSHOT_LIMIT
value: '14'
- name: HEKETI_KUBE_GLUSTER_DAEMONSET
value: "y"
ports:
- containerPort: 8080
volumeMounts:
- name: db
mountPath: "/var/lib/heketi"
readinessProbe:
timeoutSeconds: 3
initialDelaySeconds: 3
httpGet:
path: "/hello"
port: 8080
livenessProbe:
timeoutSeconds: 3
initialDelaySeconds: 30
httpGet:
path: "/hello"
port: 8080
volumes:
- name: db
hostPath:
path: "/heketi-data"
---
apiVersion: v1
kind: Service
metadata:
name: deploy-heketi
labels:
glusterfs: heketi-service
deploy-heketi: support
annotations:
description: Exposes Heketi Service
spec:
selector:
name: deploy-heketi # 匹配 Heketi Deployment 所管理的 Pod 标签
ports:
- name: deploy-heketi
port: 8080
targetPort: 8080
需要注意的是,Heketi的DB数据需要持久化保存,建议使用hostPath或其他共享存储进行保存:
# kubectl create -f heketi-deployment-svc.yaml
deployment "deploy-heketi" created
service "deploy-heketi" created
6.4、为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文件的内容如下:
{
"clusters": [
{
"nodes": [
{
"node": {
"hostnames": {
"manage": [
"k8s-node-1"
],
"storage": [
"192.168.18.3"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
},
{
"node": {
"hostnames": {
"manage": [
"k8s-node-2"
],
"storage": [
"192.168.18.4"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
},
{
"node": {
"hostnames": {
"manage": [
"k8s-node-3"
],
"storage": [
"192.168.18.5"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
}
]
}
]
}
进入Heketi容器,使用命令行工具heketi-cli完成GlusterFS集群的创建:
# export HEKETI_CLI_SERVER=http://localhost:8080
# heketi-cli topology load --json=topology.json
Creating cluster ... ID: f643da1cd64691c5705932a46a95d1d5
Creating node k8s-node-1 ... ID: 883506b091a22bd13f10bc3d0fb51223
Adding device /dev/sdb ... OK
Creating node k8s-node-2 ... ID: e64b879689106f82a9c4ac910a865cc8
Adding device /dev/sdb ... OK
Creating node k8s-node-3 ... ID: b7783484180f6a592a30baebfb97d9be
Adding device /dev/sdb ... OK
经过这个操作,Heketi完成了GlusterFS集群的创建,同时在GlusterFS集群的各个节点的/dev/sdb盘上成功创建了PV和VG。
查看Heketi的topology信息,可以看到Node和Device的详细信息,包括磁盘空间的大小和剩余空间。此时,Volume和Brick还未创建:
# heketi-cli topology info
Cluster Id: f643da1cd64691c5705932a46a95d1d5
Volumes:
Nodes:
Node Id: 883506b091a22bd13f10bc3d0fb51223
State: online
Cluster Id: f643da1cd64691c5705932a46a95d1d5
Zone: 1
Management Hostname: k8s-node-1
Storage Hostname: 192.168.18.3
Devices:
Id:b474f14b0903ed03ec80d4a989f943f2 Name:/dev/sdb
State:online Size (GiB):9 Used (GiB):0 Free (GiB):9
Bricks:
Node Id: b7783484180f6a592a30baebfb97d9be
State: online
Cluster Id: f643da1cd64691c5705932a46a95d1d5
Zone: 1
Management Hostname: k8s-node-3
Storage Hostname: 192.168.18.5
Devices:
Id:fac3fa5ac1de3d5bde3aa68f6aa61285 Name:/dev/sdb
State:online Size (GiB):9 Used (GiB):0 Free (GiB):9
Bricks:
Node Id: e64b879689106f82a9c4ac910a865cc8
State: online
Cluster Id: f643da1cd64691c5705932a46a95d1d5
Zone: 1
Management Hostname: k8s-node-2
Storage Hostname: 192.168.18.4
Devices:
Id:05532e7db723953e8643b64b36aee1d1 Name:/dev/sdb
State:online Size (GiB):9 Used (GiB):0 Free (GiB):9
Bricks:
6.5、定义StorageClass
准备工作已经就绪,集群管理员现在可以在Kubernetes集群中定义一个StorageClass了。storageclass-gluster-heketi.yaml配置文件的内容如下:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gluster-heketi # StorageClass 的名称
provisioner: kubernetes.io/glusterfs # 动态卷供应商,指定使用 GlusterFS 插件
parameters:
resturl: "http://172.17.2.2:8080" # Heketi REST API 的服务地址(无需认证的内网或测试环境地址)
restauthenabled: "false" # 关闭 Heketi REST API 的身份认证(适用于便捷测试,生产环境强烈建议设为 true 并配置 Secret)
Provisioner参数必须被设置为“kubernetes.io/glusterfs”。
resturl的地址需要被设置为API Server所在主机可以访问到的Heketi服务的某个地址,可以使用服务ClusterIP+端口号、容器IP地址+端口号,或将服务映射到物理机,使用物理机IP+NodePort。
创建这个StorageClass资源对象:
# kubectl create -f storageclass-gluster-heketi.yaml
storageclass "gluster-heketi" created
6.6、定义PVC
现在,用户可以申请一个PVC了。例如,一个用户申请一个1GiB空间的共享存储资源,StorageClass使用“gluster-heketi”,未定义任何Selector,说明使用动态资源供应模式:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-gluster-heketi # 持久卷声明 (PVC) 的名称
spec:
storageClassName: gluster-heketi # 指定关联前面创建的 Heketi StorageClass,实现动态开辟存储
accessModes:
- ReadWriteOnce # 访问模式:请求单节点以读写方式挂载
resources:
requests:
storage: 1Gi # 申请的存储空间大小为 1 吉字节 (Gi)
kubectl create -f pvc-gluster-heketi.yaml
PVC的定义一旦生成,系统便将触发Heketi进行相应的操作,主要为在GlusterFS集群上创建brick,再创建并启动一个Volume。整个过程可以在Heketi的日志中查到:
......
[kubeexec] DEBUG 2017/04/26 00:51:30
/src/github.com/heketi/heketi/executors/kubeexec/kubeexec.go:250: Host:
k8s-node-1 Pod: glusterfs-ld7nh Command: gluster --mode=script volume create
vol_87b9314cb76bafacfb7e9cdc04fcaf05 replica 3
192.168.18.3:/var/lib/heketi/mounts/vg_b474f14b0903ed03ec80d4a989f943f2/brick_d0
8520c9ff7b9a0a9165f9815671f2cd/brick
192.168.18.5:/var/lib/heketi/mounts/vg_fac3fa5ac1de3d5bde3aa68f6aa61285/brick_68
18dce118b8a54e9590199d44a3817b/brick
192.168.18.4:/var/lib/heketi/mounts/vg_05532e7db723953e8643b64b36aee1d1/brick_9e
cb8f7fde1ae937011f04401e7c6c56/brick
Result: volume create: vol_87b9314cb76bafacfb7e9cdc04fcaf05: success: please
start the volume to access data
......
[kubeexec] DEBUG 2017/04/26 00:51:33
/src/github.com/heketi/heketi/executors/kubeexec/kubeexec.go:250: Host:
k8s-node-1 Pod: glusterfs-ld7nh Command: gluster --mode=script volume start
vol_87b9314cb76bafacfb7e9cdc04fcaf05
Result: volume start: vol_87b9314cb76bafacfb7e9cdc04fcaf05: success
......
查看PVC的状态,可见其已经为Bound(已绑定):
# kubectl get pvc
NAME STATUS VOLUME CAPACITY
ACCESSMODES STORAGECLASS AGE
pvc-gluster-heketi Bound pvc-783cf949-2a1a-11e7-8717-000c29eaed40 1Gi
RWX gluster-heketi 6m
查看PV,可见系统自动创建的PV:
查看该PV的详细信息,可以看到其容量、引用的StorageClass等信息都已正确设置,状态也为Bound,回收策略则为默认的Delete。同时Gluster的Endpoint和Path也由Heketi自动完成了设置:
# kubectl describe pv pvc-783cf949-2a1a-11e7-8717-000c29eaed40
Name: pvc-783cf949-2a1a-11e7-8717-000c29eaed40
Labels: <none>
Annotations: pv.beta.kubernetes.io/gid=2000
pv.kubernetes.io/bound-by-controller=yes
pv.kubernetes.io/provisioned-by=kubernetes.io/glusterfs
StorageClass: gluster-heketi
Status: Bound
Claim: default/pvc-gluster-heketi
Reclaim Policy: Delete
Access Modes: RWX
Capacity: 1Gi
Message:
Source:
Type: Glusterfs (a Glusterfs mount on the host that shares a pod's
lifetime)
EndpointsName: glusterfs-dynamic-pvc-gluster-heketi
Path: vol_87b9314cb76bafacfb7e9cdc04fcaf05
ReadOnly: false
Events: <none>
至此,一个可供Pod使用的PVC就创建成功了。接下来Pod就能通过Volume的设置将这个PVC挂载到容器内部进行使用了。
6.7、Pod使用PVC的存储资源
在Pod中使用PVC定义的存储资源非常容易,只需设置一个Volume,其类型为persistentVolumeClaim,即可轻松引用一个PVC。下例中使用一个busybox容器验证对PVC的使用,注意Pod需要与PVC属于同一个Namespace:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-gluster-heketi # 持久卷声明 (PVC) 的名称
spec:
storageClassName: gluster-heketi # 指定关联前面创建的 Heketi StorageClass,实现动态开辟存储
accessModes:
- ReadWriteOnce # 访问模式:请求单节点以读写方式挂载
resources:
requests:
storage: 1Gi # 申请的存储空间大小为 1 吉字节 (Gi)
kubectl create -f pvc-gluster-heketi.yaml
进入容器pod-use-pvc,在/pv-data目录下创建一些文件:
# kubectl exec -ti pod-use-pvc -- /bin/sh
/ # cd /pv-data
/ # touch a
/ # echo "hello" > b
可以验证文件a和b在GlusterFS集群中是否正确生成。
至此,使用Kubernetes最新的动态存储供应模式,配合StorageClass和Heketi共同搭建基于GlusterFS的共享存储就完成了。有兴趣的读者可以继续尝试StorageClass的其他设置,例如调整GlusterFS的Volume类型、修改PV的回收策略等。
在使用动态存储供应模式的情况下,相对于静态模式的优势至少包括如下两点。
- (1)管理员无须预先创建大量的PV作为存储资源。
- (2)用户在申请PVC时无法保证容量与预置PV的容量完全匹配。从Kubernetes 1.6版本开始,建议用户优先考虑使用StorageClass的动态存储供应模式进行存储管理。
7、CSI存储机制详解
Kubernetes从1.9版本开始引入容器存储接口ContainerStorage Interface(CSI)机制,用于在Kubernetes和外部存储系统之间建立一套标准的存储管理接口,通过该接口为容器提供存储服务。CSI到Kubernetes 1.10版本升级为Beta版,到Kubernetes 1.13版本升级为GA版,已逐渐成熟。
7.1、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第三方存储插件的标准方案。
7.2、CSI存储插件的关键组件和部署架构
下图描述了Kubernetes CSI存储插件的关键组件和推荐的容器化部署架构。

其中主要包括两种组件:CSI Controller和CSI Node。
-
1.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操作。
这两个容器通过本地Socket(Unix Domain Socket,UDS),并使用gPRC协议进行通信。sidecar容器通过Socket调用CSI Driver容器的CSI接口,CSI Driver容器负责具体的存储卷操作。
-
-
2.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等)。
7.3、CSI存储插件的使用示例
下面以csi-hostpath插件为例,对如何部署CSI插件、用户如何使用CSI插件提供的存储资源进行详细说明。
-
(1)设置Kubernetes服务启动参数。为kube-apiserver、kube-controller-manager和kubelet服务的启动参数添加:
--feature-gates=VolumeSnapshotDataSource=true,CSINodeInfo=true,CSIDriverRegi stry=true这3个特性开关是Kubernetes从1.12版本引入的Alpha版功能,CSINodeInfo和CSIDriverRegistry需要手工创建其相应的CRD资源对象。
Kubernetes 1.10版本所需的CSIPersistentVolume和MountPropagation特性开关已经默认启用,KubeletPluginsWatcher特性开关也在Kubernetes 1.12版本中默认启用,无须在命令行参数中指定。
-
(2)创建CSINodeInfo和CSIDriverRegistry CRD资源对象。
csidriver.yaml的内容如下:apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: csidrivers.csi.storage.k8s.io # CRD 的完整名称,通常为 plural.group labels: addonmanager.kubernetes.io/mode: Reconcile # 标记由集群插件管理器进行调谐管理 spec: group: csi.storage.k8s.io # API 组名称 names: kind: CSIDriver # 自定义资源对应的 Kind 名称 plural: csidrivers # API 路径中使用的复数名称 scope: Cluster # 作用域:集群级别 (Cluster-scoped) versions: - name: v1 # 将 API 版本升级为标准稳定的 v1 served: true # 是否由 API Server 提供此版本的服务 storage: true # 是否为底层存储的版本(必须且只能有一个版本设置为 true) schema: openAPIV3Schema: type: object properties: spec: description: "Specification of the CSI Driver." type: object properties: attachRequired: description: "Indicates this CSI volume driver requires an attach operation, and that Kubernetes should call attach and wait for any attach operation to 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 volumeLifecycleModes: description: "volumeLifecycleModes defines what modes of volume lifecycle the CSI driver supports." type: array items: type: stringcsinodeinfo.yaml的内容如下:
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: csinodeinfos.csi.storage.k8s.io # CRD 的完整名称,通常为 plural.group labels: addonmanager.kubernetes.io/mode: Reconcile # 标记由集群插件管理器进行调谐管理 spec: group: csi.storage.k8s.io # API 组名称 names: kind: CSINodeInfo # 自定义资源对应的 Kind 名称 plural: csinodeinfos # API 路径中使用的复数名称 scope: Cluster # 作用域:集群级别 (Cluster-scoped) versions: - name: v1 # 将 API 版本升级为标准稳定的 v1(或根据需求保留 v1alpha1/v1beta1,此处升级为标准 v1 结构) served: true # 是否由 API Server 提供此版本的服务 storage: true # 是否为底层存储的版本(必须且只能有一个版本设置为 true) schema: openAPIV3Schema: type: object properties: spec: description: "Specification of CSINodeInfo" type: object properties: drivers: description: "List of CSI drivers running on the node and their specs." type: array items: type: object 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." type: array items: type: string status: description: "Status of CSINodeInfo" type: object properties: drivers: description: "List of CSI drivers running on the node and their statuses." type: array items: type: object 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使用kubectl create命令完成创建:
# kubectl create -f csidriver.yaml customresourcedefinition.apiextensions.k8s.io/csidrivers.csi.storage.k8s.io created # kubectl create -f csinodeinfo.yaml customresourcedefinition.apiextensions.k8s.io/csinodeinfos.csi.storage.k8s.i o created -
(3)创建csi-hostpath存储插件相关组件,包括csi-hostpath-attacher、csi-hostpath-provisioner和csi-hostpathplugin(其中包含csi-node-driver-registrar和hostpathplugin)。其中为每个组件都配置了相应的RBAC权限控制规则,对于安全访问Kubernetes资源对象非常重要。
csi-hostpath-attacher.yaml的内容如下:
# ========================================== # 1. ServiceAccount (服务账号) # ========================================== apiVersion: v1 kind: ServiceAccount metadata: name: csi-attacher namespace: default # 关联的命名空间(如需更改,请同步替换其他资源中的 namespace) --- # ========================================== # 2. ClusterRole (集群角色:赋予 Attacher 操作 PV、节点及卷附加的权限) # ========================================== 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"] --- # ========================================== # 3. ClusterRoleBinding (集群角色绑定) # ========================================== kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-attacher-role subjects: - kind: ServiceAccount name: csi-attacher namespace: default roleRef: kind: ClusterRole name: external-attacher-runner apiGroup: rbac.authorization.k8s.io --- # ========================================== # 4. Role (普通角色:用于当前命名空间下的领导者选举 ConfigMap 操作) # ========================================== kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: external-attacher-cfg namespace: default rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "watch", "list", "delete", "update", "create"] --- # ========================================== # 5. RoleBinding (角色绑定) # ========================================== kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: csi-attacher-role-cfg namespace: default subjects: - kind: ServiceAccount name: csi-attacher namespace: default roleRef: kind: Role name: external-attacher-cfg apiGroup: rbac.authorization.k8s.io --- # ========================================== # 6. Service (服务定义) # ========================================== kind: Service apiVersion: v1 metadata: name: csi-hostpath-attacher labels: app: csi-hostpath-attacher spec: selector: app: csi-hostpath-attacher ports: - name: dummy port: 12345 --- # ========================================== # 7. StatefulSet (有状态工作负载:运行 csi-attacher 进程) # ========================================== kind: StatefulSet apiVersion: apps/v1 metadata: name: csi-hostpath-attacher spec: serviceName: "csi-hostpath-attacher" replicas: 1 selector: matchLabels: app: csi-hostpath-attacher template: metadata: labels: app: csi-hostpath-attacher spec: serviceAccountName: csi-attacher # 使用上面创建的 ServiceAccount containers: - name: csi-attacher image: quay.io/k8scsi/csi-attacher:v1.0.1 imagePullPolicy: IfNotPresent args: - --v=5 # 日志详细级别 - --csi-address=$(ADDRESS) # 指定 CSI Socket 路径 env: - name: ADDRESS value: /csi/csi.sock volumeMounts: - name: socket-dir mountPath: /csi volumes: - name: socket-dir hostPath: path: /var/lib/kubelet/plugins/csi-hostpath type: DirectoryOrCreatecsi-hostpath-provisioner.yaml的内容如下:
# ========================================== # 1. ServiceAccount (服务账号) # ========================================== apiVersion: v1 kind: ServiceAccount metadata: name: csi-provisioner namespace: default # 关联的命名空间(如需更改,请同步替换其他资源中的 namespace) --- # ========================================== # 2. ClusterRole (集群角色:赋予 Provisioner 操作 Secret、PV、PVC 及存储类的权限) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole 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"] --- # ========================================== # 3. ClusterRoleBinding (集群角色绑定) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: csi-provisioner-role subjects: - kind: ServiceAccount name: csi-provisioner namespace: default roleRef: kind: ClusterRole name: external-provisioner-runner apiGroup: rbac.authorization.k8s.io --- # ========================================== # 4. Role (普通角色:用于当前命名空间下的领导者选举 Endpoints 操作) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: external-provisioner-cfg namespace: default rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "watch", "list", "delete", "update", "create"] --- # ========================================== # 5. RoleBinding (角色绑定) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: csi-provisioner-role-cfg namespace: default subjects: - kind: ServiceAccount name: csi-provisioner namespace: default roleRef: kind: Role name: external-provisioner-cfg apiGroup: rbac.authorization.k8s.io --- # ========================================== # 6. Service (服务定义) # ========================================== apiVersion: v1 kind: Service metadata: name: csi-hostpath-provisioner labels: app: csi-hostpath-provisioner spec: selector: app: csi-hostpath-provisioner ports: - name: dummy port: 12345 --- # ========================================== # 7. StatefulSet (有状态工作负载:运行 csi-provisioner 进程) # ========================================== apiVersion: apps/v1 kind: StatefulSet metadata: name: csi-hostpath-provisioner spec: serviceName: "csi-hostpath-provisioner" replicas: 1 selector: matchLabels: app: csi-hostpath-provisioner template: metadata: labels: app: csi-hostpath-provisioner spec: serviceAccountName: csi-provisioner # 使用上面创建的 ServiceAccount containers: - name: csi-provisioner image: quay.io/k8scsi/csi-provisioner:v1.0.1 imagePullPolicy: IfNotPresent args: - "--provisioner=csi-hostpath" # 指定供应器的名称 - "--csi-address=$(ADDRESS)" # 指定 CSI Socket 路径 - "--connection-timeout=15s" # 连接超时时间 env: - name: ADDRESS value: /csi/csi.sock volumeMounts: - name: socket-dir mountPath: /csi volumes: - name: socket-dir hostPath: path: /var/lib/kubelet/plugins/csi-hostpath type: DirectoryOrCreatecsi-hostpathplugin.yaml的内容如下:
# ========================================== # 1. ServiceAccount (服务账号) # ========================================== apiVersion: v1 kind: ServiceAccount metadata: name: csi-node-sa namespace: default # 关联的命名空间(如需更改,请同步替换其他资源中的 namespace) --- # ========================================== # 2. ClusterRole (集群角色:赋予节点端插件必要事件上报权限) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: driver-registrar-runner rules: - apiGroups: [""] resources: ["events"] verbs: ["get", "list", "watch", "create", "update", "patch"] # 注:旧版本若不使用 --kubelet-registration-path 参数,可能需要对 nodes 进行 patch, # 现代 CSI 架构推荐使用 registration 机制,故此处注释按需保留。 --- # ========================================== # 3. ClusterRoleBinding (集群角色绑定) # ========================================== apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: csi-driver-registrar-role subjects: - kind: ServiceAccount name: csi-node-sa namespace: default roleRef: kind: ClusterRole name: driver-registrar-runner apiGroup: rbac.authorization.k8s.io --- # ========================================== # 4. DaemonSet (守护进程集:在每个节点上部署 CSI 插件及 Registrar) # ========================================== apiVersion: apps/v1 kind: DaemonSet metadata: name: csi-hostpathplugin spec: selector: matchLabels: app: csi-hostpathplugin template: metadata: labels: app: csi-hostpathplugin spec: serviceAccountName: csi-node-sa hostNetwork: true containers: # 1. Driver Registrar 边车容器:负责向 kubelet 注册 CSI 插件 - name: driver-registrar image: quay.io/k8scsi/csi-node-driver-registrar:v1.0.1 imagePullPolicy: IfNotPresent args: - --v=5 - --csi-address=/csi/csi.sock - --kubelet-registration-path=/var/lib/kubelet/plugins/csi-hostpath/csi.sock env: - name: KUBE_NODE_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName volumeMounts: - mountPath: /csi name: socket-dir - mountPath: /registration name: registration-dir # 2. Hostpath 插件主容器 - name: hostpath image: quay.io/k8scsi/hostpathplugin:v1.0.1 imagePullPolicy: IfNotPresent args: - "--v=5" - "--endpoint=$(CSI_ENDPOINT)" - "--nodeid=$(KUBE_NODE_NAME)" env: - name: CSI_ENDPOINT value: unix:///csi/csi.sock - name: KUBE_NODE_NAME valueFrom: fieldRef: apiVersion: v1 fieldPath: spec.nodeName securityContext: privileged: true volumeMounts: - mountPath: /csi name: socket-dir - mountPath: /var/lib/kubelet/pods mountPropagation: Bidirectional name: mountpoint-dir volumes: - name: socket-dir hostPath: path: /var/lib/kubelet/plugins/csi-hostpath type: DirectoryOrCreate - name: mountpoint-dir hostPath: path: /var/lib/kubelet/pods type: DirectoryOrCreate - name: registration-dir hostPath: path: /var/lib/kubelet/plugins_registry type: Directory使用kubectl create命令完成创建:
# kubectl create -f csi-hostpath-attacher.yaml serviceaccount/csi-attacher created clusterrole.rbac.authorization.k8s.io/external-attacher-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-attacher-role created role.rbac.authorization.k8s.io/external-attacher-cfg created rolebinding.rbac.authorization.k8s.io/csi-attacher-role-cfg created service/csi-hostpath-attacher created statefulset.apps/csi-hostpath-attacher created # kubectl create -f csi-hostpath-provisioner.yaml serviceaccount/csi-provisioner created clusterrole.rbac.authorization.k8s.io/external-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-provisioner-role created role.rbac.authorization.k8s.io/external-provisioner-cfg created rolebinding.rbac.authorization.k8s.io/csi-provisioner-role-cfg created service/csi-hostpath-provisioner created statefulset.apps/csi-hostpath-provisioner created # kubectl create -f csi-hostpathplugin.yaml serviceaccount/csi-node-sa created clusterrole.rbac.authorization.k8s.io/driver-registrar-runner created clusterrolebinding.rbac.authorization.k8s.io/csi-driver-registrar-role created daemonset.apps/csi-hostpathplugin created确保3个Pod都正常运行:
# kubectl get pods NAME READY STATUS RESTARTS AGE csi-hostpath-attacher-0 1/1 Running 1 4m41s csi-hostpath-provisioner-0 1/1 Running 0 84s csi-hostpathplugin-t6qzs 2/2 Running 0 39s至此就完成了CSI存储插件的部署。
-
(4)应用容器使用CSI存储。应用程序如果希望使用CSI存储插件提供的存储服务,则仍然使用Kubernetes动态存储管理机制。首先通过创建StorageClass和PVC为应用容器准备存储资源,然后容器就可以挂载PVC到容器内的目录进行使用了。
创建一个StorageClass,provisioner为CSI存储插件的类型,在本例中为csi-hostpath:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: csi-hostpath-sc # StorageClass 的名称 provisioner: csi-hostpath # 动态卷供应商,指定使用 CSI HostPath 插件 reclaimPolicy: Delete # 回收策略:当关联的 PVC 被删除时,底层的存储卷也将被自动删除 (Delete) volumeBindingMode: Immediate # 卷绑定模式:立即绑定(PVC 创建后会立即触发 PV 的动态供应和绑定,而不等待 Pod 调度)kubectl create -f csi-storageclass.yaml创建一个PVC,引用刚刚创建的StorageClass,申请存储空间为1GiB:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: csi-pvc # 持久卷声明 (PVC) 的名称 spec: accessModes: - ReadWriteOnce # 访问模式:单节点读写挂载 resources: requests: storage: 1Gi # 请求分配 1 吉字节 (Gi) 的存储容量 storageClassName: csi-hostpath-sc # 显式指定使用前面创建的 csi-hostpath-sc 存储类进行动态供应kubectl create -f csi-pvc.yaml查看PVC和系统自动创建的PV,状态为Bound,说明创建成功:
# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE csi-pvc Bound pvc-f8923093-3e25-11e9-a5fa-000c29069202 1Gi RWO csi-hostpath-sc 40s # kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-f8923093-3e25-11e9-a5fa-000c29069202 1Gi RWO Delete Bound default/csi-pvc csi-hostpath-sc 42s最后,在应用容器的配置中使用该PVC:
apiVersion: v1 kind: Pod metadata: name: my-csi-app # 测试应用的 Pod 名称 spec: containers: - name: my-csi-app # 容器名称 image: busybox # 使用轻量级的 busybox 镜像 imagePullPolicy: IfNotPresent command: [ "sleep", "1000000" ] # 保持容器持续运行,以便挂载和测试存储卷 volumeMounts: - mountPath: "/data" # 将存储卷挂载到容器内的 /data 目录 name: my-csi-volume volumes: - name: my-csi-volume persistentVolumeClaim: claimName: csi-pvc # 引用前面创建的 csi-pvc 持久卷声明kubectl create -f csi-app.yaml kubectl get pods在Pod创建成功之后,应用容器中的/data目录使用的就是CSI存储插件提供的存储。
我们通过kubelet的日志可以查看到Volume挂载的详细过程:
I0304 10:39:27.408018 29488 operation_generator.go:1196] Controller attach succeeded for volume"pvc-f8923093-3e25-11e9-a5fa-000c29069202" (UniqueName: "kubernetes.io/csi/csi-hostpath^f89c8e8e-3e25-11e9-8d66-000c29069202") pod "my-csi-app" (UID: "b624c688-3e26-11e9-a5fa-000c29069202") device path: "csi-43a8c0897d21520e942e9ceea0b1ddac36c8c462d726780bed5f50841f0b0871" I0304 10:39:27.501816 29488 operation_generator.go:501] MountVolume.WaitForAttach entering for volume "pvc-f8923093-3e25-11e9-a5fa-000c29069202" (UniqueName: "kubernetes.io/csi/csi-hostpath^f89c8e8e-3e25-11e9-8d66-000c29069202") pod "my-csi-app" (UID: "b624c688-3e26-11e9-a5fa-000c29069202") DevicePath "csi-43a8c0897d21520e942e9ceea0b1ddac36c8c462d726780bed5f50841f0b0871" I0304 10:39:27.504542 29488 operation_generator.go:510] MountVolume.WaitForAttach succeeded for volume "pvc-f8923093-3e25-11e9-a5fa-000c29069202" (UniqueName: "kubernetes.io/csi/csi-hostpath^f89c8e8e-3e25-11e9-8d66-000c29069202") pod "my-csi-app" (UID: "b624c688-3e26-11e9-a5fa-000c29069202") DevicePath "csi-43a8c0897d21520e942e9ceea0b1ddac36c8c462d726780bed5f50841f0b0871" I0304 10:39:27.506867 29488 csi_attacher.go:360] kubernetes.io/csi: attacher.MountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice... I0304 10:39:27.506894 29488 operation_generator.go:531] MountVolume.MountDevice succeeded for volume "pvc-f8923093-3e25-11e9-a5fa-000c29069202" (UniqueName: "kubernetes.io/csi/csi-hostpath^f89c8e8e-3e25-11e9-8d66-000c29069202") pod "my-csi-app" (UID: "b624c688-3e26-11e9-a5fa-000c29069202") device mount path "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-f8923093-3e25-11e9-a5fa-000c2 9069202/globalmount"
7.4、CSI的发展
目前可用于生产环境的CSI插件如表所示。


实验性的CSI插件如表所示。

每种CSI存储插件都提供了容器镜像,与external-attacher、external-provisioner、node-driver-registrar等sidecar辅助容器共同完成存储插件系统的部署,每个插件的部署配置详见官网https://kubernetes-csi.github.io/docs/drivers.html中的链接。
Kubernetes从1.12版本开始引入存储卷快照(VolumeSnapshots)功能,通过新的CRD自定义资源对象 VolumeSnapshotContent、VolumeSnapshot和VolumeSnapshotClass进行管理。VolumeSnapshotContent定义从当前PV创建的快照,类似于一个新的PV;VolumeSnapshot定义需要绑定某个快照的请求,类似于PVC的定义;VolumeSnapshotClass用于屏蔽VolumeSnapshotContent的细节,类似于StorageClass的功能。
下面是一个VolumeSnapshotContent的例子:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
name: new-snapshot-content-test # VolumeSnapshotContent 的名称
spec:
volumeSnapshotClassName: csi-hostpath-snapclass # 关联的快照类名称(注意:v1 版本字段名变更为 volumeSnapshotClassName)
driver: csi-hostpath # 提供底层卷快照功能的 CSI 驱动名称
deletionPolicy: Delete # 删除策略:当 VolumeSnapshotContent 被删除时,底层实际的快照数据也将被删除 (Delete)
volumeSnapshotRef:
name: new-snapshot-test # 关联的命名空间级 VolumeSnapshot 对象名称
namespace: default # VolumeSnapshot 所在的命名空间
source:
volumeHandle: 7bdd0de3-aaeb-11e8-9aae-0242ac110002 # 底层存储卷的唯一标识符 (Volume Handle),用于指向被备份的源卷
下面是一个VolumeSnapshot的例子:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: new-snapshot-test # VolumeSnapshot 的名称,供用户层引用
spec:
volumeSnapshotClassName: csi-hostpath-snapclass # 关联的卷快照类名称(注意:v1 版本字段名变更为 volumeSnapshotClassName)
source:
apiGroup: "" # API 组为空字符串,代表核心资源
kind: PersistentVolumeClaim # 源资源类型:持久卷声明 (PVC)
name: pvc-test # 需要被备份/快照的目标 PVC 名称(例如需要备份的数据库 PVC)
后续要进一步完善的工作如下。
- (1)将以下Alpha版功能更新到Beta版:
- Raw Block类型的Volumes;
- 拓扑感知,Kubernetes理解和影响CSI卷的配置位置(如zone、region等)的能力;
- 完善基于CRD的扩展功能(例如Skip attach、Podinfo on mount等)。
- (2)完善对本地短暂卷(Local Ephemeral Volume)的支持。
- ** (3)将Kubernetes“in-tree”存储卷插件迁移到CSI。**
1325



被折叠的 条评论
为什么被折叠?



