通过PV/PVC方式使用CFS

概述

容器的特性决定了容器本身是非持久化的,容器被删除,其上的数据也一并删除。CFS可以解决容器的数据共享和持久化存储问题,适用于大数据分析、媒体、游戏等很多场景。
而PV(PersistentVolume)和PVC(PersistentVolumeClaim)是K8S提供的用于抽象存储细节的API资源。利用CFS在集群内创建PV和PVC资源,用户可以直接将CFS作为存储卷挂载到容器中,而无需关注底层的实现细节,从而更加便捷地为容器集群提供持久化存储方案。

准备工作

用户做好以下准备,才能在容器中挂载CFS实例。

创建CFS实例和挂载点

  1. 创建CFS实例,操作步骤请参考创建文件系统实例
  2. 创建文件系统挂载点,操作步骤请参考创建文件系统挂载点

注意: 创建的cfs实例和挂载点须和集群节点在同一vpc/子网内。

本操作假设CFS挂载点地址为 cfs-test.baidubce.com

创建容器集群

  1. 创建一个容器集群,操作步骤参考创建集群
  2. 下载命令行客户端kubectl,并连接集群,操作步骤参考通过kubectl连接Kubernetes集群

安装NFS基础软件包

在集群内的所有节点上执行以下指令,完成NFS基础软件包安装。

  • centos
$ sudo yum install -y nfs-utils
  • ubuntu
$ sudo apt install -y nfs-common

操作指南

静态PV/PVC方式挂载CFS

1.在集群中创建PV和PVC资源

使用kubectl,执行 kubectl create -f pv-cfs.yaml 完成PV的创建

对应的pv-cfs.yaml文件如下所示:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-cfs
spec:
  capacity:
    storage: 8Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - hard
    - nfsvers=4.1
    - nordirplus
  nfs:
    path: /
    server: cfs-test.baidubce.com

注意: yaml中server字段对应的是CFS挂载点地址

创建PV后,输入kubectl get pv可以看见一个available状态的PV,如下所示:

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
pv-cfs    8Gi        RWX            Retain           Available                                      3s

建立一个能够与该PV绑定的PVC

使用kubectl,执行 kubectl create -f pvc-cfs.yaml完成PVC的创建

对应的pvc-cfs.yaml文件如下所示:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-cfs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 8Gi

绑定前,PVC为pending状态

$ kubectl get pvc
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-cfs   Pending                                                      2s                                                  2s

绑定后,PV和PVC状态变为Bound

$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM             STORAGECLASS   REASON    AGE
pv-cfs    8Gi        RWX            Retain           Bound     default/pvc-cfs                            36s
$ kubectl get pvc
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-cfs   Bound     pv-cfs    8Gi        RWX                           1m

有关PV和PVC的更多设置和字段说明,见k8s官方文档

2.在Pod内挂载PVC

在Pod spec内指定相应的PVC名称即可,使用kubectl,执行 kubectl create -f demo-cfs-rc.yaml 完成rc的创建

对应的demo-cfs-rc.yaml文件如下所示:

apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 20
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: "/cfs-volume"
          name: mycfs
      volumes:
      - name: mycfs
        persistentVolumeClaim:
          claimName: pvc-cfs

Pod创建后,可以读写容器内的/cfs-volume路径来访问相应的CFS存储上的内容。

由于创建PV和PVC时指定了accessModesReadWriteMany,该PVC可以被多节点上的Pod挂载读写。

3.释放PV和PVC资源

完成存储资源的使用后,可以释放PVC和PV资源

使用以下命令可以释放PVC

$ kubectl delete -f  pvc-cfs.yaml

释放PVC后,原来与之绑定的PV状态会变为Release,如下所示:

NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM             STORAGECLASS   REASON    AGE
pv-cfs    8Gi        RWX            Retain           Released   default/pvc-cfs                            16m

输入以下指令释放PV资源

$ kubectl delete -f  pv-cfs.yaml

动态PV/PVC方式挂载CFS

1.创建StorageClass和Provisioner

