kubeasz 集群存储实战:基于 PV/PVC、NFS 动态供应与 Local Path 本地存储的完整配置指南
【免费下载链接】kubeasz使用Ansible脚本安装K8S集群,介绍组件交互原理,方便直接,不受国内网络环境影响项目地址: https://gitcode.com/GitHub_Trending/ku/kubeasz
导读
本文以 kubeasz 项目中的集群存储章节(对应 docs/setup/08-cluster-storage.md)为核心骨架,系统讲解 Kubernetes 中 PV(PersistentVolume)、PVC(PersistentVolumeClaim)两类存储抽象的概念与协作方式,并深入介绍 kubeasz 集成的两种存储供应者(provisioner):nfs-subdir-external-provisioner(NFS 存储目录供应者)与local-path-provisioner(本地存储目录供应者)。读完本文,你将掌握:如何手写静态 PV 并绑定 PVC;如何通过编辑集群配置文件一键启用动态 PV 供应;如何在 NFS 服务器侧验证自动创建的挂载目录;以及如何为 I/O 密集型应用配置本地 SSD 目录存储。
一、存储抽象:PV 与 PVC
在 Kubernetes 中,存储被抽象为两个核心资源对象:
- PV(PersistentVolume):集群层面的存储资源,由管理员预先创建(静态供应),或由存储供应者按需动态创建(动态供应)。它独立于任何 Pod 存在,生命周期与集群一致。
- PVC(PersistentVolumeClaim):用户对存储资源的"请求"(需求声明),它指定容量、访问模式与存储类(StorageClass)。PVC 与满足条件的 PV 绑定后,Pod 即可通过 volume 引用 PVC 来使用存储。
PV 是集群中的资源,PVC 是对这些资源的请求。
从设计上看,PV 和 PVC 都是抽象概念,真正的存储能力由 Kubernetes 的卷插件(volume plugin)提供。当前 Kubernetes 生态中支持 NFS、iSCSI 以及各大云厂商提供的存储系统,具体访问模式等细节可参考 Kubernetes 官方持久卷文档(Persistent Volumes 概念页的 Access Modes 章节)。访问模式(AccessModes)是 PVC/PV 匹配的关键字段之一,常见取值包括:
| 访问模式 | 含义 | 典型场景 | | :- | :- | :- | | ReadWriteOnce(RWO) | 单节点读写 | 单副本数据库、本地卷 | | ReadOnlyMany(ROX) | 多节点只读 | 配置共享、只读数据分发的副本 | | ReadWriteMany(RWX) | 多节点读写 | 共享文件存储(NFS/CephFS)、EFK 日志汇聚 |
在 kubeasz 项目中,存储相关能力统一归入cluster-addon角色管理(见 roles/cluster-addon/tasks/main.yml),通过 playbook playbooks/07.cluster-addon.yml 在localhost上执行安装。其中与存储直接相关的两个任务文件分别是:
- roles/cluster-addon/tasks/nfs-provisioner.yml:渲染并应用 NFS provisioner 部署清单;
- roles/cluster-addon/tasks/local-storage.yml:渲染并应用 local-path provisioner 部署清单。
二者均受配置开关控制(nfs_provisioner_install/local_path_provisioner_install为"yes"时生效),且主任务会根据集群中是否已存在对应 Pod 决定是否重复安装(幂等保护)。
二、NFS 存储目录供应者
NFS(Network File System)允许系统将本地目录共享给网络上的其他系统,用户和应用程序可以像访问本地文件一样访问远程文件。它是搭建共享读写(RWX)持久化存储最常用的方案之一。使用 NFS 动态供应前,需要先准备一台可用的 NFS 服务器。
2.1 准备 NFS 服务器
kubeasz 仓库提供了完整的 NFS 服务器搭建指南 docs/guide/nfs-server.md,核心步骤概括如下:
安装服务端(以 Ubuntu 为例):
apt install nfs-kernel-server配置共享目录:编辑
/etc/exports,每个共享目录独占一行,格式为NFS共享目录路径 客户机IP或名称(参数1,参数2,...)。例如:/share 192.168.1.0/24(rw,sync,insecure,no_subtree_check,no_root_squash)启动服务:
systemctl start nfs-kernel-server.service
关于/etc/exports参数,仓库文档给出了完整对照表,常用关键参数如下:
| 参数 | 说明 | | :- | :- | | ro / rw | 只读 / 读写访问 | | sync / async | 所有数据在请求时写入共享 / nfs 在写入数据前可以响应请求 | | insecure | nfs 通过 1024 以上的端口发送(必须加,否则客户端挂载报错mount.nfs: access denied by server while mounting) | | no_subtree_check | 不检查父目录权限 | | no_root_squash | root 用户具有根目录的完全管理访问权限 |
两个实战提示(源自仓库文档):一是尽量使用主机名/IP/IP 段做最小化授权;二是在 k8s 集群中配合 nfs-client-provisioner 使用时,
/etc/exports需要放行Pod 的网段(IP 段),否则 provisioner Pod 会因mount.nfs: access denied by server while mounting而无法启动。
2.2 静态 PV:手动创建持久卷
当存储规模小、PVC 数量有限时,管理员可以手动创建 PV 供 PVC 绑定。原文档给出了一个典型的 NFS 静态 PV 示例:
apiVersion: v1 kind: PersistentVolume metadata: name: pv-es-0 spec: capacity: storage: 4Gi accessModes: - ReadWriteMany volumeMode: Filesystem persistentVolumeReclaimPolicy: Recycle storageClassName: "es-storage-class" nfs: # 根据实际共享目录修改 path: /share/es0 # 根据实际 nfs服务器地址修改 server: 192.168.1.208各字段的作用与注意事项:
capacity.storage:PV 声明的容量(如4Gi),PVC 的容量请求必须小于等于该值才可能完成绑定;accessModes:访问模式,此处ReadWriteMany允许多个节点同时读写,是 NFS 的典型用法;volumeMode: Filesystem:卷模式为文件系统(另一可选值为Block原始块设备);persistentVolumeReclaimPolicy: Recycle:PVC 释放后 PV 的回收策略,可选Retain(保留)、Recycle(回收清空后重新可用)或Delete(动态供应时删除底层存储);storageClassName:PV 归属的存储类。PVC 声明相同storageClassName时才会与它匹配绑定;nfs.path / nfs.server:NFS 共享的实际路径与服务器地址,需要按真实环境修改。
创建该 PV 后,再创建同storageClassName的 PVC,即可完成绑定使用(具体可参考后文 test-pod 例子中的 PVC 写法)。
2.3 动态 PV:通过 StorageClass 自动供应
在生产集群中,PVC 请求数量会非常多,如果每次都需要管理员手动创建 PV 会非常繁琐。Kubernetes 提供了多种provisioner来动态创建 PV:管理员只需定义好 StorageClass(存储类),当用户提交 PVC 时,provisioner 会自动为其创建对应的 PV,并将不同存储类型封装成不同 StorageClass 供 PVC 按需选用,既节省了管理员的时间,又实现了存储能力的按需分配。
kubeasz 集成的 NFS 动态供应方案是nfs-subdir-external-provisioner(对应 roles/cluster-addon/templates/nfs-provisioner/nfs-provisioner.yaml.j2)。其部署清单主要由四部分组成:
- ServiceAccount + ClusterRole/ClusterRoleBinding:为 provisioner 授权 PV/PVC/StorageClass 的增删改查与事件上报权限;
- Role/RoleBinding(leader-locking):在同一命名空间内通过 endpoints 做选主(leader election),保证多个副本并发时只有一个 provisioner 在真正工作;
- Deployment:以 NFS 卷挂载方式运行
nfs-client-provisioner容器,通过环境变量注入PROVISIONER_NAME(k8s-sigs.io/nfs-subdir-external-provisioner)、NFS_SERVER、NFS_PATH; - StorageClass:声明 provisioner 名称与供应参数。
其中 StorageClass 定义如下(模板节选):
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: {{ nfs_storage_class }} # 默认 managed-nfs-storage provisioner: k8s-sigs.io/nfs-subdir-external-provisioner parameters: archiveOnDelete: "false" # 删除 PVC 时直接删除对应 NFS 目录,不做归档在 kubeasz 中启用 NFS 动态供应
步骤 1:编辑集群配置文件clusters/${集群名}/config.yml
找到 cluster-addon 相关参数段(默认值见 example/config.yml),修改为:
# 在 cluster-addon 中启用 nfs-provisioner 安装 nfs_provisioner_install: "yes" # 修改为 yes nfs_provisioner_namespace: "kube-system" # provisioner 部署的命名空间 nfs_provisioner_ver: "v4.0.1" # 镜像版本,由 ezdown 下载并写入实际版本号 nfs_storage_class: "managed-nfs-storage" # 生成的 StorageClass 名称 nfs_server: "192.168.31.244" # 修改为实际 nfs server 地址 nfs_path: "/data/nfs" # 修改为实际的 nfs 共享目录各参数的说明:
| 参数 | 默认值 | 说明 | | :- | :- | :- | |nfs_provisioner_install|no| 是否安装 nfs provisioner,改为yes启用 | |nfs_provisioner_namespace|kube-system| provisioner 部署所在命名空间 | |nfs_provisioner_ver| 模板变量 | provisioner 镜像版本,安装脚本按下载产物自动填充 | |nfs_storage_class|managed-nfs-storage| 生成的 StorageClass 名称,供 PVC 引用 | |nfs_server|192.168.1.10| NFS 服务器地址,必须改为实际地址 | |nfs_path|/data/nfs| NFS 共享目录,必须改为实际共享路径 |
步骤 2:创建 nfs provisioner
$ dk ezctl setup ${集群名} 07ezctl setup ${集群名} 07对应执行 playbooks/07.cluster-addon.yml 中的 cluster-addon 角色,其内部会依次渲染 NFS provisioner 部署清单与测试 Pod 清单(见 roles/cluster-addon/tasks/nfs-provisioner.yml),并将产物写入clusters/${集群名}/yml/nfs-provisioner/,最后通过kubectl apply创建资源。
执行成功后验证:
$ kubectl get pod --all-namespaces | grep nfs-client kube-system nfs-client-provisioner-84ff87c669-ksw95 1/1 Running 0 21m步骤 3:验证动态 PV 的使用
kubeasz 在clusters/${集群名}/yml/nfs-provisioner/目录下生成了测试例子test-pod.yaml(模板见 roles/cluster-addon/templates/nfs-provisioner/test-pod.yaml.j2),其内容包含一个 PVC 和一个挂载该 PVC 的 busybox 测试 Pod:
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim spec: storageClassName: {{ nfs_storage_class }} # 引用 managed-nfs-storage accessModes: - ReadWriteMany resources: requests: storage: 2Mi --- kind: Pod apiVersion: v1 metadata: name: test-pod spec: containers: - name: test-pod image: busybox command: ["/bin/sh"] args: ["-c", "touch /mnt/SUCCESS && exit 0 || exit 1"] volumeMounts: - name: nfs-pvc mountPath: "/mnt" restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim应用测试清单并验证:
$ kubectl apply -f /etc/kubeasz/clusters/hello/yml/nfs-provisioner/test-pod.yaml # 验证测试 pod(写入 SUCCESS 后退出,状态为 Completed) kubectl get pod NAME READY STATUS RESTARTS AGE test-pod 0/1 Completed 0 6h36m # 验证自动创建的 pv 资源 kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-44d34a50-e00b-4f6c-8005-40f5cc54af18 2Mi RWX Delete Bound default/test-claim managed-nfs-storage 6h36m # 验证 PVC 已经绑定成功:STATUS 字段为 Bound kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test-claim Bound pvc-44d34a50-e00b-4f6c-8005-40f5cc54af18 2Mi RWX managed-nfs-storage 6h37m从输出可以看出:PVC 提交后 provisioner 自动创建了名为pvc-44d34a50-...的 PV,其回收策略为Delete(动态供应的 PV 在 PVC 释放时删除),StorageClass 为managed-nfs-storage,PVC 状态变为Bound完成绑定。
步骤 4:在 NFS 服务器侧验证数据落盘
测试 Pod 启动完成后会在挂载目录中创建一个SUCCESS文件。到 NFS 服务器共享目录下查看:
. └── default-test-claim-pvc-44d34a50-e00b-4f6c-8005-40f5cc54af18 └── SUCCESS可以看到:挂载时 nfs-client 根据PVC 所在命名空间 + PVC 名称 + PV UID(default-test-claim-pvc-xxxx)自动创建了一个目录,Pod 中挂载的/mnt实际引用的就是该目录,/mnt下创建的SUCCESS文件也自动写入到了这里。
至此,当上层应用需要持久化存储时,只需提供对应的StorageClass即可。很多应用都会根据 StorageClass 来创建它们所需的 PVC,最后再把 PVC 挂载到 Deployment 或 StatefulSet 中使用,例如仓库中集成的 efk、jenkins 等应用。
三、本地存储目录供应者(Local Path Provisioner)
当应用对磁盘 I/O 性能要求较高时,比较适合使用本地文件目录存储,尤其可以本地挂载 SSD 磁盘(注意:本地磁盘需要配置 RAID 冗余策略以保证数据可靠性)。local-path-provisioner(来自 Rancher 社区)可以方便地在 k8s 集群中使用本地文件目录存储,其特点是利用节点本地的空余磁盘目录作为存储后端,无需额外维护网络存储服务,且天然具备接近裸盘的 I/O 性能。
3.1 工作原理与部署清单解读
kubeasz 对应的部署模板为 roles/cluster-addon/templates/local-storage/local-path-storage.yaml.j2,核心内容包括:
RBAC:ServiceAccount、Role/ClusterRole 及绑定,授权 provisioner 管理节点上的 Pod、PVC、PV、StorageClass 与事件;
Deployment:以
--config /etc/config/config.json方式启动local-path-provisioner,挂载local-path-configConfigMap 作为配置;StorageClass:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: {{ local_path_storage_class }} # 默认 local-path provisioner: rancher.io/local-path volumeBindingMode: WaitForFirstConsumer # 延迟绑定:等第一个使用它的 Pod 调度完成后才在对应节点创建 PV reclaimPolicy: Delete注意
volumeBindingMode: WaitForFirstConsumer:local-path 的 PV 依赖具体节点,因此采用"延迟绑定"模式,Pod 被调度到某节点后,provisioner 才在该节点上创建目录并生成 PV,这样 PV 一定落在 Pod 所在节点。ConfigMap
local-path-config:其中config.json通过nodePathMap指定节点上的本地存储路径:{ "nodePathMap": [ { "node": "DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths": ["{{ local_path_provisioner_dir }}"] } ] }DEFAULT_PATH_FOR_NON_LISTED_NODES表示对所有未单独列出的节点生效,paths数组中的路径即本地数据落盘目录(默认/opt/local-path-provisioner);ConfigMap 中还内置了setup(创建目录mkdir -m 0777)与teardown(删除目录)脚本,以及helperPod.yaml(在节点上执行卷准备的辅助 Pod 模板,容忍 disk-pressure 污点)。
3.2 在 kubeasz 中启用 Local Path Provisioner
步骤 1:编辑集群配置文件clusters/${集群名}/config.yml
local_path_provisioner_install: "yes" # 修改为 yes # 设置默认本地存储路径 local_path_provisioner_dir: "/opt/local-path-provisioner"相关参数(默认值见 example/config.yml):
| 参数 | 默认值 | 说明 | | :- | :- | :- | |local_path_provisioner_install|no| 是否安装 local-path provisioner | |local_path_provisioner_ver| 模板变量 | 镜像版本,安装脚本自动填充 | |local_path_storage_class|local-path| 生成的 StorageClass 名称 | |local_path_provisioner_dir|/opt/local-path-provisioner| 节点上的本地存储根目录 |
步骤 2:创建 local path provisioner
$ dk ezctl setup ${集群名} 07该命令会走与 NFS provisioner 相同的安装流程:由 roles/cluster-addon/tasks/local-storage.yml 渲染local-path-storage.yaml与test-pod.yaml到clusters/${集群名}/yml/local-storage/并 apply。执行成功后验证:
$ kubectl get pod --all-namespaces | grep provisioner应能看到local-path-provisioner相关 Pod 处于 Running 状态。
步骤 3:验证使用
kubeasz 在clusters/${集群名}/yml/local-storage/下生成了测试清单test-pod.yaml(模板见 roles/cluster-addon/templates/local-storage/test-pod.yaml.j2),内容为一个 128Mi 的 PVC(storageClassName: local-path、访问模式ReadWriteOnce)和一个挂载该 PVC 到/data的 nginx 测试 Pod:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-path-pvc spec: accessModes: - ReadWriteOnce storageClassName: local-path resources: requests: storage: 128Mi --- apiVersion: v1 kind: Pod metadata: name: volume-test spec: containers: - name: volume-test image: nginx:stable-alpine imagePullPolicy: IfNotPresent volumeMounts: - name: volv mountPath: /data ports: - containerPort: 80 volumes: - name: volv persistentVolumeClaim: claimName: local-path-pvc应用后可通过kubectl get pvc、kubectl get pv确认 PVC 绑定,并检查 Pod 所在节点/opt/local-path-provisioner(或自定义目录)下是否生成了对应的卷目录。由于WaitForFirstConsumer的存在,Pod 创建前 PVC 会处于Pending状态,这是预期行为,Pod 调度到节点后 PVC 会自动变为Bound。
四、两种存储方案的选型对比
| 维度 | NFS 动态供应(nfs-subdir-external-provisioner) | Local Path 本地供应(local-path-provisioner) | | :- | :- | :- | | 底层存储 | 独立 NFS 服务器共享目录 | 各节点本地磁盘目录 | | 访问模式 | 支持 RWX(多节点读写) | 仅 RWO(单节点读写) | | 适用场景 | 多副本共享读写、日志汇聚(如 EFK)、Jenkins workspace 等 | 高 I/O 性能要求的单副本应用、可本地挂载 SSD | | 数据可靠性 | 依赖 NFS 服务器冗余(如 RAID/备份) | 依赖节点本地磁盘,建议配置 RAID 冗余策略| | 数据位置 | 集中存储于 NFS 服务器 | 分散在各工作节点本地目录 | | 绑定模式 | 立即绑定(Immediate) | 延迟绑定(WaitForFirstConsumer) | | 典型 StorageClass |managed-nfs-storage|local-path|
选型建议:需要多节点共享读写(如日志类、文件类应用)时优先 NFS 动态供应;追求极致磁盘 I/O 且单副本可接受时选择 Local Path 本地存储。
五、总结
本文从 PV/PVC 两个存储抽象入手,完整覆盖了 kubeasz 中两类持久化存储供应者的启用与验证流程:
- 静态 PV:适用于 PVC 数量少的场景,通过手写 PV 清单(容量、访问模式、回收策略、StorageClass、NFS 地址路径)与 PVC 完成绑定;
- NFS 动态供应:在
clusters/${集群名}/config.yml中开启nfs_provisioner_install: "yes"并配置 NFS 服务器信息,执行dk ezctl setup ${集群名} 07一键部署;PVC 提交后 provisioner 自动创建 PV 与 NFS 目录,可在 NFS 服务器侧看到命名空间-PVC名-PV UID目录结构; - Local Path 本地供应:开启
local_path_provisioner_install: "yes",通过nodePathMap指定各节点本地目录,采用WaitForFirstConsumer延迟绑定,适合高 I/O 场景。
两种方案对应的配置模板与测试清单都沉淀在仓库的 roles/cluster-addon/templates/nfs-provisioner/ 与 roles/cluster-addon/templates/local-storage/ 目录下,读者可按需阅读模板细节,或结合 docs/guide/nfs-server.md 从零搭建 NFS 服务器后再进行动态供应验证。
【免费下载链接】kubeasz使用Ansible脚本安装K8S集群,介绍组件交互原理,方便直接,不受国内网络环境影响项目地址: https://gitcode.com/GitHub_Trending/ku/kubeasz
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考