跳到主要内容

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_squashall_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 来实现动态存储供应。