Kubernetes 挂 CephFS 的三种方式:直挂、静态 PV/PVC、动态分配

CephFS 是跟 POSIX 兼容的文件系统,直接提供 Ceph 存储集群上的文件访问,从 Jewel 版本(10.2.0)开始稳定。它要求至少跑一个元数据服务 MDS(ceph-mds),由 MDS 管理文件元数据并协调对存储的访问。做共享读写的数据卷时,文件存储比对象存储划算——对象存储要专门软件和大容量盘,数据量不大、只需要多 Pod 共享读写,就没必要上对象存储。

下面三种挂载方式按由浅到深排:Pod 里直接写 cephfs 卷、PV/PVC 静态绑定、cephfs-provisioner 动态分配。前提是先有可用的 Ceph 集群和 CephFS,并且 Kubernetes 所有节点(尤其 master)都装过客户端:

yum install -y ceph-common

还要把 ceph.conf 放到每个节点的 /etc/ceph 目录下。

准备 secret

key 要在 Ceph 集群的 mon 节点上取,并且必须做 base64 编码后写进 yaml:

# 方式1:
[cephu@ceph-node1 ~]$ sudo ceph auth get-key client.admin
AQB+dXxfhmh4LRAAM3Ow+ZdP64Py1N5ZvBgKiA==

[cephu@ceph-node1 ~]$ echo -n "AQB+dXxfhmh4LRAAM3Ow+ZdP64Py1N5ZvBgKiA=="| base64
QVFCK2RYeGZobWg0TFJBQU0zT3crWmRQNjRQeTFONVp2QmdLaUE9PQ==

# 方式2:
[cephu@ceph-node1 ~]$ sudo ceph auth get-key client.admin | base64
QVFCK2RYeGZobWg0TFJBQU0zT3crWmRQNjRQeTFONVp2QmdLaUE9PQ==
#首先创建一个名称空间
[root@k8s-master1 ~]# kubectl create ns dev
namespace/dev created

#然后创建secret类型的资源文件
[root@k8s-master1 ~]# vim ceph_secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: ceph-secret
  namespace: dev
type: kubernetes.io/rbd
data:
  key: QVFCK2RYeGZobWg0TFJBQU0zT3crWmRQNjRQeTFONVp2QmdLaUE9PQ==
[root@k8s-master1 ~]# kubectl apply -f ceph_secret.yaml 
secret/ceph-secret created

方式一:Pod 直接挂 cephfs 卷

和 emptyDir 的区别是数据会不会跟着 Pod 消失:emptyDir 在删 Pod 时清空,cephfs 卷只是被卸载,数据留在集群里,而且能多设备同时读写。

[root@k8s-master1 ~]# vim myapp-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: dev
spec:
  containers:
  - name: myapp
    image: busybox
    command: ["sleep", "60000"]
    volumeMounts:
    - mountPath: "/mnt/cephfs"
      name: cephfs	#指定使用的挂载卷名称
  volumes:
  - name: cephfs		#定义挂载卷名称
    cephfs:  #挂载类型为cephFS
      monitors:
      - 192.168.66.201:6789	#ceph集群mon节点
      - 192.168.66.202:6789
      - 192.168.66.203:6789
      user: admin	#ceph认证用户
      path: /
      secretRef:
        name: ceph-secret  #调用ceph认证secret
[root@k8s-master1 ~]# kubectl apply -f myapp-pod.yaml 
pod/myapp created 

[root@k8s-master1 ~]# kubectl get po -n dev
NAME    READY   STATUS    RESTARTS   AGE
myapp   1/1     Running   0          94s

方式二:静态 PV 与 PVC

PV 里除了 monitors、user、secretRef,还要指定 persistentVolumeReclaimPolicy: Delete 和 accessModes: ReadWriteMany:

[root@k8s-master1 ~]# vim myapp-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: cephfs-pv
  namespace: dev
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  cephfs:
    monitors:
    - 192.168.66.201:6789       #ceph集群mon节点地址和端口
    - 192.168.66.202:6789
    - 192.168.66.203:6789
    user: admin		#ceph的认证用户
    path: /
    secretRef:
      name: ceph-secret		#此处使用上面创建的secret资源中定义的secret名称
    readOnly: false
  persistentVolumeReclaimPolicy: Delete
