作者:运维有术星主
在 Kubernetes 生态系统中,持久化存储扮演着至关重要的角色,它是支撑应用稳定运行的基石。对于那些选择自建 Kubernetes 集群的运维架构师而言,选择合适的后端持久化存储解决方案是一项至关重要的选型决策。后端持久化存储常见的解决方案有 Ceph、GlusterFS、NFS、hostPath,以及这两年新兴起的 Longhorn。当然,技术领域日新月异,还有其他优秀的解决方案我尚未了解。
今天,我将为大家分享,如何在 Kubernetes 集群中手动安装 Kubernetes NFS Subdir External Provisioner 插件。这是一个强大的工具,能够实现为 Kubernetes 集群提供自动化的基于 NFS 存储的持久化动态卷管理能力。通过实战演示,您将学会如何将 NFS 作为后端的持久化存储解决方案集成至 Kubernetes 集群。
本文核心内容概览:
实战服务器配置(架构 1:1 复刻小规模生产环境,配置略有不同)
主机名 | IP | CPU | 内存 | 系统盘 | 数据盘 | 用途 |
---|---|---|---|---|---|---|
ksp-registry | 192.168.9.90 | 4 | 8 | 40 | 200 | Harbor 镜像仓库 |
ksp-control-1 | 192.168.9.91 | 4 | 8 | 40 | 100 | KubeSphere/k8s-control-plane |
ksp-control-2 | 192.168.9.92 | 4 | 8 | 40 | 100 | KubeSphere/k8s-control-plane |
ksp-control-3 | 192.168.9.93 | 4 | 8 | 40 | 100 | KubeSphere/k8s-control-plane |
ksp-worker-1 | 192.168.9.94 | 4 | 16 | 40 | 100 | k8s-worker/CI |
ksp-worker-2 | 192.168.9.95 | 4 | 16 | 40 | 100 | k8s-worker |
ksp-worker-3 | 192.168.9.96 | 4 | 16 | 40 | 100 | k8s-worker |
ksp-storage-1 | 192.168.9.97 | 4 | 8 | 40 | 300+ | Ceph/Longhorn/NFS/ElasticSearch |
ksp-storage-2 | 192.168.9.98 | 4 | 8 | 40 | 300+ | Ceph/Longhorn/ElasticSearch |
ksp-storage-3 | 192.168.9.99 | 4 | 8 | 40 | 300+ | Ceph/Longhorn/ElasticSearch |
ksp-gpu-worker-1 | 192.168.9.101 | 4 | 16 | 40 | 100 | k8s-worker(GPU NVIDIA Tesla M40 24G) |
ksp-gpu-worker-2 | 192.168.9.102 | 4 | 16 | 40 | 100 | k8s-worker(GPU NVIDIA Tesla P100 16G) |
ksp-gateway-1 | 192.168.9.103 | 2 | 4 | 40 | 自建应用服务代理网关/VIP:192.168.9.100 | |
ksp-gateway-2 | 192.168.9.104 | 2 | 4 | 40 | 自建应用服务代理网关/VIP:192.168.9.100 | |
ksp-mid | 192.168.9.105 | 4 | 8 | 40 | 100 | 部署在 k8s 集群之外的服务节点(Gitlab 等) |
合计 | 15 | 56 | 152 | 600 | 2000+ |
实战环境涉及软件版本信息
本文介绍的内容可直接用于研发、测试环境,对于生产环境不建议使用 NFS 存储,主要原因如下:
虽然,我个人不建议在生产环境使用 NFS 作为 Kubernetes 的后端持久化存储。但是,在我做过的小调研中发现,生产环境使用 NFS 的比率还挺高。
这是为什么呢?我左思右想可能的原因有:
生产环境一定要使用 NFS?建议仔细考虑以下几点:
在安装 Kubernetes NFS Subdir External Provisioner 之前,Kubernetes 集群所有节点需要提前安装 NFS 客户端,否则部署过程会报错。不同操作系统 NFS 客户端的包名不同,CentOS 和 openEuler 中包名为 nfs-utils。
yum install nfs-utils
本文的主要目的是测试 Kubernetes 使用 NFS 作为持久化存储的能力。因此,实战环境选择一台操作系统为 openEuler 的虚拟机部署 NFS 服务。该 NFS 服务器安装配置方法仅适用于测试环境,生产环境请谨慎评估、配置使用。
本文使用 IP 为 192.168.9.97
的 ksp-storage-1 节点, 部署单节点 NFS 存储服务器。部署过程中分配一块独立的 100G 数据盘 /dev/sde
, 使用 LVM 类型将其格式化,挂载到 /datanfs/
目录下作为共享存储的根目录。
LVM 配置比较简单,操作细节不做解释,直接上命令。
pvcreate /dev/sde vgcreate datanfs /dev/sde lvcreate -l 100%VG datanfs -n lvnfs mkfs.xfs /dev/mapper/datanfs-lvnfs mkdir /datanfs/ mount /dev/mapper/datanfs-lvnfs /datanfs/ tail -1 /etc/mtab >> /etc/fstab
yum install nfs-utils
执行以下命令,创建共享数据存储目录,本文以 /datanfs/k8s
为例,请根据实际情况调整路径和权限。
mkdir -p /datanfs/k8s chown nobody:nobody /datanfs/k8s
配置 NFS 服务器数据导出目录及访问 NFS 服务器的客户端机器权限。
编辑配置文件 vi /etc/exports
,添加如下内容:
/datanfs/k8s 192.168.9.0/24(rw,sync,all_squash,anonuid=65534,anongid=65534,no_subtree_check)
配置说明:
systemctl enable nfs-server --now
exportfs -v
正确执行后,输出结果如下 :
$ exportfs -v /datanfs/k8s 192.168.9.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,all_squash)
找一台额外的机器作为客户端验证测试,本示例使用 ksp-worker-1 节点。
mkdir /mnt/nfs
yum install nfs-utils
$showmount -e 192.168.9.97 Export list for 192.168.9.97: /datanfs/k8s 192.168.9.0/24
mount -t nfs 192.168.9.97:/datanfs/k8s /mnt/nfs/
# 创建测试目录、创建测试文件、测试文件写入内容、查看写入目录和文件权限、删除目录和文件 # 创建 mkdir /mnt/nfs/nfs-test touch /mnt/nfs/nfs-test.txt echo "nfs-test" > nfs-test.txt # 查看 $ ls -l /mnt/nfs/ total 4 drwxr-xr-x 2 nobody nobody 6 Jul 11 21:37 nfs-test -rw-r--r-- 1 nobody nobody 9 Jul 11 21:37 nfs-test.txt $ cat /mnt/nfs/nfs-test.txt nfs-test # 删除 rmdir /mnt/nfs/nfs-test rm -rf /mnt/nfs/nfs-test.txt
umount /mnt/nfs/
Kubernetes 支持 NFS 存储,需要安装 nfs-subdir-external-provisioner ,它是一个存储资源自动调配器,它可将现有的 NFS 服务器通过持久卷声明来支持 Kubernetes 持久卷的动态分配。该组件是对 Kubernetes NFS-Client Provisioner 的扩展, nfs-client-provisioner 已经不提供更新,而且 nfs-client-provisioner Github 仓库 也已经处于归档状态,已经迁移到 nfs-subdir-external-provisioner 的仓库。
官方提供的安装方式有三种:
使用 Helm 的方式比较简单,也是现在官方推荐的、使用率最高的方式。
往期我已经分享过如何使用 Helm 的方式部署 NFS Subdir External Provisioner。所以,今天实战演示如何手动部署。
其实,我个人更喜欢手动部署,手动方式更灵活,适用于没有 Helm 或是跟我一样不愿意用 Helm 的离线或在线环境。
在 K8S 控制节点 ksp-control-1 ,下载最新版 nfs-subdir-external-provisioner-4.0.18
Releases 文件,并解压。
wget https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/archive/refs/tags/nfs-subdir-external-provisioner-4.0.18.zip unzip nfs-subdir-external-provisioner-4.0.18.zip cd nfs-subdir-external-provisioner-nfs-subdir-external-provisioner-4.0.18/
可选配置,默认为 default,新建方便资源管理。
kubectl create ns nfs-system
sed -i'' "s/namespace:.*/namespace: nfs-system/g" ./deploy/rbac.yaml ./deploy/deployment.yaml
kubectl create -f deploy/rbac.yaml
编辑 provisioner’s deployment 文件 deploy/deployment.yaml
,重点修改以下内容:
nfs-subdir-external-provisioner:v4.0.2
,网络受限时需要想办法下载并上传到方便访问的镜像仓库文件 deployment.yaml
默认内容如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: nfs-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 image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: k8s-sigs.io/nfs-subdir-external-provisioner - name: NFS_SERVER value: 10.3.243.101 - name: NFS_PATH value: /ifs/kubernetes volumes: - name: nfs-client-root nfs: server: 10.3.243.101 path: /ifs/kubernetes
说明: 主要修改内容,用实际 NFS 配置信息替换默认值(受限于篇幅,未展示最终修改后的内容)
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
value: 10.3.243.101
value: /ifs/kubernetes
kubectl apply -f deploy/deployment.yaml
$ kubectl get deployment,pods -n nfs-system NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nfs-client-provisioner 1/1 1 1 41s NAME READY STATUS RESTARTS AGE pod/nfs-client-provisioner-75df4c7d5b-nc2ts 1/1 Running 0 41s
Step 1: 编辑 NFS subdir external provisioner 定义 Kubernetes Storage Class 的配置文件 deploy/class.yaml
,重点修改以下内容:
文件默认内容如下:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-client provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME' parameters: archiveOnDelete: "false"
修改后的文件内容如下:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-sc provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "true"
说明: 主要修改内容
重点说说 Parameters archiveOnDelete 的配置。
archived-<volume.Name>
的命名规则,归档保留原有的数据目录Parameters 所有可选设置项如下所示,各位可自行研究、使用:
Name | Description | Default |
---|---|---|
onDelete | If it exists and has a delete value, delete the directory, if it exists and has a retain value, save the directory. | will be archived with name on the share: archived-<volume.Name> |
archiveOnDelete | If it exists and has a false value, delete the directory. if onDelete exists, archiveOnDelete will be ignored. |
will be archived with name on the share: archived-<volume.Name> |
pathPattern | Specifies a template for creating a directory path via PVC metadata’s such as labels, annotations, name or namespace. To specify metadata use ${.PVC.<metadata>} . Example: If folder should be named like <pvc-namespace>-<pvc-name> , use ${.PVC.namespace}-${.PVC.name} as pathPattern. |
n/a |
Step 2: 执行部署命令,部署 Storage Class。
kubectl apply -f deploy/class.yaml
查看 Storage Class 部署结果。
$ kubectl get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE local (default) openebs.io/local Delete WaitForFirstConsumer false 50d nfs-sc k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 19s
vi test-nfs-pvc.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-nfs-pvc spec: storageClassName: nfs-sc accessModes: - ReadWriteMany resources: requests: storage: 1Gi
kubectl apply -f test-nfs-pvc.yaml
$ kubectl get pvc -o wide NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE test-nfs-pvc Bound pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e 1Gi RWX nfs-sc 10s Filesystem
vi test-nfs-pod.yaml
kind: Pod apiVersion: v1 metadata: name: test-nfs-pod spec: containers: - name: test-nfs-pod image: busybox:stable command: - "/bin/sh" args: - "-c" - "touch /mnt/SUCCESS && sleep 3600" volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-nfs-pvc
kubectl apply -f test-nfs-pod.yaml
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test-nfs-pod 1/1 Running 0 5s 10.233.68.152 ksp-worker-2 <none> <none>
$ kubectl exec test-nfs-pod -- df -h Filesystem Size Used Available Use% Mounted on overlay 99.9G 6.6G 93.3G 7% / tmpfs 64.0M 0 64.0M 0% /dev tmpfs 7.6G 0 7.6G 0% /sys/fs/cgroup 192.168.9.97:/datanfs/k8s/default-test-nfs-pvc-pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e 99.9G 746.0M 99.2G 1% /mnt /dev/mapper/openeuler-root 34.2G 2.2G 30.2G 7% /etc/hosts /dev/mapper/openeuler-root 34.2G 2.2G 30.2G 7% /dev/termination-log /dev/mapper/data-lvdata 99.9G 6.6G 93.3G 7% /etc/hostname /dev/mapper/data-lvdata 99.9G 6.6G 93.3G 7% /etc/resolv.conf shm 64.0M 0 64.0M 0% /dev/shm tmpfs 13.9G 12.0K 13.9G 0% /var/run/secrets/kubernetes.io/serviceaccount tmpfs 7.6G 0 7.6G 0% /proc/acpi tmpfs 64.0M 0 64.0M 0% /proc/kcore tmpfs 64.0M 0 64.0M 0% /proc/keys tmpfs 64.0M 0 64.0M 0% /proc/timer_list tmpfs 64.0M 0 64.0M 0% /proc/sched_debug tmpfs 7.6G 0 7.6G 0% /proc/scsi tmpfs 7.6G 0 7.6G 0% /sys/firmware
注意: 在输出结果中我们可以看到挂载的 NFS 存储的可用空间为 99.9G,而不是我们 PVC 中分配的 1G。
# 写入 2G 的数据 $ kubectl exec test-nfs-pod -- dd if=/dev/zero of=/mnt/test-nfs.img bs=1M count=2000 2000+0 records in 2000+0 records out 2097152000 bytes (2.0GB) copied, 2.731103 seconds, 732.3MB/s # 查看结果 $ kubectl exec test-nfs-pod -- ls -lh /mnt/ total 2G -rw-r--r-- 1 nobody nobody 0 Jul 11 21:45 SUCCESS -rw-r--r-- 1 nobody nobody 2.0G Jul 11 21:47 test-nfs.img
注意: 实际测试我们写入了 2G 的数据量,已经超过了我们创建的 PVC 1G 的限制。因此,要特别注意,使用 NFS 存储时无法限制存储使用量。
SSH 登陆 NFS 存储服务器( ksp-storage-1 节点),执行以下命令。
# 查看 NFS 导出数据根目录 $ ls -l /datanfs/k8s/ total 0 drwxrwxrwx. 2 nobody nobody 41 Jul 11 21:47 default-test-nfs-pvc-pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e # 查看分配的 PVC 数据目录 $ ls -l /datanfs/k8s/default-test-nfs-pvc-pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e/ total 2048000 -rw-r--r--. 1 nobody nobody 0 Jul 11 21:45 SUCCESS -rw-r--r--. 1 nobody nobody 2097152000 Jul 11 21:47 test-nfs.img
在输出结果中,可以看见 SUCCESS 和 test.img 两个文件,并且可以看出命名目录的规则为 namespace 名称-pvc 名称-pv 名称。
PV 名称格式是 pvc+随机字符串,所以,每次只要不删除 PVC,那么 Kubernetes 中 PV 与存储绑定将不会丢失,要是删除 PVC 也就意味着删除了绑定的文件夹,下次就算重新创建相同名称的 PVC,生成的文件夹名称也不会一致,因为 PV 名是随机生成的字符串,而文件夹命名又跟 PV 有关,所以删除 PVC 需谨慎。
kubectl delete -f test-nfs-pod.yaml -f test-nfs-pvc.yaml
# 查看 NFS 导出数据根目录 $ ls -l /datanfs/k8s/ total 0 drwxrwxrwx. 2 nobody nobody 41 Jul 11 21:47 archived-default-test-nfs-pvc-pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e # 查看删除的 PVC 数据目录 $ ls -l /datanfs/k8s/archived-default-test-nfs-pvc-pvc-6b7abed8-805e-45ff-82c8-1ad3beca8b9e/ total 2048000 -rw-r--r--. 1 nobody nobody 0 Jul 11 21:45 SUCCESS -rw-r--r--. 1 nobody nobody 2097152000 Jul 11 21:47 test-nfs.img
从结果中可以看到,Kubernetes 删除 PVC 后,在 NFS 存储层并没有立即删除 PVC 对应的数据目录及数据,而是将原来的数据目录改名为 archived-+原有数据目录名称的形式。
该结果与我们配置 Storage Class 时,将参数 archiveOnDelete 值设置为 true 的预期相符(你可以可自行测试其他参数和值的配置效果)。
在控制台左侧功能菜单,依次选择「集群」->「存储」->「存储类」。
Step 1: 在控制台左侧功能菜单,依次选择「集群」->「存储」->「持久卷声明」,点击「创建」按钮。
nfs-sc
,容量选择 2GStep 2: 创建完成后,查看已经创建的 PVC、PV 及详情。
容量显示 NFS 存储空间的总容量、剩余容量、已使用百分比,并不是单个 PV 的容量信息。初始测试时占用 2G 未删除,因此 NFS 存储空间总体分配了 4G,显示结果略有差异也属于正常现象。
当需要卸载 NFS Subdir External Provisioner 时,先确认满足以下前提条件。
archived-
命名的所有数据目录(可选,谨慎操作)按顺序执行资源删除命令, 卸载 NFS Subdir External Provisioner。
kubectl delete -f deploy/class.yaml
kubectl delete -f deploy/deployment.yaml
kubectl delete -f deploy/rbac.yaml
$ kubectl get deploy,pod,sc -n nfs-system NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE storageclass.storage.k8s.io/local (default) openebs.io/local Delete WaitForFirstConsumer false 22h
说明: 结果中显示的是默认的 openebs 存储,nfs 相关资源已经删除
kubectl delete ns nfs-system
免责声明:
本文由博客一文多发平台 OpenWrite 发布!