K3s 使用 NFS Provisioner 实现动态存储供应 (Ubuntu 版本)
概述
本文档详细介绍了如何在基于 Ubuntu 系统的 K3s 集群中配置和使用 NFS Subdir External Provisioner 来实现动态存储供应。通过此方案,您可以为 Kubernetes 工作负载提供持久化存储,而无需手动管理 PersistentVolumes (PV)。
NFS Subdir External Provisioner 是一个自动配置 NFS 存储卷的存储供应器,它能够根据 PersistentVolumeClaims (PVC) 动态创建 NFS 子目录并将其作为 PV 提供给应用程序使用。
目录
前置条件
在开始部署 NFS Provisioner 之前,请确保满足以下条件:
- 已成功部署基于 Ubuntu 系统的 K3s 集群
- 具备集群管理员权限
- 准备一台可用的 NFS 服务器(可以是集群内的节点或独立服务器)
- 确保 K3s 节点能够访问 NFS 服务器
- 确保 K3s 客户端节点已安装 nfs-common 和 rpcbind 软件包
- 确保防火墙规则允许 NFS 相关端口通信(通常为 111, 2049, 以及挂载端口)
NFS 服务器配置
在K3s集群中,NFS服务器可以部署在集群外部或者作为集群中的一个节点。无论采用哪种方式,都需要确保所有K3s节点都能够访问NFS服务器。
在 K3s 客户端节点安装 NFS 客户端工具
在K3s集群的所有节点(包括master和worker节点)上,都需要安装NFS客户端工具,以便能够挂载NFS共享卷:
sudo apt update
sudo apt install -y nfs-common rpcbind
安装完成后,可以通过以下命令验证安装:
# 检查NFS客户端服务状态
sudo systemctl status rpcbind
# 验证NFS工具是否可用
showmount --help
注意:虽然rpcbind服务在某些情况下不是必需的,但为了确保兼容性,建议在所有K3s节点上都安装。
在 Ubuntu 上配置 NFS 服务器
- 更新系统并安装 NFS 服务器软件包:
sudo apt update
sudo apt install -y nfs-kernel-server
- 创建共享目录并设置权限:
sudo mkdir -p /srv/nfs/k3s-storage
sudo chown nobody:nogroup /srv/nfs/k3s-storage
sudo chmod 777 /srv/nfs/k3s-storage
- 配置 NFS 共享:
编辑 /etc/exports 文件:
sudo tee -a /etc/exports <<EOF
/srv/nfs/k3s-storage *(rw,sync,no_subtree_check,no_root_squash,insecure)
EOF
参数说明:
*(rw,sync,no_subtree_check,no_root_squash,insecure):*: 允许所有客户端访问(生产环境中应指定具体IP)rw: 允许读写访问sync: 同步写入磁盘no_subtree_check: 要用子树检查以提高性能no_root_squash: 允许客户端以root身份写入insecure: 允许使用大于1024的端口连接
- 重启 NFS 服务:
sudo exportfs -ra
sudo systemctl restart nfs-kernel-server
sudo systemctl enable nfs-kernel-server
- 配置 UFW 防火墙(如果启用了UFW):
sudo ufw allow from 192.168.0.0/16 to any port nfs
sudo ufw allow ssh
- 验证 NFS 服务:
showmount -e localhost
应该能看到类似以下输出 :
Export list for localhost:
/srv/nfs/k3s-storage *
部署 NFS Subdir External Provisioner
方法一:使用 Helm 部署(推荐)
- 添加 NFS Subdir External Provisioner Helm 仓库:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
- 创建命名空间(可选):
kubectl create namespace nfs-provisioner
- 部署 NFS Subdir External Provisioner:
helm upgrade --install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace=nfs-provisioner \
--create-namespace \
--set image.repository=willdockerhub/nfs-subdir-external-provisioner \
--set image.tag=v4.0.2 \
--set replicaCount=2 \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=true \
--set nfs.server=192.168.192.250 \
--set nfs.path=/fs/1000/nfs/NFSData/k3s-platform-storage \
--set podSecurityContext.runAsUser=999 \
--set podSecurityContext.fsGroup=999
方法二:使用 YAML 文件部署
- 创建部署文件
nfs-provisioner.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- 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
namespace: kube-system
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-system
labels:
app: nfs-client-provisioner
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.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
- name: NFS_SERVER
value: YOUR_NFS_SERVER_IP
- name: NFS_PATH
value: /fs/1000/nfs/NFSData/k3s-platform-storage
volumes:
- name: nfs-client-root
nfs:
server: YOUR_NFS_SERVER_IP
path: /fs/1000/nfs/NFSData/k3s-platform-storage
- 部署 NFS Provisioner:
kubectl apply -f nfs-provisioner.yaml
配置 StorageClass
创建默认 StorageClass
如果您希望将 NFS Provisioner 设置为默认存储类,可以创建如下 StorageClass:
sudo tee storageclass.yaml << 'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
server: 192.168.192.250
path: /fs/1000/nfs/NFSData/kk3s-platform-storage
onDelete: delete
EOF
创建非默认 StorageClass
如果您不想将 NFS Provisioner 设置为默认存储类,可以创建如下 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
server: 192.168.192.250
path: /fs/1000/nfs/NFSData/k3s-platform-storage
onDelete: delete
参数说明:
onDelete: 定义删除 PVC 时的行为delete: 删除 PVC 时同时删除 NFS 子目录retain: 删除 PVC 时保留 NFS 子目录
应用 StorageClass
kubectl apply -f storageclass.yaml
验证部署
查看
kubectl get pods,deploy,sc -n nfs-provisioner
检查 Provisioner Pod 状态
kubectl get pods -n kube-system | grep nfs-client-provisioner
应该看到类似以下输出:
nfs-client-provisioner-xxxxxx-xxxxx 1/1 Running 0 5m
检查 StorageClass
kubectl get storageclass
应该能看到我们创建的 nfs-client 存储类。
测试动态供应
创建一个测试 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: nfs-client
应用配置:
kubectl apply -f test-pvc.yaml
检查 PVC 状态:
kubectl get pvc test-claim
应该看到 PVC 状态为 Bound,表示已成功绑定到 PV。
查看创建的 PV:
kubectl get pv
应该能看到一个自动创建的 PV,其存储源为 NFS。
使用示例
部署使用 NFS 存储的应用
创建一个使用 NFS 存储的简单应用示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
storageClassName: nfs-client
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-storage
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-storage
persistentVolumeClaim:
claimName: nginx-pvc
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort
应用配置:
kubectl apply -f nginx-example.yaml
验证应用状态:
kubectl get pods
kubectl get pvc
测试数据持久化
进入容器并创建一个测试文件:
kubectl exec -it deployment/nginx-deployment -- /bin/bash
echo "Hello NFS Storage!" > /usr/share/nginx/html/index.html
exit
删除并重新创建 Pod:
kubectl delete pod -l app=nginx
等待新 Pod 启动后,再次检查文件是否存在:
kubectl exec -it deployment/nginx-deployment -- cat /usr/share/nginx/html/index.html
应该能看到之前创建的文件内容,证明数据持久化成功。
最佳实践
1. 安全配置
- 限制 NFS 服务器访问范围:
/srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
- 使用专用用户而非 root:
/srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000)
2. 性能优化
-
调整 NFS 挂载选项:
volumes:
- name: nfs-client-root
nfs:
server: YOUR_NFS_SERVER_IP
path: /srv/nfs/k3s-storage
readOnly: false -
根据应用需求选择合适的访问模式:
ReadWriteOnce: 单个节点读写ReadOnlyMany: 多节点只读ReadWriteMany: 多节点读写(需要 NFS 支持)
3. 存储管理
-
合理设置存储请求和限制:
resources:
requests:
storage: 5Gi
limits:
storage: 10Gi -
使用不同的 StorageClass 满足不同应用需求:
高性能存储类:
# 高性能存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-fast
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
server: YOUR_NFS_SERVER_IP
path: /srv/nfs/fast-storage归档存储类:
# 归档存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-archive
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
server: YOUR_NFS_SERVER_IP
path: /srv/nfs/archive-storage
故障排除
常见问题
-
PVC 一直处于 Pending 状态
检查 StorageClass 是否正确配置:
kubectl describe pvc YOUR_PVC_NAME检查 NFS Provisioner 日志:
kubectl logs -n kube-system deployment/nfs-client-provisioner -
Pod 无法挂载 NFS 卷
检查 NFS 服务器是否可达:
showmount -e YOUR_NFS_SERVER_IP检查防火墙设置是否允许 NFS 端口通信。
-
权限问题
检查 NFS 共享目录权限:
ls -la /srv/nfs/k3s-storage确保设置了正确的
no_root_squash或all_squash选项。
诊断命令
# 检查 NFS Provisioner Pod 状态
kubectl get pods -n kube-system | grep nfs
# 查看 NFS Provisioner 日志
kubectl logs -n kube-system deployment/nfs-client-provisioner
# 检查 StorageClass
kubectl get storageclass
# 检查 PVC 状态
kubectl describe pvc YOUR_PVC_NAME
# 检查 PV 状态
kubectl describe pv YOUR_PV_NAME
# 检查 NFS 服务器导出的共享
showmount -e YOUR_NFS_SERVER_IP
# 在节点上测试 NFS 挂载
mkdir /tmp/test-nfs
mount -t nfs YOUR_NFS_SERVER_IP:/srv/nfs/k3s-storage /tmp/test-nfs
umount /tmp/test-nfs
rmdir /tmp/test-nfs
日志分析
如果遇到问题,请重点检查以下日志:
-
NFS Provisioner 日志:
kubectl logs -n kube-system deployment/nfs-client-provisioner -
相关事件:
kubectl get events --sort-by=.metadata.creationTimestamp -
PVC 详细信息:
kubectl describe pvc YOUR_PVC_NAME
通过以上步骤,您应该能够在基于 Ubuntu 系统的 K3s 集群中成功配置和使用 NFS Subdir External Provisioner 来实现动态存储供应。