[root@k8s-master1 ~]# kubectl apply -f myapp-pv.yaml 
persistentvolume "cephfs-pv" created

[root@k8s-master1 ~]# kubectl get pv cephfs-pv -n dev
k8s-cephfs01

PVC 的访问模式和存储容量必须和 PV 完全对得上,否则绑定不成功,一直 Pending:

[root@k8s-master1 ~]# vim myapp-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cephfs-pvc
  namespace: dev
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
[root@k8s-master1 ~]# kubectl apply -f myapp-pvc.yaml 
persistentvolumeclaim/cephfs-pvc created 

[root@k8s-master1 ~]# kubectl get pvc cephfs-pvc -n dev
k8s-cephfs02

Pod 引用 PVC:

[root@k8s-master1 ~]# vim myapp-pod-pv.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: cephfs-pv-myapp
  name: cephfs-pv-myapp
  namespace: dev
spec:
  containers:
  - name: cephfs-pv-myapp
    image: busybox
    command: ["sleep", "60000"]
    volumeMounts:
    - mountPath: "/mnt/cephfs"	#映射至容器中路径
      name: cephfs-myapp
      readOnly: false
  volumes:
  - name: cephfs-myapp
    persistentVolumeClaim:
      claimName: cephfs-pvc     #挂载的pvc卷名
[root@k8s-master1 ~]# kubectl apply -f myapp-pod-pv.yaml 
pod/cephfs-pv-myapp created

[root@k8s-master1 ~]# kubectl get po cephfs-pv-myapp -n dev
NAME              READY   STATUS    RESTARTS   AGE
cephfs-pv-myapp   1/1     Running   0          25s

[root@k8s-master1 ~]# kubectl describe po cephfs-pv-myapp -n dev
k8s-cephfs03

方式三:provisioner 动态分配

