目录
1、Kubernetes StorageClass 介绍
Kubernetes 集群存储 PV 支持 Static 静态配置以及 Dynamic 动态配置,动态卷配置 (Dynamic provisioning) 可以根据需要动态的创建存储卷。我们知道,之前的静态配置方式,集群管理员必须手动调用云/存储服务提供商的接口来配置新的固定大小的 Image 存储卷,然后创建 PV 对象以在 Kubernetes 中请求分配使用它们。通过动态卷配置,能自动化完成以上两步骤,它无须集群管理员预先配置存储资源,而是使用 StorageClass 对象指定的供应商来动态配置存储资源。
2、环境、软件准备
本次演示环境,我是在虚拟机 Linux Centos7 上操作,通过虚拟机完成 Ceph 存储集群搭建以及 Kubernetes 集群的搭建,以下是安装的软件及版本:
注意:这里我们着重描述一下 Kubernetes 集群如何使用动态配置使用 RBD 来实现持久化存储,所以需要提前搭建好 Kubernetes 集群和 Ceph 存储集群,具体搭建过程可参考之前文章 国内使用 kubeadm 在 Centos 7 搭建 Kubernetes 集群 和 初试 Centos7 上 Ceph 存储集群搭建,这里就不在详细讲解了。同时由于本机内存限制,共开启了 3 个虚拟机节点,每个节点既是 Ceph 集群节点又是 Kubernetes 集群节点,所以功能节点图如下:
3、Kubernetes 使用 RBD 作为 StorageClass
StorageClass 对象支持多种类型的存储卷插件来提供 PV,从 Storage Classes 官方文档 provisioner 部分可以看到,它目前支持很多种存储卷类型,其中就有我们熟悉的 Ceph RBD 类型。
当然除了上述 k8s 内部支持类别,如果我们需要使用其他类型卷插件,例如 NFS、CephFS 等第三方熟知的类型,可以去 kubernetes-incubator/external-storage 这个 GitHub 仓库,这里有更多扩展存储卷插件支持,下边我们在使用 RBD 作为 StorageClass 的时候也会演示到。
正式开始之前要提一下,通过前边两篇文章 初试 Kubernetes 集群使用 Ceph RBD 块存储 和 初试 Kubernetes 集群使用 CephFS 文件存储 的介绍,我们知道,k8s 不支持跨节点挂载同一 Ceph RBD,支持跨节点挂载 CephFS,所以这里 k8s 集群只包含 admin 和 node0 两个节点,也就是让所有的任务都调度到 node0 上执行,来保证针对 RBD 的操作在同一节点上。同时既然是动态配置存储资源,意思就是我们不需要提前创建好指定大小的 Image 了,而是动态创建它,所以这里只需要参照 初试 Centos7 上 Ceph 存储集群搭建 搭建好 Ceph 存储集群即可,不需要进行 RBD 操作。
3.1 创建 ceph-secret-admin
我们知道 Ceph 存储集群默认是开启了 cephx 认证的,所以我们可以创建一个名称为 ceph-secret-admin 的 secret 对象,用于 k8s volume 插件通过 cephx 认证访问 ceph 存储集群。
$ ceph auth get-key client.admin |base64
QVFDUWFsMWFuUWlhRHhBQXpFMGpxMSsybEFjdHdSZ3J3M082YWc9PQ==
首先获取并 base64 生成一下 k8s secret 认证 key,然后创建 ceph-secret-admin.yaml 文件,key 值替换一下。
$ vim ceph-secret-admin.yaml
apiVersion: v1
kind: Secret
metadata:
name: ceph-secret-admin
type: "kubernetes.io/rbd"
data:
key: QVFDUWFsMWFuUWlhRHhBQXpFMGpxMSsybEFjdHdSZ3J3M082YWc9PQ==
创建名称为 ceph-secret-admin 的 Secret。
$ kubectl create -f ceph-secret-admin.yaml
secret "ceph-secret-admin" created
$ kubectl get secret
NAME TYPE DATA AGE
ceph-secret-admin kubernetes.io/rbd 1 16s
default-token-630xt kubernetes.io/service-account-token 3 15m
3.2 创建 rbd-storage-class
通过 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: kube
adminSecretName: ceph-secret
adminSecretNamespace: kube-system
pool: kube
userId: kube
userSecretName: ceph-secret-user
fsType: ext4
imageFormat: "2"
imageFeatures: "layering"
这里每个字段我就不一一解释了,其中有几个字段要说明一下。
namespace: other-namespace
。参照上边示例,我们创建一个 rbd-storage-class.yaml 文件如下。
$ vim rbd-storage-class.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: rbd
provisioner: kubernetes.io/rbd
parameters:
monitors: 10.222.78.12:6789
adminId: admin
adminSecretName: ceph-secret-admin
adminSecretNamespace: default
pool: rbd
userId: admin
userSecretName: ceph-secret-admin
然后我们创建一下名称为 rbd 类型为 rbd 的 storage-class 看下。
$ kubectl create -f rbd-storage-class.yaml
storageclass "rbd" created
$ kubectl get storageclass
NAME TYPE
rbd kubernetes.io/rbd
3.2 创建 rbd-dyn-pv-claim
好了,现在 storageClass 已经创建好了,这里跟之前的区别就是,不需要创建 PV 和提前创建好指定大小的 Image,只需要创建 PVC 时请求指定存储大小就行,k8s 会根据请求存储大小和类型动态创建并分配,是不是很方便。那么我们就来创建一个 PVC 申请 1G 存储空间,新建 rbd-dyn-pv-claim.yaml 文件如下。
$ vim rbd-dyn-pv-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ceph-rbd-dyn-pv-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: rbd
resources:
requests:
storage: 1Gi
注意:这里要使用 storageClassName: rbd
指明我们使用的 storageClass 为前面创建的 rbd。accessModes
指定模型为 ReadWriteOnce
rbd 只支持 ReadWriteOnce 和 ReadOnlyMany,因为下边有写入操作,所以这里使用 ReadWriteOnce
即可。
然后我们创建一个该 PVC,看下能否创建成功吧!
$ kubectl create -f rbd-dyn-pv-claim.yaml
persistentvolumeclaim "ceph-rbd-dyn-pv-claim" created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
ceph-rbd-dyn-pv-claim Pending rbd 29s
不过很遗憾,状态为 Pending 并没有创建成功,这是什么原因呢?我们查看下该 PVC 详细出错信息吧!
$ kubectl describe pvc/ceph-rbd-dyn-pv-claim
Name: ceph-rbd-dyn-pv-claim
Namespace: default
StorageClass: rbd
Status: Pending
Volume:
Labels: <none>
Annotations: volume.beta.kubernetes.io/storage-provisioner=kubernetes.io/rbd
Capacity:
Access Modes:
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
46s 6s 4 persistentvolume-controller Warning ProvisioningFailed Failed to provision volume with StorageClass "rbd": failed to create rbd image: executable file not found in $PATH, command output:
从打印信息中可以看到如下出错信息 failed to create rbd image: executable file not found in $PATH
,提示创建 rbd image 失败,因为在 $PATH
中没找到可执行文件。于是在 kubernetes github issues 中搜索了一下相关信息,通过 issues/38923 这个 issues 中的描述,大概了解到是需要安装 ceph-common 工具插件来操作 Ceph,上边报错应该就是找不到该插件导致的。里面也给出了一个解决办法,那就是添加 ceph-common 到 hyperkube image 中,具体就是构建一个新的安装了 ceph-common 的同名镜像 hyperkube-amd64 替换官方镜像即可。
$ vim Dockerfile
FROM gcr.io/google_containers/hyperkube-amd64:v1.2.1
RUN curl https://raw.githubusercontent.com/ceph/ceph/master/keys/release.asc | apt-key add - && \
echo deb http://download.ceph.com/debian-hammer/ jessie main | tee /etc/apt/sources.list.d/ceph.list && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -q -y ceph-common && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
$ docker build -t custom/hyperkube-amd64:v1.2.1 .
不过,因为国内网络的问题,没法拉取 gcr.io/google_containers/hyperkube-amd64:v1.2.1
镜像,所以我采用另一种方式,就是使用上边提到的 扩展存储卷插件 来帮我们完成这一步。
$ cd /home/wanyang3/k8s
$ git clone https://github.com/kubernetes-incubator/external-storage.git
$ tree external-storage/ceph/rbd/deploy/
├── README.md
├── non-rbac
│ └── deployment.yaml
└── rbac
├── clusterrole.yaml
├── clusterrolebinding.yaml
├── deployment.yaml
└── serviceaccount.yaml
简单说一下,这里提供 rbac 和 no-rbac 两种方式,这里因为我们搭建的 k8s 集群时开启了 rbac 认证的,所以这里采用 rbac 方式来创建该 deployment。ClusterRoleBinding 默认绑定 namespace: default
,如果要修改为其他 namespace,对应的 storageClass 中的adminSecretNamespace 也需要对应修改下。下边我们就把 rbac 文件夹下相关的 ClusterRole、ClusterRoleBinding、ServiceAccount、Deployment 创建一下吧!
$ kubectl apply -f rbac/
clusterrole "rbd-provisioner" created
clusterrolebinding "rbd-provisioner" created
deployment "rbd-provisioner" created
serviceaccount "rbd-provisioner" create
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
rbd-provisioner-687025274-1l6h5 1/1 Running 0 9s
我们看到该 rbd-provisioner 的 Deployment 已经成功启动起来了,接下来,最重要的一步就是修改上边 rbd-storage-class.yaml 文件将 provisioner: kubernetes.io/rbd
修改为 provisioner: ceph.com/rbd
,意思就是不使用 k8s 内部提供的 rbd 存储类型,而是使用我们刚创建的扩展 rbd 存储。
$ vim rbd-storage-class.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: rbd
provisioner: ceph.com/rbd
parameters:
monitors: 10.222.78.12:6789
adminId: admin
adminSecretName: ceph-secret-admin
adminSecretNamespace: default
pool: rbd
userId: admin
userSecretName: ceph-secret-admin
OK 我们来重新创建一下名称为 rbd 类型为 ceph.com/rbd 的 storage-class 看下。
$ kubectl apply -f rbd-storage-class.yaml
$ kubectl get sc
NAME TYPE
rbd ceph.com/rbd
创建成功,rbd-dyn-pv-claim.yaml 文件不用做任何修改,再次重新创建一下 rbd-dyn-pv-claim 看下这次能够正常创建吧!
$ kubectl create -f rbd-dyn-pv-claim.yaml
persistentvolumeclaim "ceph-rbd-dyn-pv-claim" created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
ceph-rbd-dyn-pv-claim Bound pvc-cd63e53a-fa6f-11e7-a8e8-080027ee5979 1Gi RWO rbd 7s
这次创建成功啦!可以看到 STORAGECLASS
字段显示的 rbd 即为上边创建的 rbd storageClass。接下来,我们来创建一个挂载该 PVC 的 Pod,看能否挂载成功吧!
3.3 创建 rbd-dyn-pvc-pod
最后我们创建一个挂载该 RBD 的 Pod 了,这里我还是使用 busybox 容器测试吧!新建 Pod 文件 rbd-dyn-pvc-pod1.yaml 如下。
$ vim rbd-dyn-pvc-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: rbd-dyn-pvc-pod
name: ceph-rbd-dyn-pv-pod1
spec:
containers:
- name: ceph-rbd-dyn-pv-busybox1
image: busybox
command: ["sleep", "60000"]
volumeMounts:
- name: ceph-dyn-rbd-vol1
mountPath: /mnt/ceph-dyn-rbd-pvc/busybox
readOnly: false
volumes:
- name: ceph-dyn-rbd-vol1
persistentVolumeClaim:
claimName: ceph-rbd-dyn-pv-claim
从文件可以看到,我们要将上边创建的 ceph-rbd-dyn-pv-claim 请求的资源挂载到容器的 /mnt/ceph-dyn-rbd-pvc/busybox 目录。接下来创建一下该 Pod,看是否能够正常运行吧!
$ kubectl create -f rbd-dyn-pvc-pod1.yaml
pod "ceph-rbd-dyn-pv-pod1" created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
ceph-rbd-dyn-pv-pod1 1/1 Running 0 25s
rbd-provisioner-687025274-1l6h5 1/1 Running 0 7m
创建成功,接下来,我们去 node0 节点验证一下容器内是否正确挂载了该 rbd 到指定路径吧!
# node0 上操作
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1095a0a49cc6 docker.io/busybox@sha256:263477f49bb8d4d3d33c16a09671711c7eb5fac4bdf777f776ca16b931834ebf "sleep 60000" About a minute ago Up About a minute k8s_ceph-rbd-dyn-pv-busybox1_ceph-rbd-dyn-pv-pod1_default_12923bed-fa70-11e7-a8e8-080027ee5979_0
# 查看容器挂载信息
$ docker inspect 1095a0a49cc6
"Mounts": [
{
"Source": "/var/lib/kubelet/pods/12923bed-fa70-11e7-a8e8-080027ee5979/volumes/kubernetes.io~rbd/pvc-cd63e53a-fa6f-11e7-a8e8-080027ee5979",
"Destination": "/mnt/ceph-dyn-rbd-pvc/busybox",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Source": "/var/lib/kubelet/pods/12923bed-fa70-11e7-a8e8-080027ee5979/volumes/kubernetes.io~secret/default-token-630xt",
"Destination": "/var/run/secrets/kubernetes.io/serviceaccount",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
},
{
"Source": "/var/lib/kubelet/pods/12923bed-fa70-11e7-a8e8-080027ee5979/etc-hosts",
"Destination": "/etc/hosts",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Source": "/var/lib/kubelet/pods/12923bed-fa70-11e7-a8e8-080027ee5979/containers/ceph-rbd-dyn-pv-busybox1/520bdd35",
"Destination": "/dev/termination-log",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
# 进入容器内,查看挂载详情以及测试生成文件
$ docker exec -it 1095a0a49cc6 /bin/sh
/ # df -h
Filesystem Size Used Available Use% Mounted on
10.0G 34.1M 10.0G 0% /
/dev/rbd0 975.9M 2.5M 906.2M 0% /mnt/ceph-dyn-rbd-pvc/busybox
...
/ # cd /mnt/ceph-dyn-rbd-pvc/busybox
/mnt/ceph-dyn-rbd-pvc/busybox # dd if=/dev/zero of=test-rbd-dyn-1 bs=500M count=1
1+0 records in
1+0 records out
524288000 bytes (500.0MB) copied, 5.256829 seconds, 95.1MB/s
/mnt/ceph-dyn-rbd-pvc/busybox # df -h
Filesystem Size Used Available Use% Mounted on
10.0G 34.1M 10.0G 0% /
/dev/rbd0 975.9M 502.5M 406.2M 55% /mnt/ceph-dyn-rbd-pvc/busybox
...
妥妥没问题的!我们看到 k8s 动态的创建了 1G 大小的 rbd Image 并挂载到容器指定路径下。这里我们可以使用 ceph rbd 命令行查看下。
# rbd list
kubernetes-dynamic-pvc-cd6e6d18-fa6f-11e7-bd8e-0a580a600102
# rbd info kubernetes-dynamic-pvc-cd6e6d18-fa6f-11e7-bd8e-0a580a600102
rbd image 'kubernetes-dynamic-pvc-cd6e6d18-fa6f-11e7-bd8e-0a580a600102':
size 1024 MB in 256 objects
order 22 (4096 kB objects)
block_name_prefix: rb.0.1023.2ae8944a
format: 1
我们会发现,没有提前创建好 1G 大小的 rbd image,只是创建 PVC 时申请了 1G 存储,k8s 就自动创建好了指定大小的 Image 并挂载到容器内部,太方便了有木有! 同时,我们看到默认使用的 format 为 1,这里也可以指定格式为 2,可以在 rbd-storage-class.yaml 中指定 imageFormat: "2"
,同时还可以指定 imageFeatures: layering
等等。
为了更好的演示 k8s 的动态卷配置,我们在创建一个 PVC 申请 5G 存储,并挂载到到一个新的 pod 的指定路径上试试。注意: 这里我们就不用再创建 StorageClass 了,只需要创建 PVC 和 Pod 即可,要是像之前使用静态配置的话,我们既要创建 Image 又要创建 PV,是不是很麻烦。
创建 rbd-dyn-pv-claim2 的 PVC 申请 5G 存储空间。
$ vim rbd-dyn-pv-claim2.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ceph-rbd-dyn-pv-claim2
spec:
accessModes:
- ReadWriteOnce
storageClassName: rbd
resources:
requests:
storage: 5Gi
$ kubectl create -f rbd-dyn-pv-claim2.yaml
persistentvolumeclaim "ceph-rbd-dyn-pv-claim2" created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
ceph-rbd-dyn-pv-claim1 Bound pvc-cd63e53a-fa6f-11e7-a8e8-080027ee5979 1Gi RWO rbd 2h
ceph-rbd-dyn-pv-claim2 Bound pvc-1095b851-fa86-11e7-a8e8-080027ee5979 5Gi RWO rbd 6s
创建 rbd-dyn-pvc-pod2 的 Pod 挂载动态创建的 rbd 到指定目录 /mnt/ceph-dyn-rbd-pvc2/busybox。
$ vim rbd-dyn-pvc-pod2.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
test: rbd-dyn-pvc-pod
name: ceph-rbd-dyn-pv-pod2
spec:
containers:
- name: ceph-rbd-dyn-pv-busybox2
image: busybox
command: ["sleep", "60000"]
volumeMounts:
- name: ceph-dyn-rbd-vol2
mountPath: /mnt/ceph-dyn-rbd-pvc2/busybox
readOnly: false
volumes:
- name: ceph-dyn-rbd-vol2
persistentVolumeClaim:
claimName: ceph-rbd-dyn-pv-claim2
$ kubectl create -f rbd-dyn-pvc-pod2.yaml
pod "ceph-rbd-dyn-pv-pod2" created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
ceph-rbd-dyn-pv-pod1 1/1 Running 0 2h
ceph-rbd-dyn-pv-pod2 1/1 Running 0 16s
rbd-provisioner-687025274-1l6h5 1/1 Running 0 2h
去 node0 验证一下本次创建的 Pod2 是否正确挂载了动态创建的 rbd。
# node0 上执行
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
32c632fedff6 docker.io/busybox@sha256:263477f49bb8d4d3d33c16a09671711c7eb5fac4bdf777f776ca16b931834ebf "sleep 60000" About a minute ago Up About a minute k8s_ceph-rbd-dyn-pv-busybox2_ceph-rbd-dyn-pv-pod2_default_282aea2b-fa86-11e7-a8e8-080027ee5979_0
$ docker exec -it 32c632fedff6 df -h
Filesystem Size Used Available Use% Mounted on
10.0G 34.1M 10.0G 0% /
/dev/rbd1 4.8G 20.0M 4.5G 0% /mnt/ceph-dyn-rbd-pvc2/busybox
...
可以看到已经挂载了动态申请的 5G 存储空间,使用 ceph rbd 命令查看下创建的 Image 信息吧!
$ rbd list
kubernetes-dynamic-pvc-1c2aee43-fa86-11e7-bd8e-0a580a600102
kubernetes-dynamic-pvc-cd6e6d18-fa6f-11e7-bd8e-0a580a600102
$ rbd info kubernetes-dynamic-pvc-1c2aee43-fa86-11e7-bd8e-0a580a600102
rbd image 'kubernetes-dynamic-pvc-1c2aee43-fa86-11e7-bd8e-0a580a600102':
size 5120 MB in 1280 objects
order 22 (4096 kB objects)
block_name_prefix: rb.0.1028.74b0dc51
format: 1
最后,要提一下的是,如果我们使用动态配置的卷,则默认的回收策略为 “删除”。这意味着,在默认的情况下,当 PVC 被删除时,基础的 PV 和对应的存储也会被删除。如果需要保留存储在卷上的数据,则必须在 PV 被设置之后将回收策略从 delete
更改为 retain
。可以通过修改 PV 对象中的 persistentVolumeReclaimPolicy
字段的值来修改 PV 的回收策略。
参考资料