dynamic-cfs-template.yaml是一个yaml文件模板,包含了需要创建的集群资源信息。

dynamic-cfs-template.yaml文件内容如下:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: kube-system
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: kube-system
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-cfs
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  mountOptions:
    - hard
    - nfsvers=4.1
    - nordirplus
  nfs:
    path: {{NFS_PATH}}
    server: {{NFS_SERVER}}
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-cfs
  namespace: kube-system
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: hub.baidubce.com/jpaas-public/nfs-client-provisioner:latest
          imagePullPolicy: Always
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: {{PROVISIONER_NAME}}
            - name: NFS_SERVER
              value: {{NFS_SERVER}}
            - name: NFS_PATH
              value: {{NFS_PATH}}
            - name: SHARE_PATH
              value: "{{SHARE_PATH}}"
      volumes:
        - name: nfs-client-root
          persistentVolumeClaim:
            claimName: pvc-cfs

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: {{STORAGE_CLASS_NAME}}
provisioner: {{PROVISIONER_NAME}}
parameters:
  archiveOnDelete: "{{ARCHIVE_ON_DELETE}}"
mountOptions:
  - hard
  - nfsvers=4.1
  - nordirplus

dynamic-cfs-template.yaml模板文件中可自定义的选项如下:

  • NFS_SERVER: CFS挂载点地址。
  • NFS_PATH: CFS远程挂载目录。
  • SHARE_PATH: 不同PVC的CFS挂载目录是否隔离,true-不隔离,false-隔离。若指定隔离,则会在CFS挂载目录下为每个PVC创建一个子目录,对应PVC使用该子目录作为挂载目录;否则所有PVC共享挂载目录。
  • ARCHIVE_ON_DELETE: 删除PVC后是否保留对应数据,仅当PVC挂载目录隔离时生效,true-保留,false-不保留;PVC挂载目录共享时,删除PVC不会删除任何数据。设置为不保留则直接删除对应PVC的子目录,否则仅将原子目录名加上archive-前缀后保留。
  • STORAGE_CLASS_NAME: 创建的StorageClass名称。
  • PROVISIONER_NAME: Provisioner名称。

支持shell的系统中,可以直接使用下面的replace.sh脚本进行yaml模板中模板变量的替换操作。

#!/bin/sh
# user defined vars

NFS_SERVER="cfs-test.baidubce.com"
NFS_PATH="/cce/shared"
SHARE_PATH="true" # 不同PVC的挂载目录是否隔离,true-不隔离,false-隔离
ARCHIVE_ON_DELETE="false" # 删除PVC是否保留对应数据,仅当PVC挂载目录隔离时生效,true-保留,false-不保留
STORAGE_CLASS_NAME="sharedcfs" # StorageClass名称
PROVISIONER_NAME="baidubce/$STORAGE_CLASS_NAME" # provisioner名称

YAML_FILE="./dynamic-cfs-template.yaml"

# replace template vars in yaml file

sed -i "s#{{SHARE_PATH}}#$SHARE_PATH#" $YAML_FILE
sed -i "s#{{ARCHIVE_ON_DELETE}}#$ARCHIVE_ON_DELETE#" $YAML_FILE
sed -i "s#{{STORAGE_CLASS_NAME}}#$STORAGE_CLASS_NAME#" $YAML_FILE
sed -i "s#{{PROVISIONER_NAME}}#$PROVISIONER_NAME#" $YAML_FILE
sed -i "s#{{NFS_SERVER}}#$NFS_SERVER#" $YAML_FILE
sed -i "s#{{NFS_PATH}}#$NFS_PATH#" $YAML_FILE

将脚本中前半段中的shell变量替换为期望值,将replace.sh脚本和dynamic-cfs-template.yaml文件放置在同一个目录下,执行sh replace.sh即可。

或者采用其他方式,将模板yaml文件中的模板变量替换为期望值。

最后,使用kubectl工具,执行 kubectl create -f dynamic-cfs-template.yaml 完成StorageClass和Provisioner的创建。

