初试 Kubernetes 动态卷配置使用 RBD 作为 StorageClass

目录

  • Kubernetes StorageClass 介绍
  • 环境、软件准备
  • Kubernetes 使用 RBD 作为 StorageClass

1、Kubernetes StorageClass 介绍

Kubernetes 集群存储 PV 支持 Static 静态配置以及 Dynamic 动态配置,动态卷配置 (Dynamic provisioning) 可以根据需要动态的创建存储卷。我们知道,之前的静态配置方式,集群管理员必须手动调用云/存储服务提供商的接口来配置新的固定大小的 Image 存储卷,然后创建 PV 对象以在 Kubernetes 中请求分配使用它们。通过动态卷配置,能自动化完成以上两步骤,它无须集群管理员预先配置存储资源,而是使用 StorageClass 对象指定的供应商来动态配置存储资源。

2、环境、软件准备

本次演示环境,我是在虚拟机 Linux Centos7 上操作,通过虚拟机完成 Ceph 存储集群搭建以及 Kubernetes 集群的搭建,以下是安装的软件及版本:

  1. Centos:release 7.4.1708 (Core)
  2. Ceph:jewel-10.2.10
  3. Kubernetes:v1.6.2
  4. Docker:v1.12.6

注意:这里我们着重描述一下 Kubernetes 集群如何使用动态配置使用 RBD 来实现持久化存储,所以需要提前搭建好 Kubernetes 集群和 Ceph 存储集群,具体搭建过程可参考之前文章 国内使用 kubeadm 在 Centos 7 搭建 Kubernetes 集群初试 Centos7 上 Ceph 存储集群搭建,这里就不在详细讲解了。同时由于本机内存限制,共开启了 3 个虚拟机节点,每个节点既是 Ceph 集群节点又是 Kubernetes 集群节点,所以功能节点图如下:

这里写图片描述

3、Kubernetes 使用 RBD 作为 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,所以这里 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"

这里每个字段我就不一一解释了,其中有几个字段要说明一下。

  • provisioner 该字段指定使用存储卷类型为 kubernetes.io/rbd,注意 kubernetes.io/ 开头为 k8s 内部支持的存储提供者,不同的存储卷提供者类型这里要修改成对应的值。
  • adminId | userId 这里需要指定两种 Ceph 角色 admin 和其他 user,admin 角色默认已经有了,其他 user 可以去 Ceph 集群创建一个并赋对应权限值,如果不创建,也可以都指定为 admin。
  • adminSecretName 为上边创建的 Ceph 管理员 admin 使用的 ceph-secret-admin。
  • adminSecretNamespace 管理员 secret 使用的命名空间,默认 default,如果修改为其他的话,需要修改 ceph-secret-admin.yaml 增加 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 的回收策略。

参考资料

猜你喜欢

转载自blog.csdn.net/aixiaoyang168/article/details/79120095