Kubernetes 官方没有为 cephfs 提供类似 Ceph RBD 的 storageClass 动态分配,社区用 cephfs-provisioner 补上这个功能。要注意这个仓库已经不再维护(说明见 https://github.com/kubernetes/org/issues/1563),试验环境可以用,生产建议换当前在维护的驱动。

先建 provisioner 用的 admin-secret,名称空间单独用 cephfs:

#在ceph-mon节点查看admin用户的权限
[cephu@ceph-node1 ~]$ sudo ceph auth get-key client.admin|base64
QVFCK2RYeGZobWg0TFJBQU0zT3crWmRQNjRQeTFONVp2QmdLaUE9PQ==

#在K8S-Master节点创建名称空间cephfs
[root@k8s-master1 ~]# kubectl create ns cephfs

#在K8S-Master1节点上创建cephfs所以定义文件存储目录
[root@k8s-master1 ~]# mkdir ceph-cephfs
[root@k8s-master1 ~]# cd ceph-cephfs/

#在K8S集群Master节点上创建secret资源并使用ceph中转换后key
[root@k8s-master1 ceph-cephfs]# vim admin-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: ceph-secret-admin
  namespace: cephfs
type: kubernetes.io/rbd
data:
  key: QVFCK2RYeGZobWg0TFJBQU0zT3crWmRQNjRQeTFONVp2QmdLaUE9PQ==

#执行secret定义文件
[root@k8s-master1 ceph-cephfs]# kubectl apply -f admin-secret.yaml 
secret/ceph-secret-admin created

#查看secret资源信息
[root@k8s-master1 ceph-cephfs]# kubectl get secret -n cephfs
NAME                   TYPE                                  DATA   AGE
ceph-secret-admin     kubernetes.io/rbd                     1      82s

再部署 provisioner,一份 yaml 里包含 ServiceAccount、ClusterRole、ClusterRoleBinding、Role、RoleBinding 和 Deployment,关键环境变量是 PROVISIONER_NAME=ceph.com/cephfs,command 是 /usr/local/bin/cephfs-provisioner:

[root@k8s-master1 ~]# vim storage-cephfs-provisioner.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cephfs-provisioner
  namespace: cephfs
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cephfs-provisioner
rules:
  - 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: ["create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "delete"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cephfs-provisioner
subjects:
  - kind: ServiceAccount
    name: cephfs-provisioner
    namespace: cephfs
roleRef:
  kind: ClusterRole
  name: cephfs-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cephfs-provisioner
  namespace: cephfs
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cephfs-provisioner
  namespace: cephfs
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: cephfs-provisioner
subjects:
- kind: ServiceAccount
  name: cephfs-provisioner
  namespace: cephfs

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cephfs-provisioner
  namespace: cephfs
spec:
  selector:
    matchLabels:
      app: cephfs-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: cephfs-provisioner
    spec:
      containers:
      - name: cephfs-provisioner
        image: "registry.cn-chengdu.aliyuncs.com/ives/cephfs-provisioner:latest"
        env:
        - name: PROVISIONER_NAME
          value: ceph.com/cephfs
        command:
        - "/usr/local/bin/cephfs-provisioner"
        args:
        - "-id=cephfs-provisioner-1"
      serviceAccount: cephfs-provisioner
[root@k8s-master1 ceph-cephfs]# kubectl get pods -n cephfs |grep cephfs
cephfs-provisioner-6d76ff6bd5-pcz5m   1/1     Running   0          103s

StorageClass 的 provisioner 要写成上面那个名字,monitors、adminId、adminSecretName、adminSecretNamespace 四项指向已有资源;资源名后面创建 PVC 时要与之绑定,别随手改名:

[root@k8s-master1 ceph-cephfs]# vim storageclass-cephfs.yaml 
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: cephfs	#注意:该名称后面创建pvc时要与之绑定
  namespace: cephfs
provisioner: ceph.com/cephfs	#使用外部cephfs
parameters:
    monitors: 192.168.66.201:6789,192.168.66.202:6789,192.168.66.203:6789	#ceph集群mon节点地址和端口
    adminId: admin  #cephfs认证用户
    adminSecretName: ceph-secret-admin  #secret认证资源NAME名称
    adminSecretNamespace: "cephfs"	#secret所在的名称空间名称
[root@k8s-master1 ceph-cephfs]#kubectl apply -f storageclass-cephfs.yaml
storageclass.storage.k8s.io/cephfs created

[root@k8s-master1 ceph-cephfs]# kubectl get sc
k8s-cephfs04

测试 PVC 用注解 volume.beta.kubernetes.io/storage-class: "cephfs" 触发分配。状态 Bound 表示成功;Pending 或 Failed 就得 kubectl describe 看事件、查 provisioner 日志:

[root@k8s-master1 ceph-cephfs]# vim cephfs-pvc-test.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cephfs-pvc-test
  namespace:  cephfs
  annotations:
    volume.beta.kubernetes.io/storage-class: "cephfs"  #注意:填写storage资源名称,上面创建的storage资源名称为【cephfs】
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
[root@k8s-master1 ceph-cephfs]# kubectl apply -f cephfs-pvc-test.yaml 
persistentvolumeclaim/cephfs-pvc-test created

[root@k8s-master1 ceph-cephfs]#kubectl get pvc cephfs-pvc-test -n cephfs
k8s-cephfs05

Pod 绑这个 PVC。两条硬规则:PVC 与 Pod 必须在同一名称空间;PVC 被删除,关联 Pod 的数据也跟着没了。

[root@k8s-master1 ceph-cephfs]# vim cephfs-pvc-pod.yaml 
kind: Pod
apiVersion: v1
metadata:
  labels:
    app: cephfs-pvc-pod
  name: cephfs-pv-pod
  namespace: cephfs
spec:
  containers:
  - name: cephfs-pv-busybox
    image: busybox
    command: ["sleep", "60000"]
    volumeMounts:
    - mountPath: "/mnt/cephfs"
      name: cephfs-vol
      readOnly: false
  volumes:
  - name: cephfs-vol
    persistentVolumeClaim:
      claimName: cephfs-pvc-test		#Pod关联所要绑定的PVC名称
[root@k8s-master1 ceph-cephfs]# kubectl apply -f cephfs-pvc-pod.yaml 
pod/cephfs-pv-pod created

[root@k8s-master1 ceph-cephfs]# kubectl get po -n cephfs
NAME                                  READY   STATUS    RESTARTS   AGE
cephfs-pv-pod                         1/1     Running   0          43s

[root@k8s-master1 ceph-cephfs]# kubectl get po -n cephfs -o wide
k8s-cephfs06

验证数据是否真的持久

拿 nginx 做一轮:在 webapp 名称空间建 5Gi 的 PVC,挂到 /usr/share/nginx/html,往 index.html 写一句话,再删掉 Deployment 重建,看内容还在不在。

#创建一个名称空间,命名为webapp
[root@k8s-master1 ceph-cephfs]# kubectl create ns webapp

#pvc与pod所属名称空间都为webapp
#创建PVC
[root@k8s-master1 ceph-cephfs]# vim cephfs-pvc-nginx.yaml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cephfs-pvc-nginx
  namespace: webapp
  annotations:
    volume.beta.kubernetes.io/storage-class: "cephfs"  #指定绑定的storage名称
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

#创建Pod
[root@k8s-master1 ceph-cephfs]# vim cephfs-deploy-nginx.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: nginx-web
  name: nginx-web
  namespace: webapp
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: nginx-web
  template:
    metadata:
      labels:
        k8s-app: nginx-web
      namespace: webapp
      name: nginx-web
    spec:
      containers:
        - name: nginx-web
          image: nginx
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
              name: web
              protocol: TCP
          volumeMounts:
          - name: cephfs
            mountPath: /usr/share/nginx/html   #挂载至容器的路径
      volumes:
      - name: cephfs
        persistentVolumeClaim:
          claimName: cephfs-pvc-nginx      #Pod关联所要绑定的PVC名称
[root@k8s-master1 ceph-cephfs]# kubectl apply -f cephfs-pvc-nginx.yaml 
persistentvolumeclaim/cephfs-pvc-nginx created

[root@k8s-master1 ceph-cephfs]#kubectl apply -f cephfs-deploy-nginx.yaml
deployment.apps/nginx-web created
[root@k8s-master1 ceph-cephfs]# kubectl get pvc -n webapp
[root@k8s-master1 ceph-cephfs]# kubectl get po -n webapp
k8s-cephfs07
k8s-cephfs08

写入与访问:

[root@k8s-master1 ~]# kubectl get po -n webapp
NAME                           READY   STATUS    RESTARTS   AGE
nginx-web-6898d9dd9b-plwwc   1/1     Running   0          3m57s

[root@k8s-master1 ~]# kubectl exec -it nginx-web-6898d9dd9b-plwwc -n webapp -- /bin/bash -c 'echo hello cephfs-nginx > /usr/share/nginx/html/index.html'
[root@k8s-master1 ~]# kubectl get po -o wide -n webapp | grep nginx-web-6898d9dd9b-plwwc | awk '{print $6}'

[root@k8s-master1 ~]# curl 10.68.159.135
hello cephfs-nginx

删除后重建:

[root@k8s-master1 ~]# cd ceph-cephfs/
[root@k8s-master1 ceph-cephfs]# kubectl delete -f cephfs-deploy-nginx.yaml 
deployment.apps "nginx-web" deleted

[root@k8s-master1 ceph-cephfs]# kubectl get po -n webapp
No resources found in webapp namespace.

[root@k8s-master1 ceph-cephfs]# kubectl get pvc -n webapp
k8s-cephfs09
[root@k8s-master1 ceph-cephfs]#kubectl apply -f cephfs-deploy-nginx.yaml
deployment.apps/nginx-web created

[root@k8s-master1 ceph-cephfs]# kubectl get po -n webapp
NAME                           READY   STATUS    RESTARTS   AGE
nginx-web-6898d9dd9b-lhhjf   1/1     Running   0          16s

[root@k8s-master1 ceph-cephfs]# kubectl get po -o wide -n webapp | grep nginx-web-6898d9dd9b-lhhjf | awk '{print $6}'

[root@k8s-master1 ceph-cephfs]# curl 10.68.159.136
hello cephfs-nginx

踩过的点

一是 key 忘了 base64,Pod 起来后挂载报错 Failed to mount,describe 里只显示 secret 无效;二是 monitors 写成单个 mon 地址,那个 mon 一挂全集群的卷都卡住;三是名称空间不一致,secret、PVC、Pod 三者都要在同一个名称空间里,StorageClass 是集群级资源但要在参数里写清 adminSecretNamespace。反复建删测试时加 --rm 跑一次性容器,比留一堆 Completed Pod 干净。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容