一、环境清单
集群基础信息
| 项目 | 配置值 |
|---|---|
| 集群类型 | 单 Master + 3 Worker(1 主 3 从) |
| Kubernetes 版本 | v1.23.0 |
| 容器运行时 | Docker 26.1.4 |
| 网络插件 | Calico v3.22 |
| 操作系统 | CentOS 7.9 |
| 内核版本 | 3.10.0-1160.el7.x86_64 |
| Pod 网络段 | 10.244.0.0/16(Calico 默认) |
| Service 网络段 | 10.96.0.0/12(K8s 默认) |
| CPU | 4核 |
| 内存 | 8G |
| 机器名称 | 机器IP | 操作系统 | 角色 |
|---|---|---|---|
| k8s-master | 10.132.47.60 | Centos7.9 | master |
| k8s-node1 | 10.132.47.61 | Centos7.9 | Worker |
| k8s-node2 | 10.132.47.62 | Centos7.9 | Worker |
| k8s-node3 | 10.132.47.63 | Centos7.9 | Worker |
环境部署查阅我的这篇文章:Centos7.9 安装K8S 1master3node-CSDN博客
二、配置NFS
所有节点安装
bash
# CentOS/RHEL系统
yum install -y nfs-utils rpcbind
# Ubuntu/Debian系统
apt update && apt install -y nfs-kernel-server
创建共享目录
bash
# 创建共享目录(用于存储MySQL数据)
mkdir -p /data/nfs/mysql
# 设置目录权限(确保K8s节点能读写)
chmod -R 777 /data/nfs/mysql
chown -R nfsnobody:nfsnobody /data/nfs/mysql
配置共享规则
编辑 NFS 配置文件 /etc/exports,添加以下内容:
bash
# 格式:共享目录 K8s节点网段/子网掩码(rw,sync,no_root_squash,no_all_squash)
/data/nfs/mysql 10.132.47.60/24(rw,sync,no_root_squash,no_all_squash)
rw:读写权限sync:数据实时同步到磁盘(保证数据一致性)no_root_squash:允许 root 用户操作(避免 K8s Pod 权限问题)no_all_squash:保留用户身份(不映射为匿名用户)
配置服务
bash
# CentOS/RHEL系统
systemctl start rpcbind
systemctl start nfs-server
systemctl enable rpcbind
systemctl enable nfs-server
# Ubuntu/Debian系统
systemctl start nfs-kernel-server
systemctl enable nfs-kernel-server
# 使配置生效(修改exports后执行)
exportfs -r
# 验证共享配置
exportfs -v
开放防火墙规则
bash
# CentOS/RHEL(firewalld)
firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --permanent --add-service=mountd
firewall-cmd --reload
# 关闭防火墙(测试环境可选,生产环境不推荐)
# systemctl stop firewalld && systemctl disable firewalld
验证NFS
bash
# 所有节点查看NFS服务器的共享列表
showmount -e 10.132.47.60
挂载测试
bash
# 在K8s节点创建临时目录
mkdir -p /tmp/test-nfs
# 挂载NFS共享目录到临时目录
mount -t nfs 10.132.47.60:/data/nfs/mysql /tmp/test-nfs
# 测试读写:创建文件
touch /tmp/test-nfs/test.txt
# 查看文件是否同步到NFS服务器(可登录NFS服务器查看/data/nfs/mysql目录)
# 卸载临时挂载(测试完成后)
umount /tmp/test-nfs
三、部署 NFS 动态供应器(nfs-subdir-external-provisioner)
自动为 PVC 创建对应的 PV,并关联 NFS 共享目录,避免手动创建 PV 的繁琐。
创建部署清单 nfs-provisioner.yaml
yaml
# 1. 服务账户:给供应器授权访问 K8s API 的权限
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner # 服务账户名称
namespace: kube-system # 部署在系统命名空间,便于管理
---
# 2. 集群角色:定义供应器需要的权限(操作 PV、PVC、StorageClass 等)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner # 角色名称
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"] # 允许操作 PV
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"] # 允许操作 PVC
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"] # 允许查看 StorageClass
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"] # 允许生成事件(便于排障)
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"] # 允许获取服务信息
- apiGroups: ["extensions"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "delete", "update"] # 允许操作自身部署
---
# 3. 集群角色绑定:将角色权限绑定到服务账户
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-provisioner # 绑定名称
subjects:
- kind: ServiceAccount
name: nfs-provisioner # 关联上面创建的服务账户
namespace: kube-system
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner # 关联上面创建的角色
apiGroup: rbac.authorization.k8s.io
---
# 4. 部署供应器 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner # 部署名称
namespace: kube-system # 与服务账户同命名空间
spec:
replicas: 1 # 单副本(生产可根据需求调整)
selector:
matchLabels:
app: nfs-client-provisioner # 匹配 Pod 标签
strategy:
type: Recreate # 重建策略(更新时删除旧 Pod 再创建新的)
template:
metadata:
labels:
app: nfs-client-provisioner # Pod 标签
spec:
serviceAccountName: nfs-provisioner # 使用上面创建的服务账户
containers:
- name: nfs-client-provisioner # 容器名称
# 镜像:使用阿里云镜像,避免国外镜像拉取失败(适配 K8s v1.23)
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root # 挂载 NFS 共享目录到容器内
mountPath: /persistentvolumes # 容器内挂载点
env:
- name: PROVISIONER_NAME # 供应器名称,需与 StorageClass 一致
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER # NFS 服务器 IP(你的 master 节点 IP)
value: 10.132.47.60
- name: NFS_PATH # NFS 共享目录(之前创建的 /data/nfs/mysql)
value: /data/nfs/mysql
volumes:
- name: nfs-client-root # 定义 NFS 卷
nfs:
server: 10.132.47.60 # NFS 服务器 IP(同上)
path: /data/nfs/mysql # NFS 共享目录(同上)
启动
bash
kubectl apply -f nfs-provisioner.yaml
验证部署
bash
# 查看 Pod 是否运行(状态为 Running 表示成功)
kubectl get pods -n kube-system | grep nfs-client-provisioner
------------------------------------------------------------------------------------------------------------
# 输出示例:
[root@k8s-master nfs]# kubectl get pods -n kube-system | grep nfs-client-provisioner
nfs-client-provisioner-5bfb45b7b8-6xd5z 1/1 Running 0 29s
四、创建 StorageClass
定义动态供应的规则(如回收策略、是否允许扩容等),关联 NFS 供应器。
创建清单 storageclass.yaml
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage # StorageClass 名称,PVC 会引用此名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须与供应器的 PROVISIONER_NAME 一致
parameters:
archiveOnDelete: "false" # 删除 PVC 时是否归档数据(false 表示直接删除,避免残留文件)
reclaimPolicy: Delete # PV 回收策略(Delete:删除 PVC 时自动删除 PV;Retain:保留 PV 手动处理)
allowVolumeExpansion: true # 允许 PVC 扩容(需在 PVC 中修改 storage 请求)
volumeBindingMode: Immediate # 立即绑定(PVC 创建后立即分配 PV)
部署启动
bash
kubectl apply -f storageclass.yaml
验证 StorageClass
bash
kubectl get sc # sc 是 storageclasses 的缩写
------------------------------------------------------------------------------------------------------------
# 输出示例
[root@k8s-master nfs]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate true 24s
五、创建 PVC 测试动态供应
通过 PVC 请求存储,验证动态供应器是否自动创建对应的 PV 并绑定。
创建清单 test-mysql-pvc.yaml(模拟 MySQL 存储需求)
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-pvc # PVC 名称,供 Pod 引用
namespace: default # 可根据实际命名空间调整(如创建 mysql 命名空间)
spec:
accessModes:
- ReadWriteMany # NFS 支持多节点读写(RWX),适合 MySQL 主从等场景
resources:
requests:
storage: 5Gi # 请求 5GB 存储(根据实际需求调整)
storageClassName: nfs-storage # 指定使用上面创建的 StorageClass
部署启动
bash
kubectl apply -f test-mysql-pvc.yaml
验证PV/PVC绑定
bash
# 查看 PVC 状态(STATUS 为 Bound 表示绑定成功)
kubectl get pvc mysql-data-pvc -n default
------------------------------------------------------------------------------------------------------------
# 输出示例:
[root@k8s-master nfs]# kubectl get pvc mysql-data-pvc -n default
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-data-pvc Bound pvc-9ea1beb5-7e35-4955-a567-66eead2161c3 5Gi RWX nfs-storage 7m15s
[root@k8s-master nfs]# kubectl get pvc mysql-data-pvc -n default
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-data-pvc Bound pvc-9ea1beb5-7e35-4955-a567-66eead2161c3 5Gi RWX nfs-storage 10m
------------------------------------------------------------------------------------------------------------
# 查看自动创建的 PV(与 PVC 绑定,容量和访问模式一致)
kubectl get pv
------------------------------------------------------------------------------------------------------------
# 输出示例:
[root@k8s-master nfs]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-9ea1beb5-7e35-4955-a567-66eead2161c3 5Gi RWX Delete Bound default/mysql-data-pvc nfs-storage 4m25s
六、清理测试资源(可选)
bash
# 删除 PVC(会自动删除关联的 PV 和 NFS 子目录,因 reclaimPolicy 为 Delete)
kubectl delete pvc mysql-data-pvc -n default
# 如需删除 StorageClass 和供应器(谨慎操作,会影响依赖的资源)
kubectl delete sc nfs-storage
kubectl delete -f nfs-provisioner.yaml
七、可能遇到的问题
NFS 动态供应器的服务账户(nfs-provisioner)缺少创建 endpoints 资源的权限,导致 leader 选举失败(供应器需要通过 leader 选举确保单实例运行)
解决方法:补充 RBAC 权限(添加 endpoints 资源操作权限)
需要修改之前的 ClusterRole 配置,新增对 endpoints 资源的 create、update、patch 权限。
编辑集群角色(nfs-provisioner-runner)
bash
kubectl edit clusterrole nfs-provisioner-runner
在 rules 中添加以下内容(补充 endpoints 权限)
找到现有规则,在其中新增一段关于 endpoints 的配置,完整规则如下:
yaml
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: ["services", "endpoints"] # 保留原有的 services,新增 endpoints
verbs: ["get", "create", "update", "patch"] # 新增 create、update、patch 权限
- apiGroups: ["extensions"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
# 新增 leader 选举需要的 leases 权限(K8s v1.14+ 用 leases 替代 endpoints 做选举)
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "create", "update"]
直接替换完整的 ClusterRole 配置(避免手动编辑出错)
bash
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-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"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get", "create", "update", "patch"] # 补充 create/update/patch 权限
- apiGroups: ["extensions"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
- apiGroups: ["coordination.k8s.io"] # 新增 leases 权限(leader 选举必需)
resources: ["leases"]
verbs: ["get", "create", "update"]
EOF
存退出后,重启 NFS 供应器 Pod
bash
# 删除现有 Pod(Deployment 会自动重建)
kubectl delete pod -n kube-system nfs-client-provisioner-5bfb45b7b8-6xd5z
验证权限是否生效
bash
kubectl logs -n kube-system <新的 Pod 名称> # 新 Pod 名称可通过 kubectl get pods -n kube-system 查看
检查 PVC 状态
当供应器正常运行后,PVC 应自动绑定 PV:
bash
kubectl get pvc mysql-data-pvc -n default
八、清单整合
核心清单:NFS 动态供应器(含完整 RBAC 权限)
文件名:nfs-provisioner-complete.yaml(整合所有依赖资源,一次部署)
yaml
# 1. 服务账户:授权供应器访问 K8s API
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: kube-system
---
# 2. 集群角色:包含 leader 选举+存储操作完整权限(已修复 endpoints/leases 权限)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-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"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get", "create", "update", "patch"] # 解决 leader 选举权限问题
- apiGroups: ["extensions"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "create", "update"] # 兼容 K8s 新版选举机制
---
# 3. 集群角色绑定:关联服务账户与权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: kube-system
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
# 4. 动态供应器部署:关联 NFS 服务器配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-provisioner
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/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 # 与 StorageClass 供应器名称一致
- name: NFS_SERVER
value: 10.132.47.60 # 你的 NFS 服务器(master 节点)IP
- name: NFS_PATH
value: /data/nfs/mysql # 你的 NFS 共享目录
volumes:
- name: nfs-client-root
nfs:
server: 10.132.47.60
path: /data/nfs/mysql
验证用 PVC 清单(测试动态供应)
文件名:mysql-data-pvc.yaml(模拟 MySQL 持久化存储需求)
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-pvc
namespace: default # 可根据实际应用命名空间调整
spec:
accessModes:
- ReadWriteMany # NFS 支持多节点读写(适配 MySQL 主从等场景)
resources:
requests:
storage: 5Gi # 存储容量需求(可按需修改)
storageClassName: nfs-storage # 关联已存在的 StorageClass
部署验证
bash
#部署 NFS 动态供应器
kubectl apply -f nfs-provisioner-complete.yaml
#验证供应器运行状态
kubectl get pods -n kube-system | grep nfs-client-provisioner
#部署测试 PVC
kubectl apply -f mysql-data-pvc.yaml
#验证 PV/PVC 绑定
# 查看 PVC 状态(Bound 表示成功)
kubectl get pvc mysql-data-pvc -n default
# 查看自动创建的 PV
kubectl get pv
#最终验证 NFS 目录
ls /data/nfs/mysql
九、配置(方便记录学习)
集群基础信息
| 项目 | 配置值 |
|---|---|
| 集群类型 | 单 Master + 3 Worker(1 主 3 从) |
| Kubernetes 版本 | v1.23.0 |
| 容器运行时 | Docker 26.1.4 |
| 网络插件 | Calico v3.22 |
| 操作系统 | CentOS 7.9 |
| 内核版本 | 3.10.0-1160.el7.x86_64 |
| Pod 网络段 | 10.244.0.0/16(Calico 默认) |
| Service 网络段 | 10.96.0.0/12(K8s 默认) |
| CPU | 4 核 |
| 内存 | 8G |
节点信息
| 机器名称 | 机器 IP | 操作系统 | 角色 | 主要功能 |
|---|---|---|---|---|
| k8s-master | 10.132.47.60 | Centos7.9 | Master + NFS 服务器 | 集群控制面 + 提供 NFS 共享存储 |
| k8s-node1 | 10.132.47.61 | Centos7.9 | Worker | 运行应用 Pod + NFS 客户端 |
| k8s-node2 | 10.132.47.62 | Centos7.9 | Worker | 运行应用 Pod + NFS 客户端 |
| k8s-node3 | 10.132.47.63 | Centos7.9 | Worker | 运行应用 Pod + NFS 客户端 |
NFS 服务器配置(master 节点)
| 配置项 | 配置值 |
|---|---|
| 共享目录路径 | /data/nfs/mysql |
| 目录权限 | 777(读写执行) |
| 目录所属用户 / 组 | nfsnobody:nfsnobody |
| 共享规则(/etc/exports) | /data/nfs/mysql 10.132.47.0/24(rw,sync,no_root_squash,no_all_squash) |
| 依赖服务 | rpcbind、nfs-server(已开机自启) |
| 服务状态 | 运行中 |
NFS 动态供应器配置
| 配置项 | 配置值 |
|---|---|
| 供应器名称 | nfs-client-provisioner |
| 部署命名空间 | kube-system |
| 服务账户名称 | nfs-provisioner |
| 集群角色名称 | nfs-provisioner-runner |
| 集群角色绑定名称 | run-nfs-provisioner |
| 供应器镜像 | registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2 |
| 供应器标识(PROVISIONER_NAME) | k8s-sigs.io/nfs-subdir-external-provisioner |
| 关联 NFS 服务器 IP | 10.132.47.60 |
| 关联 NFS 共享目录 | /data/nfs/mysql |
| 运行副本数 | 1 |
| 容器挂载点 | /persistentvolumes |
| 核心权限 | PV/PVC/StorageClass 操作、endpoints/leases leader 选举权限 |
StorageClass 配置
| 配置项 | 配置值 |
|---|---|
| StorageClass 名称 | nfs-storage |
| 关联供应器 | k8s-sigs.io/nfs-subdir-external-provisioner |
| 回收策略 | Delete(删除 PVC 时自动删除 PV) |
| 允许扩容 | 是(allowVolumeExpansion: true) |
| 绑定模式 | Immediate(立即绑定) |
| 额外参数 | archiveOnDelete: "false"(删除 PVC 不归档数据) |
| 状态 | 可用(Available) |
PVC 配置(测试 / 业务用)
| 配置项 | 配置值 |
|---|---|
| PVC 名称 | mysql-data-pvc |
| 所属命名空间 | default |
| 关联 StorageClass | nfs-storage |
| 访问模式 | ReadWriteMany(RWX,多节点读写) |
| 申请存储容量 | 5Gi |
| 状态 | 待绑定 / 已绑定(根据实际部署结果更新) |
| 用途 | 模拟 MySQL 持久化存储 |
PV 配置(动态创建)
| 配置项 | 配置值 |
|---|---|
| PV 名称 | 自动生成(格式:pvc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) |
| 关联 PVC | default/mysql-data-pvc |
| 存储容量 | 5Gi(与 PVC 申请容量一致) |
| 访问模式 | ReadWriteMany(RWX) |
| 回收策略 | Delete(继承 StorageClass 配置) |
| 存储后端 | NFS(10.132.47.60:/data/nfs/mysql/[子目录]) |
| 动态创建触发条件 | PVC 创建后自动生成 |