在 Kubernetes 中实现 NFS 动态存储供应:生产级实践指南

简介: 本文详解如何在Kubernetes生产环境中基于NFS实现动态存储供应,涵盖NFS服务器部署、`nfs-subdir-external-provisioner`集成、StorageClass优化配置及离线环境下的Harbor私有镜像管理方案,提供完整YAML模板与安全实践,助力构建稳定高效的共享存储体系。

在 Kubernetes 中实现 NFS 动态存储供应:生产级实践指南

在 Kubernetes 环境中,持久化存储是支撑有状态应用的关键基础设施。NFS(Network File System)作为一种成熟、简单且广泛支持的共享文件系统,常被用于开发测试环境或对高可用性要求不极端苛刻的生产场景。然而,Kubernetes 原生并不支持 NFS 的动态供应(Dynamic Provisioning),这意味着管理员需要手动创建 PersistentVolume(PV)对象,这在大规模集群中是不可持续的。

本文将详细介绍如何利用社区项目 nfs-subdir-external-provisioner,在 Kubernetes 集群中实现 NFS 存储的动态供应,并提供一套完整的、可直接用于生产环境的 YAML 模板和最佳实践,同时涵盖NFS 服务器部署离线环境镜像管理自建 Harbor 私有仓库的集成方案。

一、NFS 服务器部署与配置

在开始 Kubernetes 集成之前,必须先准备一个稳定、安全的 NFS 服务器。以下是基于 Ubuntu 22.04 的详细部署步骤。

1.1 安装 NFS 服务端

在一台专用服务器(例如 192.168.1.10)上执行以下操作:

# 更新系统并安装 NFS 服务端
sudo apt update
sudo apt install -y nfs-kernel-server

# 创建用于 Kubernetes 的共享目录
sudo mkdir -p /data/k8s/monitor
sudo chown nobody:nogroup /data/k8s/monitor
sudo chmod 777 /data/k8s/monitor

1.2 配置共享目录

编辑 /etc/exports 文件,定义共享策略:

# /etc/exports
/data/k8s/monitor 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)

参数说明

  • 192.168.1.0/24:允许访问的客户端网段,请替换为你的 Kubernetes 集群节点所在网段。
  • rw:读写权限。
  • sync:同步写入磁盘,保证数据一致性,但性能略低。如需更高性能,可改为 async,但有数据丢失风险。
  • no_subtree_check:禁用子树检查,提升性能。
  • no_root_squash:允许 root 用户保留其权限。这是关键选项,因为容器内进程可能以 root 身份运行。

1.3 启动并验证 NFS 服务

# 重新加载 exports 配置
sudo exportfs -ra

# 重启 NFS 服务
sudo systemctl restart nfs-kernel-server

# 设置开机自启
sudo systemctl enable nfs-kernel-server

# 验证本地导出
sudo exportfs -v

1.4 配置防火墙

确保 NFS 相关端口对 Kubernetes 节点开放:

# Ubuntu (UFW)
sudo ufw allow from 192.168.1.0/24 to any port nfs

# 或者开放具体端口
sudo ufw allow 2049/tcp
sudo ufw allow 111/tcp
sudo ufw allow 111/udp

1.5 从 Kubernetes 节点验证连接

在任意一个 Kubernetes 工作节点上执行:

# 安装 NFS 客户端
sudo apt install -y nfs-common

# 测试挂载
sudo showmount -e 192.168.1.10
# 应输出: /data/k8s/monitor 192.168.1.0/24

# 手动挂载测试(可选)
sudo mkdir -p /mnt/test-nfs
sudo mount -t nfs 192.168.1.10:/data/k8s/monitor /mnt/test-nfs
echo "test" | sudo tee /mnt/test-nfs/test.txt
sudo umount /mnt/test-nfs

如果以上步骤均成功,说明 NFS 服务器已准备就绪。

二、核心概念与工作原理

2.1 为什么需要动态供应?

