Skip to content

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共享卷:

bash
sudo apt update
sudo apt install -y nfs-common rpcbind

安装完成后,可以通过以下命令验证安装:

bash
# 检查NFS客户端服务状态
sudo systemctl status rpcbind

# 验证NFS工具是否可用
showmount --help

注意:虽然rpcbind服务在某些情况下不是必需的,但为了确保兼容性,建议在所有K3s节点上都安装。

在 Ubuntu 上配置 NFS 服务器 ​

  1. 更新系统并安装 NFS 服务器软件包:
bash
sudo apt update
sudo apt install -y nfs-kernel-server
  1. 创建共享目录并设置权限:
bash
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 文件:

bash
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 服务:
bash
sudo exportfs -ra
sudo systemctl restart nfs-kernel-server
sudo systemctl enable nfs-kernel-server
  1. 配置 UFW 防火墙(如果启用了UFW):
bash
sudo ufw allow from 192.168.0.0/16 to any port nfs
sudo ufw allow ssh
  1. 验证 NFS 服务:
bash
showmount -e localhost

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

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

部署 NFS Subdir External Provisioner ​

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

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

配置 StorageClass ​

创建默认 StorageClass ​

如果您希望将 NFS Provisioner 设置为默认存储类,可以创建如下 StorageClass:

bash
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:

yaml
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 ​

bash
kubectl apply -f storageclass.yaml

验证部署 ​

查看 ​

bash
kubectl get pods,deploy,sc -n nfs-provisioner

检查 Provisioner Pod 状态 ​

bash
kubectl get pods -n kube-system | grep nfs-client-provisioner

应该看到类似以下输出:

text
nfs-client-provisioner-xxxxxx-xxxxx   1/1     Running   0          5m

检查 StorageClass ​

bash
kubectl get storageclass

应该能看到我们创建的 nfs-client 存储类。

测试动态供应 ​

创建一个测试 PVC:

yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs-client

应用配置:

bash
kubectl apply -f test-pvc.yaml

检查 PVC 状态:

bash
kubectl get pvc test-claim

应该看到 PVC 状态为 Bound,表示已成功绑定到 PV。

查看创建的 PV:

bash
kubectl get pv

应该能看到一个自动创建的 PV,其存储源为 NFS。

使用示例 ​

部署使用 NFS 存储的应用 ​

创建一个使用 NFS 存储的简单应用示例:

yaml
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

应用配置:

bash
kubectl apply -f nginx-example.yaml

验证应用状态:

bash
kubectl get pods
kubectl get pvc

测试数据持久化 ​

进入容器并创建一个测试文件:

bash
kubectl exec -it deployment/nginx-deployment -- /bin/bash
echo "Hello NFS Storage!" > /usr/share/nginx/html/index.html
exit

删除并重新创建 Pod:

bash
kubectl delete pod -l app=nginx

等待新 Pod 启动后,再次检查文件是否存在:

bash
kubectl exec -it deployment/nginx-deployment -- cat /usr/share/nginx/html/index.html

应该能看到之前创建的文件内容,证明数据持久化成功。

最佳实践 ​

1. 安全配置 ​

  1. 限制 NFS 服务器访问范围:
bash
   /srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)
  1. 使用专用用户而非 root:
text
   /srv/nfs/k3s-storage 192.168.1.0/24(rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=1000)

2. 性能优化 ​

  1. 调整 NFS 挂载选项:

    yaml
    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. 合理设置存储请求和限制:

    yaml
    resources:
      requests:
        storage: 5Gi
      limits:
        storage: 10Gi
  2. 使用不同的 StorageClass 满足不同应用需求:

    高性能存储类:

    yaml
    # 高性能存储类
    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

    归档存储类:

    yaml
    # 归档存储类
    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 是否正确配置:

    bash
    kubectl describe pvc YOUR_PVC_NAME

    检查 NFS Provisioner 日志:

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

    检查 NFS 服务器是否可达:

    bash
    showmount -e YOUR_NFS_SERVER_IP

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

  3. 权限问题

    检查 NFS 共享目录权限:

    bash
    ls -la /srv/nfs/k3s-storage

    确保设置了正确的 no_root_squash 或 all_squash 选项。

诊断命令 ​

bash
# 检查 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 日志:

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

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

    bash
    kubectl describe pvc YOUR_PVC_NAME

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