文章目录
- [0 背景与架构](#0 背景与架构)
-
- [0.1 为什么需要NFS](#0.1 为什么需要NFS)
- [0.2 环境架构](#0.2 环境架构)
- [0.3 镜像拉取方案](#0.3 镜像拉取方案)
- [1 配置NFS服务器(rockylinux10-4)](#1 配置NFS服务器(rockylinux10-4))
-
- [1.1 创建出 rockylinux10-4](#1.1 创建出 rockylinux10-4)
- [1.2 安装nfs-utils](#1.2 安装nfs-utils)
- [1.3 创建共享目录并设置权限](#1.3 创建共享目录并设置权限)
- [1.4 配置/etc/exports](#1.4 配置/etc/exports)
- [1.5 启动并启用nfs-server](#1.5 启动并启用nfs-server)
- [1.6 验证NFS共享](#1.6 验证NFS共享)
- [2 在所有K8s节点安装nfs-utils(客户端)](#2 在所有K8s节点安装nfs-utils(客户端))
- [3 部署nfs-subdir-external-provisioner](#3 部署nfs-subdir-external-provisioner)
-
- [3.1 下载部署文件](#3.1 下载部署文件)
- [3.2 修改deployment.yaml](#3.2 修改deployment.yaml)
- [3.3 应用部署](#3.3 应用部署)
- [4 验证部署](#4 验证部署)
-
- [4.1 查看 Pod 状态](#4.1 查看 Pod 状态)
- [4.2 查看 StorageClass](#4.2 查看 StorageClass)
- [5 测试RWX PVC](#5 测试RWX PVC)
-
- [5.1 创建 RWX PVC](#5.1 创建 RWX PVC)
- [5.2 创建两个 Pod 同时挂载](#5.2 创建两个 Pod 同时挂载)
- [5.3 验证数据共享](#5.3 验证数据共享)
- [6 常见问题与解决](#6 常见问题与解决)
-
- [6.1 镜像拉取失败(ImagePullBackOff)](#6.1 镜像拉取失败(ImagePullBackOff))
- [6.2 Pod 一直 Pending](#6.2 Pod 一直 Pending)
- [6.3 挂载失败](#6.3 挂载失败)
- [7 总结](#7 总结)
- 感谢阅读
0 背景与架构
0.1 为什么需要NFS
在 《02_在多个RockyLinux10虚拟机上安装k8s集群》 和 《04_在k8s集群中安装OpenEBS Rawfile并测试快照》 之后,我们的集群已经具备了本地存储(Rawfile)和快照能力。但 Rawfile 只支持 ReadWriteOnce(RWO),而很多场景(例如多个Pod同时读写同一份数据)需要 ReadWriteMany(RWX)。
NFS 是 Kubernetes 中原生支持 RWX 的存储方案之一,它允许多个节点上的多个 Pod 同时挂载同一个共享目录并进行读写。非常适合学习 PVC 的不同访问模式,尤其是 RWX。
0.2 环境架构
| IP | 虚拟机名称 | 角色 | 说明 |
|---|---|---|---|
| 192.168.56.101 | rockylinux10-1 | control-plane | k8s master 节点 |
| 192.168.56.102 | rockylinux10-2 | worker | k8s worker 节点 |
| 192.168.56.103 | rockylinux10-3 | worker | k8s worker 节点 |
| 192.168.56.104 | rockylinux10-4 | NFS Server | 独立的 NFS 服务器(这个是从 rockylinux10-1 的 VirtualBox 快照中复制出来后,配置的) |
0.3 镜像拉取方案
由于国内网络限制,我们采用以下镜像源:
| 原始仓库 | 替换地址 | 用途 |
|---|---|---|
| docker.io | docker.m.daocloud.io | DaoCloud 镜像加速 |
| registry.k8s.io | k8s.m.daocloud.io | DaoCloud 的 k8s 镜像代理 |
1 配置NFS服务器(rockylinux10-4)
1.1 创建出 rockylinux10-4
从 rockylinux10-1 的 VirtualBox 快照 rockylinux10-init 中,复制出 rockylinux10-4,如下图:

在弹出的对话框中做如下图的配置:

复制好之后,启动 rockylinux10-4,我们来修改它的静态 ip 和 hostname 。这块儿可以参考 《02_在多个RockyLinux10虚拟机上安装k8s集群》 的 1.14 设置静态IP 小节的内容。
由于 rockylinux10-4 是从 rockylinux10-1 复制出来的,所以不用想,现在 rockylinux10-4 的静态 ip 肯定是 192.168.56.101 ,我们直接在 rockylinux10-4 中执行 nmtui 命令,如下图:

回车之后:

回车之后:

按照上图的编辑好之后,一路 OK,就好。然后设置 hotstname 主机名:
bash
hostnamectl set-hostname rockylinux10-4
然后 reboot 重启 rockylinux10-4 。重启之后,给 rockylinux10-1 到 10-4 的每台机器,设置一下 /etc/hosts ,保证有如下内容:
bash
192.168.56.101 rockylinux10-1
192.168.56.102 rockylinux10-2
192.168.56.103 rockylinux10-3
192.168.56.104 rockylinux10-4
1.2 安装nfs-utils
在 rockylinux10-4 上执行:
bash
sudo dnf install -y nfs-utils
1.3 创建共享目录并设置权限
bash
sudo mkdir -p /data/k8s-nfs
sudo chown nobody:nobody /data/k8s-nfs
sudo chmod 777 /data/k8s-nfs
这里设置为
nobody:nobody和777是为了简化权限,生产环境请根据实际需求调整。
1.4 配置/etc/exports
编辑 /etc/exports,添加以下内容:
bash
/data/k8s-nfs 192.168.56.0/24(rw,sync,no_subtree_check,no_root_squash)
参数说明:
rw:读写权限sync:同步写入no_subtree_check:不检查子树,提高性能no_root_squash:允许 root 用户保持权限(方便测试)
1.5 启动并启用nfs-server
bash
sudo exportfs -ra
sudo systemctl restart nfs-server
sudo systemctl enable nfs-server
1.6 验证NFS共享
bash
sudo showmount -e localhost
期望输出:
Export list for localhost:
/data/k8s-nfs 192.168.56.0/24
2 在所有K8s节点安装nfs-utils(客户端)
Kubernetes 的 kubelet 需要调用 mount -t nfs 来挂载 NFS,因此每个节点 都需要安装 nfs-utils。
在 rockylinux10-1、10-2、10-3 上分别执行:
bash
sudo dnf install -y nfs-utils --disablerepo=docker-ce-stable
加
--disablerepo=docker-ce-stable是为了跳过可能会连接失败的 Docker 仓库,避免dnf卡住。如果已经永久禁用了该仓库,可以省略此参数。
3 部署nfs-subdir-external-provisioner
nfs-subdir-external-provisioner 是 Kubernetes SIG-Storage 官方维护的项目,它可以在 NFS 共享目录下自动创建子目录作为 PV,实现动态供应。
3.1 下载部署文件
在 master 节点(rockylinux10-1)上执行:
bash
mkdir -p ~/nfs-provisioner && cd ~/nfs-provisioner
# 下载三个 YAML 文件
curl -O https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/deployment.yaml
curl -O https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/rbac.yaml
curl -O https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/class.yaml
如果 curl 下载失败,可以用浏览器打开链接保存文件。
3.2 修改deployment.yaml
需要修改三个地方:
a. 修改 NFS 服务器地址和路径
找到 env 部分,改成你的 NFS 服务器信息:
yaml
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.56.104 # NFS 服务器 IP
- name: NFS_PATH
value: /data/k8s-nfs # NFS 共享路径
volumes:
- name: nfs-client-root
nfs:
server: 192.168.56.104 # 同上
path: /data/k8s-nfs # 同上
b. 修改镜像地址为国内可访问的
原镜像 registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 国内拉不动,改成 DaoCloud 的镜像源:
yaml
image: docker.m.daocloud.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
c. 确认镜像版本
当前最新稳定版是 v4.0.2。如果拉取失败,可以试试 v4.0.1 或 v4.0.0。
3.3 应用部署
bash
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml
kubectl apply -f class.yaml
4 验证部署
4.1 查看 Pod 状态
bash
kubectl get pods | grep nfs-client
期望输出:
nfs-client-provisioner-6bd7ff8964-gf9jv 1/1 Running 0 2m
4.2 查看 StorageClass
bash
kubectl get sc
期望输出:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE AGE
nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate 2m
5 测试RWX PVC
5.1 创建 RWX PVC
创建文件 test-pvc-rwx.yaml:
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-rwx-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-client
resources:
requests:
storage: 1Gi
应用:
bash
kubectl apply -f test-pvc-rwx.yaml
kubectl get pvc
期望输出:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
demo-rwx-pvc Bound pvc-e380223a-732e-4446-89e9-20af374a7803 1Gi RWX nfs-client 10s
5.2 创建两个 Pod 同时挂载
创建文件 test-pods-rwx.yaml:
yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-rwx-pod-1
spec:
containers:
- name: busybox
image: m.daocloud.io.localcache.registry:65000/docker.io/library/busybox:1.38.0
command: ["/bin/sh", "-c", "while true; do echo 'pod-1' >> /mnt/data/test.txt; sleep 5; done"]
volumeMounts:
- name: data
mountPath: /mnt/data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-rwx-pvc
---
apiVersion: v1
kind: Pod
metadata:
name: demo-rwx-pod-2
spec:
containers:
- name: busybox
image: m.daocloud.io.localcache.registry:65000/docker.io/library/busybox:1.38.0
command: ["/bin/sh", "-c", "while true; do echo 'pod-2' >> /mnt/data/test.txt; sleep 5; done"]
volumeMounts:
- name: data
mountPath: /mnt/data
volumes:
- name: data
persistentVolumeClaim:
claimName: demo-rwx-pvc
应用:
bash
kubectl apply -f test-pods-rwx.yaml
kubectl get pods -o wide
5.3 验证数据共享
进入任意一个 Pod 查看文件内容:
bash
kubectl exec -it demo-rwx-pod-1 -- cat /mnt/data/test.txt
你会看到 pod-1 和 pod-2 交替出现,证明 RWX 生效。
6 常见问题与解决
6.1 镜像拉取失败(ImagePullBackOff)
现象 :nfs-client-provisioner Pod 处于 ImagePullBackOff。
排查:
bash
kubectl describe pod -l app=nfs-client-provisioner | grep -E "Image:|Failed"
解决 :将 deployment.yaml 中的镜像地址替换为国内可访问的镜像源,例如:
yaml
image: docker.m.daocloud.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
6.2 Pod 一直 Pending
现象 :使用 RWX PVC 的 Pod 一直 Pending。
排查:
bash
kubectl describe pod <pod-name>
常见原因:
- PVC 未绑定:检查 PVC 状态,确保
STATUS为Bound。 - 节点资源不足:检查节点 CPU/内存。
- 节点污点:Master 节点有污点,Pod 需要容忍度。
6.3 挂载失败
现象 :Pod 事件中出现 mount failed 或 permission denied。
解决:
- 确认所有节点已安装
nfs-utils。 - 确认 NFS 服务器共享目录权限正确(
/data/k8s-nfs为777或合适的权限)。 - 确认
/etc/exports中no_root_squash已设置,并执行exportfs -ra。
7 总结
通过本文,我们完成了以下工作:
- 配置了 NFS 服务器 :在
rockylinux10-4上安装并启动了 NFS 服务,共享/data/k8s-nfs目录。 - 在所有 K8s 节点安装了 nfs-utils:确保 kubelet 可以挂载 NFS。
- 部署了 nfs-subdir-external-provisioner :实现了 NFS 存储的动态供应,并创建了
nfs-clientStorageClass。 - 测试了 RWX PVC :创建了
ReadWriteMany的 PVC,并用两个 Pod 同时挂载,验证了多节点读写共享。