$ kubectl create -f dynamic-cfs-template.yaml
clusterrole "nfs-client-provisioner-runner" created
clusterrolebinding "run-nfs-client-provisioner" created
role "leader-locking-nfs-client-provisioner" created
rolebinding "leader-locking-nfs-client-provisioner" created
serviceaccount "nfs-client-provisioner" created
persistentvolume "pv-cfs" created
persistentvolumeclaim "pvc-cfs" created
deployment "nfs-client-provisioner" created
storageclass "sharedcfs" created
$ kubectl get pod --namespace kube-system  | grep provisioner
nfs-client-provisioner-c94494f6d-dlxmj   1/1       Running   0          26s

如果相应的Pod进入Running状态,则动态绑定PV所需的资源已经建立成功。

2.创建PVC时动态生成PV并绑定

在PVC Spec中指定上面创建的StorageClass名称,则在创建PVC时,会自动调用相应StorageClass绑定的的Provisioner生成相应的PV进行绑定。

使用kubectl,执行 kubectl create -f dynamic-pvc-cfs.yaml 完成PVC的创建。

假设创建的StorageClass名称为sharedcfs,对应的 dynamic-pvc-cfs.yaml 文件如下所示

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: dynamic-pvc-cfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: sharedcfs
  resources:
    requests:
      storage: 5Gi

创建PVC后,可以看见相应的PV自动创建,PVC状态变为Bound,即PVC已经与新创建的PV绑定。

$ kubectl create -f dynamic-pvc-cfs.yaml
persistentvolumeclaim "dynamic-pvc-cfs" created
$ kubectl get pvc
NAME              STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
dynamic-pvc-cfs   Bound     pvc-6dbf3265-bbe0-11e8-bc54-fa163e08135d   5Gi        RWX            sharedcfs      4s
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                     STORAGECLASS   REASON    AGE
pv-cfs                                     5Gi        RWX            Retain           Bound     kube-system/pvc-cfs                                21m
pvc-6dbf3265-bbe0-11e8-bc54-fa163e08135d   5Gi        RWX            Delete           Bound     default/dynamic-pvc-cfs   sharedcfs                7s

3.在Pod内挂载PVC

在Pod spec内指定相应的PVC名称即可,使用kubectl,执行 kubectl create -f dynamic-cfs-pod.yaml 完成资源的创建。

对应的dynamic-cfs-pod.yaml文件如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: test-pvc-pod
  labels:
    app: test-pvc-pod
spec:
  containers:
  - name: test-pvc-pod
    image: nginx
    volumeMounts:
      - name: cfs-pvc
        mountPath: "/cfs-volume"
  volumes:
    - name: cfs-pvc
      persistentVolumeClaim:
        claimName: dynamic-pvc-cfs

Pod创建后,可以读写容器内的/cfs-volume路径来访问相应的CFS存储上的内容。

4.释放PVC时动态销毁绑定PV

删除PVC时,与之绑定的动态PV会被一同删除,其中的数据则根据用户定义的SHARE_PATHARCHIVE_ON_DELETE选项进行相应的保留或删除处理。

$ kubectl get pvc
NAME              STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
dynamic-pvc-cfs   Bound     pvc-6dbf3265-bbe0-11e8-bc54-fa163e08135d   5Gi        RWX            sharedcfs      9m
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                     STORAGECLASS   REASON    AGE
pv-cfs                                     5Gi        RWX            Retain           Bound     kube-system/pvc-cfs                                31m
pvc-6dbf3265-bbe0-11e8-bc54-fa163e08135d   5Gi        RWX            Delete           Bound     default/dynamic-pvc-cfs   sharedcfs                9m
$ kubectl delete -f dynamic-pvc-cfs.yaml
persistentvolumeclaim "dynamic-pvc-cfs" deleted
$ kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                 STORAGECLASS   REASON    AGE
pv-cfs    5Gi        RWX            Retain           Bound     kube-system/pvc-cfs                            31m
$ kubectl get pvc
No resources found.