跳到主要内容

K3s 使用 NFS Provisioner 实现动态存储供应 (Ubuntu 版本)

概述​

本文档详细介绍了如何在基于 Ubuntu 系统的 K3s 集群中配置和使用 NFS Subdir External Provisioner 来实现动态存储供应。通过此方案,您可以为 Kubernetes 工作负载提供持久化存储,而无需手动管理 PersistentVolumes (PV)。

NFS Subdir External Provisioner 是一个自动配置 NFS 存储卷的存储供应器,它能够根据 PersistentVolumeClaims (PVC) 动态创建 NFS 子目录并将其作为 PV 提供给应用程序使用。

目录​

  1. 前置条件
  2. NFS 服务器配置
  3. 部署 NFS Subdir External Provisioner
  4. 配置 StorageClass
  5. 验证部署
  6. 使用示例
  7. 最佳实践
  8. 故障排除

前置条件​

在开始部署 NFS Provisioner 之前,请确保满足以下条件:

  1. 已成功部署基于 Ubuntu 系统的 K3s 集群
  2. 具备集群管理员权限
  3. 准备一台可用的 NFS 服务器(可以是集群内的节点或独立服务器)
  4. 确保 K3s 节点能够访问 NFS 服务器
  5. 确保 K3s 客户端节点已安装 nfs-common 和 rpcbind 软件包
  6. 确保防火墙规则允许 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 服务器​

  1. 更新系统并安装 NFS 服务器软件包:
sudo apt update
sudo apt install -y nfs-kernel-server
  1. 创建共享目录并设置权限:
sudo mkdir -p /srv/nfs/k3s-storage
sudo chown nobody:nogroup /srv/nfs/k3s-storage
sudo chmod 777 /srv/nfs/k3s-storage
  1. 配置 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的端口连接
  1. 重启 NFS 服务:
sudo exportfs -ra
sudo systemctl restart nfs-kernel-server
sudo systemctl enable nfs-kernel-server
  1. 配置 UFW 防火墙(如果启用了UFW):
sudo ufw allow from 192.168.0.0/16 to any port nfs
sudo ufw allow ssh
  1. 验证 NFS 服务:
showmount -e localhost

应该能看到类似以下输出:

Export list for localhost:
/srv/nfs/k3s-storage *

部署 NFS Subdir External Provisioner​

方法一:使用 Helm 部署(推荐)​

  1. 添加 NFS Subdir External Provisioner Helm 仓库:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
  1. 创建命名空间(可选):
kubectl create namespace nfs-provisioner
  1. 部署 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 文件部署​

  1. 创建部署文件 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
  1. 部署 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. 安全配置​

  1. 限制 NFS 服务器访问范围:

    /srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
  2. 使用专用用户而非 root:

    /srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000)

2. 性能优化​

  1. 调整 NFS 挂载选项:

    volumes:
    - name: nfs-client-root
    nfs:
    server: YOUR_NFS_SERVER_IP
    path: /srv/nfs/k3s-storage
    readOnly: false
  2. 根据应用需求选择合适的访问模式:

    • ReadWriteOnce: 单个节点读写
    • ReadOnlyMany: 多节点只读
    • ReadWriteMany: 多节点读写(需要 NFS 支持)

3. 存储管理​

  1. 合理设置存储请求和限制:

    resources:
    requests:
    storage: 5Gi
    limits:
    storage: 10Gi
  2. 使用不同的 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

故障排除​

常见问题​

  1. PVC 一直处于 Pending 状态

    检查 StorageClass 是否正确配置:

    kubectl describe pvc YOUR_PVC_NAME

    检查 NFS Provisioner 日志:

    kubectl logs -n kube-system deployment/nfs-client-provisioner
  2. Pod 无法挂载 NFS 卷

    检查 NFS 服务器是否可达:

    showmount -e YOUR_NFS_SERVER_IP

    检查防火墙设置是否允许 NFS 端口通信。

  3. 权限问题

    检查 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

日志分析​

如果遇到问题,请重点检查以下日志:

  1. NFS Provisioner 日志:

    kubectl logs -n kube-system deployment/nfs-client-provisioner
  2. 相关事件:

    kubectl get events --sort-by=.metadata.creationTimestamp
  3. PVC 详细信息:

    kubectl describe pvc YOUR_PVC_NAME

通过以上步骤,您应该能够在基于 Ubuntu 系统的 K3s 集群中成功配置和使用 NFS Subdir External Provisioner 来实现动态存储供应。