# 03-06-k3s 使用 NFS 存储 > 本文只讲 K3s 集群侧如何使用已安装好的 NFS。 ## 前置条件 - 已完成 `01-05-armv7-nfs服务安装.md` - 可从 K3s 节点访问 NFS 服务器与导出目录 ## 方式对比(从简单到复杂) | 项 | 方式 1:Pod 直接挂 NFS | 方式 2:静态 NFS(PV + PVC) | 方式 3:动态 NFS(选装 provisioner) | |---|---|---|---| | 复杂度 | 低 | 中 | 高 | | 最少 YAML 段数 | 1 段(Deployment/Pod) | 2 段(PV+PVC) | 通常 1 段 PVC(PV/目录动态生成) | | 目录创建 | 需手工提前创建 | 需手工提前创建 | 通常由 provisioner 自动创建子目录 | | 与应用耦合 | 高(写死 server/path) | 低(应用只引用 PVC) | 低(应用只引用 PVC) | | 适用场景 | 临时验证、概念验证(先跑通) | 稳定运行、团队协作 | 大规模/多团队、追求自动化 | ## 操作步骤 1. 按复杂度选择一种方式 2. 应用清单 3. 验证读写 > 结论:K3s 使用 NFS **不需要**先把 NFS 手工挂到每台节点主机;Pod/PVC 可直接挂远端 NFS。 ### 方式 1:Pod 直接挂 NFS(最简单,快速验证) > 说明:`nfs.path` 指向的目录(或子目录)需要在 NFS 服务端**提前创建并设置好权限**;K3s 不会自动在 NFS 服务端创建该目录。 > > 例如在 onecloud 上预创建子目录: > > ```bash > sudo mkdir -p /sdcard/app-a > sudo chown 1000:1000 /sdcard/app-a > sudo chmod 0770 /sdcard/app-a > ``` ```yaml apiVersion: apps/v1 # Deployment 使用的 API 版本 kind: Deployment # 资源类型:Deployment metadata: # 资源元信息 name: nfs-direct-demo # Deployment 名称 namespace: default # 命名空间 spec: # Deployment 规格 replicas: 1 # 副本数 selector: # Pod 选择器 matchLabels: # 标签匹配条件 app: nfs-direct-demo # 选择带 app=nfs-direct-demo 的 Pod template: # Pod 模板 metadata: # Pod 元信息 labels: # Pod 标签 app: nfs-direct-demo # Pod 标签值 spec: # Pod 规格 containers: # 容器列表 - name: app # 容器名称 image: nginx:alpine # 容器镜像 volumeMounts: # 容器内挂载点 - name: nfs-data # 引用下方 volumes 的卷名 mountPath: /usr/share/nginx/html # 挂载到容器内目录 volumes: # Pod 卷定义 - name: nfs-data # 卷名 nfs: # 直接使用 NFS 卷 server: # NFS 服务器地址(应用前替换) path: # NFS 导出目录或子目录(应用前替换) ``` ### 方式 2:静态 NFS(PV + PVC,推荐) **唯一真源**:[`ansible/files/03-06-nfs-demo/nfs-pv-pvc-demo.yaml`](../ansible/files/03-06-nfs-demo/nfs-pv-pvc-demo.yaml)。 > 为减少硬编码,示例清单已改为占位符:``、``。 > 应用前必须先替换(例如 `192.168.2.22`、`/sdcard`)。 > 静态 NFS 同样需要在服务端提前创建目录并设置权限;不会自动创建目录。 ### 方式 3:动态 NFS(选装 provisioner) 这是选装增强,不是 K3s 内置默认能力。常见组件是 `nfs-subdir-external-provisioner`。 ### 3.1 安装 provisioner(Helm) > 前提:NFS 服务端已可用(例如 `192.168.2.22:/sdcard`),且 K3s 节点网络可达。 ```bash # 1) 添加 chart 仓库 helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm repo update # 2) 安装(建议放到 kube-system) helm upgrade --install nfs-subdir-external-provisioner \ nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ -n kube-system --create-namespace \ --set nfs.server=192.168.2.22 \ --set nfs.path=/sdcard \ --set storageClass.name=nfs-client \ --set storageClass.defaultClass=false \ --set storageClass.reclaimPolicy=Delete \ --set storageClass.archiveOnDelete=true ``` 参数说明(最常改): - `nfs.server`:NFS 服务器地址 - `nfs.path`:NFS 导出根目录(provisioner 会在其下创建子目录) - `storageClass.name`:动态供给使用的 StorageClass 名称 - `archiveOnDelete`:删除 PVC 时是否归档目录(`true` 更稳妥,`false` 更干净) ### 3.2 验证 provisioner 与 StorageClass ```bash kubectl -n kube-system get pod -l app.kubernetes.io/name=nfs-subdir-external-provisioner -o wide kubectl get storageclass ``` ### 3.3 用动态 PVC 验证自动建卷 创建一个最小 PVC(示例): ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-dynamic-pvc-demo namespace: default spec: accessModes: - ReadWriteMany storageClassName: nfs-client resources: requests: storage: 5Gi ``` 应用并验证: ```bash kubectl apply -f /tmp/nfs-dynamic-pvc-demo.yaml kubectl get pvc nfs-dynamic-pvc-demo -n default kubectl get pv | grep nfs-dynamic-pvc-demo ``` 当 PVC `Bound` 后,通常可在 NFS 服务器的 `/sdcard` 下看到自动创建的子目录(命名规则由 provisioner 管理)。 ### 3.5 本次实机验证记录(ylc61 + onecloud) - `nfs-subdir-external-provisioner` 安装成功并 `Running` - `StorageClass nfs-client` 创建成功 - 动态 PVC `nfs-dynamic-pvc-demo` 成功 `Bound` - 自动创建 PV 成功,并在 NFS 服务端目录下生成子目录: - `/sdcard/default-nfs-dynamic-pvc-demo-pvc-` - 删除临时 PVC 后,PV 按 `Delete` 回收完成 > 备注:验证中曾出现过镜像拉取 `EOF`(偶发网络抖动),重试后官方镜像 `registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2` 已可正常拉取并启动。 ### 3.4 何时选动态 NFS - 目录数量多、变更频繁 - 希望减少手工维护 PV/PVC 对应关系 - 团队里希望应用只声明 PVC,平台统一管理 StorageClass ## 验证命令(若 YAML 在 manifests 目录,按实际路径选择其一复制执行) ```bash # 仓库根直接应用 # 先替换 ansible/files/03-06-nfs-demo/nfs-pv-pvc-demo.yaml 里的占位符 # -> 例如 192.168.2.22 # -> 例如 /sdcard kubectl apply -f ansible/files/03-06-nfs-demo/nfs-pv-pvc-demo.yaml ``` ```bash # 或默认路径(已拷贝到 manifests 时) kubectl apply -f /var/lib/rancher/k3s/server/manifests/nfs-pv-pvc.yaml kubectl get pv,pvc -A kubectl describe pv nfs-pv-demo ``` ```bash # 自定义 data-dir(如 /storage) kubectl apply -f /storage/server/manifests/nfs-pv-pvc.yaml kubectl get pv,pvc -A kubectl describe pv nfs-pv-demo ``` ```bash # 对方式 1(Pod 直挂)可这样验证 kubectl apply -f /tmp/nfs-direct-demo.yaml kubectl rollout status deploy/nfs-direct-demo --timeout=180s kubectl exec deploy/nfs-direct-demo -- sh -c 'echo nfs-direct-ok > /usr/share/nginx/html/nfs.txt && cat /usr/share/nginx/html/nfs.txt' ``` ## 预期 - PV/PVC 状态为 `Bound` - 业务 Pod 可读写挂载目录 ## 失败排查 - 检查 NFS 服务与导出目录权限 - 检查节点到 NFS 服务器网络 - 检查 `path` 与 `server` 配置是否正确 - 若报 `Permission denied`,回到 `01-05` 的 `root_squash` 权限章节,确认导出目录与业务 UID/GID 对齐 ## 下一步 - `03-05-k3s-local-path-pvc.md`:单副本应用用 K3s 自带 local-path 即可,无需 NFS - `05-06-openlist挂载网盘与自动备份.md`