Kubernetes 集群存储 PV 支持 Static 静态配置以及 Dynamic 动态配置,动态卷配置 (Dynamic provisioning) 可以根据需要动态的创建存储卷。静态配置方式,集群管理员必须手动调用云/存储服务提供商的接口来配置新的固定大小的 Image 存储卷,然后创建 PV 对象以在 Kubernetes 中请求分配使用它们。通过动态卷配置,能自动化完成以上两步骤,它无须集群管理员预先配置存储资源,而是使用 StorageClass 对象指定的供应商来动态配置存储资源。
StorageClass 对象支持多种类型的存储卷插件来提供 PV,从 Storage Classes 官方文档 provisioner 部分可以看到,它目前支持很多种存储卷类型,其中就有我们熟悉的 Ceph RBD 类型。
AWSElasticBlockStore
AzureFile
AzureDisk
Cinder
Flocker
GCEPersistentDisk
Glusterfs
PhotonPersistentDisk
Quobyte
RBD
VsphereVolume
PortworxVolume
ScaleIO
StorageOS
当然除了上述 k8s 内部支持类别,如果我们需要使用其他类型卷插件,例如 NFS、CephFS 等第三方熟知的类型,可以去 kubernetes-incubator/external-storage 这个 GitHub 仓库,这里有更多扩展存储卷插件支持,下边我们在使用 RBD 作为 StorageClass 的时候也会演示到。
正式开始之前要提一下,通过前边两篇文章 初试 Kubernetes 集群使用 Ceph RBD 块存储 和 初试 Kubernetes 集群使用 CephFS 文件存储 的介绍,我们知道,k8s 不支持跨节点挂载同一 Ceph RBD,支持跨节点挂载 CephFS,让所有的任务都调度到指定node上执行,来保证针对 RBD 的操作在同一节点上。同时既然是动态配置存储资源,意思就是我们不需要提前创建好指定大小的 Image 了,而是动态创建它,所以这里只需要参照 初试 Centos7 上 Ceph 存储集群搭建搭建好 Ceph 存储集群即可,不需要进行 RBD 操作。
我们知道 Ceph 存储集群默认是开启了 cephx 认证的,所以我们可以创建一个名称为 ceph-secret-admin 的 secret 对象,用于 k8s volume 插件通过 cephx 认证访问 ceph 存储集群。首先获取并 base64 生成一下 k8s secret 认证 key,然后创建 ceph-secret-admin.yaml 文件,key 值替换一下。
获取并 base64 生成 k8s secret 认证 key
$ ceph auth get-key client.admin | base64
QVFCS3FYSmFRa05wSEJBQWxIRkgrR1NMQ1B3TzNXS2V2YUlMVkE9PQ==
创建名称为 ceph-secret-admin 的 Secret
ceph-secret-admin.yaml文件如下:
apiVersion: v1
kind: Secret
metadata:
name: ceph-secret-admin
namespace: kube-system
type: "kubernetes.io/rbd"
data:
key: QVFCS3FYSmFRa05wSEJBQWxIRkgrR1NMQ1B3TzNXS2V2YUlMVkE9PQ==
通过 StorageClass RBD Config Example 官方示例代码,我们可以看到如下信息。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: fast
provisioner: kubernetes.io/rbd
parameters:
monitors: 10.16.153.105:6789
adminId: admin
adminSecretName: ceph-secret
adminSecretNamespace: kube-system
pool: kube
userId: kube
userSecretName: ceph-secret-user
fsType: ext4
imageFormat: "2"
imageFeatures: "layering"
这里每个字段我就不一一解释了,其中有几个字段要说明一下:
--image-format format-id
,format-id取值为1或2,默认为 2。 参照上边示例,我们创建一个 rbd-storage-class.yaml 文件如下。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rbd
provisioner: kubernetes.io/rbd
parameters:
monitors: 10.142.21.21:6789,10.142.21.22:6789,10.142.21.23:6789
adminId: admin
adminSecretName: ceph-secret-admin
adminSecretNamespace: kube-system
pool: rbd
userId: admin
userSecretName: ceph-secret-admin
fsType: ext4
imageFormat: "1"
说明:
--image-feature
,所以必须将imageFormat设为1,并且不设置imageFeatures参数。相关issue见: 然后我们创建一下名称为 rbd 类型为 rbd 的 storage-class 看下:
$ kubectl create -f rbd-storage-class.yaml
storageclass "rbd" created
$ kubectl get storageclass
NAME TYPE
rbd kubernetes.io/rbd
现在 storageClass 已经创建好了,这里跟之前的区别就是,不需要创建 PV 和提前创建好指定大小的 Image,只需要创建 PVC 时请求指定存储大小就行,k8s 会根据请求存储大小和类型动态创建并分配,是不是很方便。那么我们就来创建一个 PVC 申请 1G 存储空间,新建 rbd-pvc.yaml 文件如下。
$ vim rbd-pv.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: rbd-pvc1
namespace: kube-system
spec:
storageClassName: rbd
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
注意:
这里要使用 storageClassName: rbd 指明我们使用的 storageClass 为前面创建的 rbd。accessModes 指定模型为 ReadWriteOnce rbd 只支持 ReadWriteOnce 和 ReadOnlyMany,因为下边有写入操作,所以这里使用 ReadWriteOnce 即可。
然后创建一个PVC:
$ kubectl create -f rbd-pvc.yaml
persistentvolumeclaim "rbd-pvc1" created
$ kubectl get pvc -n kube-system
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
rbd-pvc1 Bound pvc-b9ecad19-07bc-11e8-a9ca-00505694eb6a 1Gi RWO rbd 2m
还自动创建出pv:
kubectl get pv
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-b9ecad19-07bc-11e8-a9ca-00505694eb6a 1Gi RWO Delete Bound kube-system/rbd-pvc1 rbd 5m
用rbd命令查看:
我们会发现,没有提前创建好 1G 大小的 rbd image,而是创建 PVC 时申请了 1G 存储,k8s 就自动创建好了指定大小的 Image。
此时相当于执行了rbd create
,还没有把这个image 映射到内核,并格式化为块设备。
这一步相当于在pod所在的节点执行了rbd map
、mkfs
、mount
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-use-rbd
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.11.4-alpine
imagePullPolicy: IfNotPresent
name: nginx-use-rbd
volumeMounts:
- mountPath: /test
name: rbd-pvc
volumes:
- name: rbd-pvc
persistentVolumeClaim:
claimName: rbd-pvc1
---
apiVersion: v1
kind: Service
metadata:
name: nginx-use-rbd
namespace: default
spec:
type: NodePort
ports:
- name: nginx-use-rbd
port: 80
targetPort: 80
nodePort: 30099
selector:
app: nginx
创建成功后在kubelet日志中看到如下内容:
df -h 可以看到盘挂载上来了:
dmesg | tail可以看到:
并挂载到容器内部,太方便了有木有! 同时,我们看到默认使用的 format 为 1,这里也可以指定格式为 2(需要高版本的ceph和rbd支持),然后可以在 rbd-storage-class.yaml 中指定 imageFormat: “2”,同时还可以指定 imageFeatures: layering 等等。
最后,要提一下的是,如果我们使用动态配置的卷,则默认的回收策略为 “删除”。这意味着,在默认的情况下,当 PVC 被删除时,基础的 PV 和对应的存储也会被删除。如果需要保留存储在卷上的数据,则必须在 PV 被设置之后将回收策略从 delete 更改为 retain。可以通过修改 PV 对象中的 persistentVolumeReclaimPolicy 字段的值来修改 PV 的回收策略。
ceph-rbd的ReadWriteOnce问题
ceph-rbd只支持RWO和RWM,也就是说写数据的话rbd只能挂给一个node,当一个应用只有一个pod实例的时候还好,当有多个pod实例的时候就要求这些实例一定要起在同一个节点上。实验发现,创建多实例应用不做啥约束的话,k8s的调度系统并不会根据这个应用所挂载的pvc类型是RWO而给你去把所有pod实例调度到同一个节点,最终结果甚至会一个pod也创建不出来,这个issue:https://github.com/kubernetes/kubernetes/issues/26567
1.http://blog.csdn.net/aixiaoyang168/article/details/79120095
2.https://kubernetes.io/docs/concepts/storage/storage-classes/#ceph-rbd