在 Kubernetes 中,用户通过 PersistentVolumeClaim(PVC)来申请存储资源。静态供应模式下,集群管理员必须预先创建好 PV,其大小、访问模式等属性必须与 PVC 请求精确匹配。这种方式在小型环境中可行,但在多租户、多应用的动态环境中,会带来巨大的管理负担。

动态供应通过 StorageClass 对象解耦了存储的定义与使用。用户只需在 PVC 中指定所需的 storageClassName,一个名为 Provisioner 的控制器便会自动创建底层存储(如云盘、NFS 子目录)并生成对应的 PV 对象,从而实现“按需分配”。

2.2 nfs-subdir-external-provisioner 是什么?

nfs-subdir-external-provisioner 是一个由 Kubernetes SIG Storage 维护的外部供应器(External Provisioner)。它本身不提供 NFS 服务,而是作为一个智能代理,利用你已有的 NFS 服务器来实现动态供应。

其工作原理如下:

  1. 监听事件:Provisioner 以 Deployment 形式运行在集群中,监听所有 PVC 的创建事件。
  2. 匹配 StorageClass:当发现 PVC 的 storageClassName 与自身管理的 StorageClass 匹配时,触发供应逻辑。
  3. 创建子目录:Provisioner 在 NFS 服务器的根共享目录下,创建一个以 {namespace}-{pvc-name}-{pv-name} 命名的子目录。
  4. 注册 PV:向 Kubernetes API Server 注册一个新的 PV 对象,其后端指向刚刚创建的 NFS 子目录。
  5. 绑定 PVC:Kubernetes 的持久卷控制器将新创建的 PV 与原始 PVC 绑定。

整个过程对用户透明,用户只需像使用云原生存储一样声明 PVC 即可。

三、生产级部署模板

以下是一套经过验证的、可直接应用于生产环境的 YAML 模板。请根据你的实际环境(NFS 服务器地址、共享路径等)修改相应的值。

3.1 RBAC 权限配置 (01-nfs-rbac.yaml)

此文件定义了 Provisioner 运行所需的最小权限集,包括操作 PV/PVC、读取 StorageClass 以及进行 Leader 选举的能力。

# File: nfs-on-k8s/01-nfs-rbac.yaml
# 创建专用的 ServiceAccount
apiVersion: v1
kind: ServiceAccount
meta
  name: nfs-client-provisioner
  namespace: nfs-provisioner
---
# 定义集群角色,授予操作持久化资源的权限
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
meta
  name: nfs-client-provisioner-runner
rules:
  - 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"]
---
# 将集群角色绑定到 ServiceAccount
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
meta
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-provisioner
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
# 定义命名空间内的角色,用于 Leader 选举
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
meta
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
# 将角色绑定到 ServiceAccount
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
meta
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

3.2 Provisioner 控制器部署 (02-nfs-deployment.yaml)

这是核心组件。请特别注意 NFS_SERVERNFS_PATH 的值,它们必须与你的 NFS 服务器配置完全一致。

# File: nfs-on-k8s/02-nfs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
meta
  name: nfs-client-provisioner
  namespace: nfs-provisioner
spec:
  replicas: 1
  # 使用 Recreate 策略确保任何时候只有一个实例在运行
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    meta
      labels:
        app: nfs-client-provisioner
    spec:
      # 使用前面创建的 ServiceAccount
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          # 【关键】在离线或私有仓库环境中,替换为你的镜像地址
          image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          # 关键:在离线环境中必须设置为 IfNotPresent
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-root
              # Provisioner 将在此路径下创建子目录
              mountPath: /persistentvolumes
          env:
            # PROVISIONER_NAME 必须与 StorageClass 中的 provisioner 字段一致
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs
            - name: NFS_SERVER
              value: 192.168.1.10 # 替换为你的 NFS 服务器 IP
            - name: NFS_PATH
              value: /data/k8s/monitor # 替换为你的 NFS 共享路径
      volumes:
        - name: nfs-root
          nfs:
            server: 192.168.1.10 # 与上面的 NFS_SERVER 保持一致
            path: /data/k8s/monitor # 与上面的 NFS_PATH 保持一致

