动态PV 使用UFS
背景
前面我们描述了通过创建静态 PV 的方式在 UK8S 中使用 UFS,但这种方式存在两个问题:一是每次都需要手动创建 PV 和 PVC,非常不便;二是无法自动在 UFS 创建子目录,需要预先配置。
下面介绍一个名为nfs-subdir-external-provisioner的开源项目,项目地址为 nfs-subdir-external-provisioner 。此项目可以为我们提供一个基于 UFS 的StorageClass:在业务需要 UFS 存储资源时,只需要创建 PVC,nfs-client-provisioner就会自动创建 PV,并在 UFS 下创建一个名为${namespace}-${pvcName}-${pvName}的子目录。
工作原理
我们将 nfs 相关参数通过环境变量传入到nfs-client-provisioner,这个 provisioner 通过 Deployment 控制器运行一个 Pod 来管理 nfs 的存储空间。服务启动后,我们再创建一个StorageClass,其 provisioner 与nfs-client-provisioner服务内的provisioner-name一致。nfs-client-provisioner会 watch 集群内的 PVC 对象,为其提供适配 PV 的服务,并且会在 nfs 根目录下创建对应的目录。 这些官方文档的描述已经比较详细了,不再赘述。
以下说明如何在 UK8S 中使用这个服务来管理 UFS。
操作指南
1、克隆项目
在deploy/目录下,我们需要关注三个文件,分别是rbac.yaml, deployment.yaml, class.yaml。
# git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
# cd nfs-subdir-external-provisioner/deploy/
# ls
class.yaml         kustomization.yaml rbac.yaml          test-claim.yaml
deployment.yaml    objects            test-pod.yaml2、修改nfs-client-provisioner服务的 namespace
我们将把nfs-client-provisioner服务和 RBAC 需要的资源都部署在系统插件所在的kube-systemnamespace。
sed -i "s/namespace:.*/namespace: kube-system/g" ./rbac.yaml ./deployment.yaml3、修改deployment.yaml
nfs-client-provisioner服务启动时,需要挂载 UFS,官网文档是通过spec.volume.nfs来声明的,我们这里改为静态声明 PV 的方式。具体原因说明如下:
UFS 文件系统在 mount 到云主机时需要指定额外的mountOption参数,但spec.volume.nfs不支持这个参数。而PersistentVolume的声明中可以支持这个参数,因此我们通过挂载静态 PV 的方式来完成首次挂载。
deployment.yaml文件改动处较多,建议直接替换即可。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  namespace: kube-system
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          # 使用uhub提供的镜像以获取更快的拉取速度
          image: uhub.tnqacloud.com/uk8s/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: ucloud/ufs   # 修改为TopnewCloud/ufs
            - name: NFS_SERVER
              value: 10.9.x.x     # 这里需要修改为UFS的Server地址
            - name: NFS_PATH
              value: /            # 这里改成UFS的挂载路径
      volumes:
        - name: nfs-client-root
          persistentVolumeClaim:  #这里由 nfs 改为 persistentVolumeClaim
             claimName: nfs-client-root
---
# 创建 PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-client-root
  namespace: kube-system
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 200Gi
---
#手动创建 PV 并指定 mountOption
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-client-root
spec:
  capacity:
    storage: 200Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /
    server: 10.9.x.x #这里直接写UFS的Server地址即可。
  mountOptions:
    - nolock
    - nfsvers=4.04、修改class.yaml
最后需要修改的是StorageClass的定义文件 class.yaml。
主要是新增了mountOption参数,这个值会传递给nfs-client-provisioner; 如果不加的话,挂载UFS的时候会失败。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
provisioner: ucloud/ufs
parameters:
  onDelete: "retain"
mountOptions:
  - nolock
  - nfsvers=4.0此外您可以在parameters中指定 PV 的回收策略: 删除、保留或者归档。上面示例中配置了删除 PV 后保留对应的目录。
| 参数 | 选项及作用 | 默认选项 | 
|---|---|---|
| onDelete | "delete": 删除目录"retain": 保留目录""(空值): 取决于archiveOnDelete参数 | 空值 | 
| archiveOnDelete | "true": 归档, 目录会被重命名为archived-<volume.Name>"false": 删除目录 | "true" | 
5、执行部署
依次执行
# kubectl create -f rbac.yaml
# kubectl create -f deployment.yaml
# kubectl create -f class.yaml6、验证
创建test-nfs-sc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
---
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: uhub.tnqacloud.com/uk8s/busybox:1.31.1
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "echo 1 > /mnt/SUCCESS; sleep 10000000"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim创建测试 pod 并验证挂载的 UFS 可读写:
# kubectl create -f test-nfs-sc.yaml
# kubectl exec test-pod -- /bin/sh -c 'ls /mnt/ && cat /mnt/*'
SUCCESS
1
# kubectl delete -f test-nfs-sc.yaml升级指南
从旧版external-storage 到新版nfs-subdir-external-provisioner的升级流程
升级背景
旧版provisioner能支持的最新k8s版本为1.23, 其中1.20及以上需要在apiserver上添加--feature-gates=RemoveSelfLink=false支持。
1.24及更新的k8s版本必须使用新版provisioner(因为已经不支持该apiserver参数)。
对存量pvc和pod的影响
使用旧版provisioner (即managed-nfs-storage storage class) 申请的pvc,升级完成后可以继续挂载使用,pod内的挂载不受影响,pod重启后仍然可以挂载。pod和pvc删除后不会清理ufs上对应的文件,如需释放空间应手动删除文件。
升级流程
结合新版provisioner部署文档(见本文前部分)进行升级。
rbac
确认rbac.yaml中指定的namespace与原nfs-client-provisioner ServiceAccount所在namespace相同,比如同为default或同为kube-system, 修改完成后执行kubectl apply -f rbac.yaml升级
deployment
- 确认namespace与上面rbac部署的namespace相同
- 删除原来的deployment: kubectl delete deploy nfs-client-provisioner
- apply新的deployment.yaml。因为创建静态pvc和pv的部分和原来相同, 这一步apply的结果应如下所示
deployment.apps/nfs-client-provisioner configured
persistentvolumeclaim/nfs-client-root unchanged
persistentvolume/nfs-client-root unchangedstorage class
直接apply -f class.yaml即可。(旧的storage class managed-nfs-storage 可以不删除)。