3.3 生产级 StorageClass 定义 (03-nfs-storageclass.yaml)

此文件定义了名为 nfs-client-prod 的存储类,包含了生产环境所需的关键参数。

# File: nfs-on-k8s/03-nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
meta
  name: nfs-client-prod
provisioner: k8s-sigs.io/nfs
parameters:
  # 【关键】数据保留策略:PVC删除时重命名目录而非直接删除
  archiveOnDelete: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
allowVolumeExpansion: false
mountOptions:
  - vers=4.1
  - noatime
  - nodiratime
  - hard
  - intr
  - nconnect=8

3.4 测试用例 (test-pvc.yamltest-pod.yaml)

用于验证整个动态供应流程是否正常工作。

# File: nfs-on-k8s/test-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
meta
  name: test-nfs-pvc
spec:
  # 引用我们创建的 StorageClass
  storageClassName: nfs-client-prod
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
# File: nfs-on-k8s/test-pod.yaml
apiVersion: v1
kind: Pod
meta
  name: test-nfs-pod
spec:
  containers:
    - name: nginx
      image: nginx:1.29.4
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: data
      # 挂载前面创建的 PVC
      persistentVolumeClaim:
        claimName: test-nfs-pvc

四、生产环境 StorageClass 参数详解

在生产环境中,StorageClass 的配置需要优先考虑数据安全系统稳定性

  • archiveOnDelete: "true":这是最重要的数据保护机制。当 PVC 被删除时,NFS 子目录会被重命名为 archived-*,而不是直接删除,有效防止了因误操作导致的数据永久丢失。
  • reclaimPolicy: Retain:将数据删除的最终控制权交给存储管理员。即使 PV 对象被删除,后端数据依然保留,为恢复操作提供了最后的机会。
  • volumeBindingMode: WaitForFirstConsumer:延迟 PV 的绑定,直到使用该 PVC 的 Pod 被调度。这可以避免在集群初始化阶段就创建大量无用的 PV,优化了资源分配。
  • allowVolumeExpansion: false:NFS 本身没有卷大小的概念,所谓的“扩容”只是修改了元数据,容易导致应用层和存储层的认知不一致。在没有完善的配额管理前,禁用此功能更为安全。
  • mountOptions
    • vers=4.1:使用更健壮的 NFS v4.1 协议。
    • noatime, nodiratime:禁止更新访问时间戳,提升 I/O 性能。
    • hard, intr:保证数据一致性的同时,允许请求被中断。
    • nconnect=8:建立多个 TCP 连接以并行传输数据,大幅提升吞吐量。

五、部署与验证流程

5.1 应用配置

按照顺序应用上述 YAML 文件:

# 1. 创建命名空间
kubectl create namespace nfs-provisioner

# 2. 应用 RBAC
kubectl apply -f nfs-on-k8s/01-nfs-rbac.yaml

# 3. 部署 Provisioner
kubectl apply -f nfs-on-k8s/02-nfs-deployment.yaml

# 4. 创建 StorageClass
kubectl apply -f nfs-on-k8s/03-nfs-storageclass.yaml

5.2 验证 Provisioner

确保 Provisioner Pod 处于 Running 状态:

kubectl get pods -n nfs-provisioner

5.3 执行测试

# 创建测试 PVC
kubectl apply -f nfs-on-k8s/test-pvc.yaml

# 创建测试 Pod
kubectl apply -f nfs-on-k8s/test-pod.yaml

# 验证
kubectl get pvc test-nfs-pvc # 应为 Bound
kubectl exec test-nfs-pod -- ls /usr/share/nginx/html

同时,登录你的 NFS 服务器,检查共享目录下是否生成了新的子目录。

六、离线与私有化环境的镜像管理策略

在企业内网、安全隔离或无外网访问的环境中,无法直接从 registry.k8s.io 等公共仓库拉取镜像。我们需要采用两种主要策略来解决这个问题:本地预加载自建私有仓库

6.1 策略一:本地预加载(适用于小规模或临时环境)

此方法将镜像直接导入到每个 Kubernetes 节点的容器运行时中。

步骤 1:准备镜像
在一个有网络的机器上,拉取并保存镜像:

docker pull registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker save registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 -o nfs-provisioner.tar

步骤 2:分发并导入
.tar 文件复制到所有 Kubernetes 工作节点,并导入到 containerd:

# 导入到 k8s.io 命名空间
sudo ctr -n k8s.io images import nfs-provisioner.tar

步骤 3:验证

crictl images | grep nfs-subdir-external-provisioner

优点:简单直接,无需额外基础设施。
缺点:难以维护和扩展,每次更新镜像都需要手动同步到所有节点。

6.2 策略二:自建 Harbor 私有仓库(推荐用于生产环境)

Harbor 是一个开源的企业级 Docker Registry 项目,提供了权限管理、漏洞扫描、镜像复制等高级功能,是管理私有镜像的理想选择。

步骤 1:部署 Harbor
可以使用 Helm Chart 或 Docker Compose 在内网部署 Harbor。假设你的 Harbor 地址为 harbor.local.com

步骤 2:推送镜像到 Harbor

# 重新打标签
docker tag registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 harbor.local.com/library/nfs-subdir-external-provisioner:v4.0.2

# 登录并推送
docker login harbor.local.com
docker push harbor.local.com/library/nfs-subdir-external-provisioner:v4.0.2

步骤 3:配置 Kubernetes 节点信任 Harbor(如果使用 HTTPS 自签名证书)
将 Harbor 的 CA 证书添加到所有 Kubernetes 节点的信任库中。

步骤 4:修改 Deployment 配置
02-nfs-deployment.yaml 中的镜像地址指向你的私有仓库,并移除 imagePullPolicy: IfNotPresent(因为现在可以从私有仓库动态拉取):

containers:
  - name: nfs-client-provisioner
    image: harbor.local.com/library/nfs-subdir-external-provisioner:v4.0.2
    # imagePullPolicy: IfNotPresent # 可注释掉,使用默认策略

步骤 5:配置 ImagePullSecrets(如果 Harbor 需要认证)

# 创建 Secret
kubectl create secret docker-registry harbor-secret \
  --docker-server=harbor.local.com \
  --docker-username=admin \
  --docker-password=yourpassword \
  -n nfs-provisioner

# 在 Deployment 的 spec.template.spec 下添加
imagePullSecrets:
  - name: harbor-secret

优点

  • 集中管理:所有镜像统一存储,便于版本控制和审计。
  • 自动化:CI/CD 流水线可以直接推送和拉取镜像。
  • 安全性:支持基于角色的访问控制(RBAC)和内容信任。
  • 可扩展:支持跨数据中心的镜像复制。

七、总结与注意事项

通过 nfs-subdir-external-provisioner,我们可以轻松地为 Kubernetes 集群赋予 NFS 动态供应能力。对于镜像管理,自建 Harbor 私有仓库是生产环境的最佳选择,它提供了集中化、安全且可扩展的解决方案。

然而,在生产环境中使用 NFS 仍需谨慎:

  • 单点故障:NFS 服务器本身是一个单点。对于关键业务,应考虑部署高可用的 NFS 集群(例如使用 DRBD + Pacemaker 或 NFS-Ganesha)。
  • 性能瓶颈:NFS 的性能受限于网络带宽和服务器 I/O 能力,不适合高吞吐或低延迟的应用场景。
  • 权限管理:需要仔细规划 NFS 导出选项(如 no_root_squash)和容器内的用户 ID(UID),以避免权限问题。
  • 备份策略:务必为 NFS 服务器上的数据制定定期备份计划。

尽管如此,对于日志收集、配置共享、开发测试等场景,NFS 动态供应仍然是一个高效、经济的解决方案。

七、参考资源

nfs-subdir-external-provisioner

文章分类标签

Kubernetes, DevOps, SRE, 存储, NFS, 容器化, 运维自动化, 云原生, Harbor, 镜像仓库

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
20天前
|
Kubernetes Cloud Native 安全
一文掌握k8s容器的生命周期
容器生命周期钩子是Kubernetes核心机制,支持在容器启动后(postStart)和终止前(preStop)执行自定义逻辑,实现服务注册、优雅停机等操作。结合启动命令,可精细控制应用生命周期,提升云原生应用稳定性与可靠性。
141 4
一文掌握k8s容器的生命周期
|
20天前
|
资源调度 Kubernetes 监控
一文掌握k8s容器的资源限制
在Kubernetes中,合理设置容器的资源请求与限制可保障集群资源高效利用。通过定义CPU和内存的requests与limits,防止资源滥用,提升应用稳定性。结合命名空间配额与工具如xkube,可实现多集群统一管理与可视化配置,优化资源调度。
170 3
|
存储 数据库
掌握GitLab数据备份与恢复:全面操作指南
【10月更文挑战第11天】 GitLab作为一个流行的开源代码仓库管理系统,其数据的安全性和完整性至关重要。本文将详细介绍GitLab数据的备份与恢复流程,帮助用户掌握如何保护和管理他们的GitLab实例。
1607 0
|
移动开发 前端开发 Java
使用ipaguard插件对Spring Boot程序进行代码混淆
使用ipaguard插件对Spring Boot程序进行代码混淆
543 0
|
2月前
|
Kubernetes 数据可视化 Perl
【k8s-1.34.2安装部署】八.metric-server-0.8.0安装
本章介绍metrics-server的安装与配置,用于采集K8s集群中节点和Pod的CPU、内存指标,支撑Dashboard及xkub等工具的资源使用可视化。通过修改配置启用 insecure-tls 并替换为阿里云镜像,快速完成部署,安装后可通过`kubectl top`命令验证。
278 0
|
2月前
|
监控 安全 Unix
iOS 崩溃排查不再靠猜!这份分层捕获指南请收好
从 Mach 内核异常到 NSException,从堆栈遍历到僵尸对象检测,阿里云 RUM iOS SDK 基于 KSCrash 构建了一套完整、异步安全、生产可用的崩溃捕获体系,让每一个线上崩溃都能被精准定位。
629 73
|
27天前
|
人工智能 弹性计算 运维
探秘 AgentRun丨为什么应该把 LangChain 等框架部署到函数计算 AgentRun
阿里云函数计算 AgentRun,专为 AI Agent 打造的一站式 Serverless 基础设施。无缝集成 LangChain、AgentScope 等主流框架,零代码改造即可享受弹性伸缩、企业级沙箱、模型高可用与全链路可观测能力,助力 Agent 高效、安全、低成本地落地生产。
320 48
|
Java 测试技术 API
解决harbor上删除镜像不释放空间,无需停止harbor
解决harbor上删除镜像不释放空间 docker镜像仓库中镜像的清理,一直是个比较麻烦的事情。尤其是在测试环境当中,每天都会有大量的构建。由此会产生大量的历史镜像,而这些镜像,大多数都没有用。
3536 0
|
2月前
|
运维 Kubernetes NoSQL
【k8s-1.34.2安装部署】九.k8s管理平台xkube安装部署
xkube是一款永久免费、无功能限制的云原生Kubernetes多集群管理工具,支持PC端与APP端,提供集群管理、运维监控、CI/CD发布等功能,助力企业实现跨集群统一管理与自动化运维。
234 2
【k8s-1.34.2安装部署】九.k8s管理平台xkube安装部署
|
2月前
|
负载均衡 测试技术 Kubernetes
【k8s-1.34.2安装部署】十一.metallb-v0.15.2安装
本章介绍Metallb v0.15.2的安装与配置,适用于测试环境或小流量场景。通过下载YAML文件、修改镜像地址并应用即可完成安装,再配置IP地址池,实现LoadBalancer服务类型对外暴露。简单易用,但大流量时性能受限。
210 3