前言
在Kubernetes(K8s)集群中,容器的临时性本质带来了两个关键存储问题:一是容器崩溃重启后,可写层数据会完全丢失,例如应用日志、用户配置等关键信息无法保留;二是同一Pod内的多个容器(如应用容器与日志收集容器)无法直接访问彼此的文件系统,数据共享存在天然障碍。为解决这些问题,K8s设计了Volume抽象机制,打破容器的存储边界,在此基础上进一步衍生出PV(Persistent Volume,持久化存储卷)与PVC(Persistent Volume Claim,持久化存储请求)的资源解耦方案,最终通过StorageClass实现动态存储管理。本文将从基础存储卷实践出发,逐步深入核心机制,结合NFS存储进行全流程实战,帮助读者掌握K8s持久化存储的选型与配置要点。
一、容器存储的核心挑战与K8s Volume解决方案
1.1 容器存储的短暂性问题
容器的文件系统基于镜像层和可写层构建,其临时性特征主要体现在两方面:
- 数据易失性:容器因故障、重启或重建导致可写层销毁时,内部数据会彻底丢失,无法恢复;
- 共享困难:同一Pod内的多个容器运行在独立的文件系统空间,缺乏直接的数据交互通道,无法满足协同工作需求(如日志收集容器需读取应用容器的日志文件)。
1.2 K8s Volume的核心价值
K8s通过Volume抽象提供了容器存储的基础解决方案,其核心设计逻辑基于Pod的Pause容器(基础容器)实现:
- 多容器共享:同一Pod内的所有容器可挂载同一个Volume,实现数据实时共享;
- 生命周期解耦:Volume的生命周期独立于单个容器,仅与Pod生命周期绑定(默认),容器重启时数据不会丢失;
- 存储类型兼容:支持本地存储(如emptyDir、hostPath)、网络存储(如NFS、Ceph)等多种类型,适配不同场景需求。
二、K8s基础存储卷实践
K8s提供了多种基础Volume类型,各自适用于不同的存储场景。以下重点讲解emptyDir、hostPath和NFS三种常用类型的特点与实战配置。
2.1 emptyDir:Pod内临时共享存储
emptyDir是最基础的Volume类型,其生命周期与Pod完全绑定,适用于临时缓存或Pod内多容器数据共享场景。
2.1.1 核心特点
- 自动创建:Pod调度到节点时,K8s自动在节点本地磁盘(或内存)创建emptyDir目录;
- 随Pod销毁:Pod被删除时,emptyDir内的所有数据会被彻底清理,无法持久化;
- 存储介质:默认使用节点本地磁盘,可通过
medium: Memory配置为内存存储(适合高性能临时缓存,但需注意内存溢出风险); - 适用场景:临时数据缓存、Pod内容器间数据交换(如应用容器生成数据,日志容器收集数据)。
2.1.2 实战配置(YAML示例)
yaml
# 创建目录用于存放YAML文件
# mkdir /opt/volumes && cd /opt/volumes
vim pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-emptydir
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
# 应用容器:Nginx,挂载emptyDir到/html目录
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts:
- name: html # 与下方volumes.name对应
mountPath: /usr/share/nginx/html/ # 容器内挂载路径
# 辅助容器:BusyBox,定期写入日期到共享目录
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /data/ # 容器内挂载路径
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
# 定义存储卷
volumes:
- name: html
emptyDir: {} # 定义为emptyDir类型
2.1.3 验证与结果
- 部署Pod并查看状态:
bash
kubectl apply -f pod-emptydir.yaml
# 确认Pod的2个容器均正常运行(READY为2/2)
kubectl get pods pod-emptydir -o wide
预期输出:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-emptydir 2/2 Running 0 9s 10.244.1.59 node01 <none> <none>
- 访问Nginx验证数据共享:
bash
# 获取Pod的IP(从上方命令的IP列获取)
kubectl get pod -o wide
# 访问Nginx,可见时间持续追加(证明两容器共享数据)
curl 10.244.1.59
预期输出:
Tue Oct 14 11:56:27 UTC 2025
Tue Oct 14 11:56:29 UTC 2025
Tue Oct 14 11:56:31 UTC 2025
...
- 小结:emptyDir仅适用于临时数据场景,Pod删除后数据会彻底丢失,无法用于持久化存储需求。
2.2 hostPath:节点级持久化存储
hostPath将节点(宿主机)的本地目录直接挂载到容器,实现节点级别的数据持久化,Pod删除后节点目录中的数据仍会保留。
2.2.1 核心特点
- 节点绑定:数据存储在宿主机目录,与Pod生命周期解耦,Pod删除后数据不丢失;
- 跨Pod共享:同一节点上的多个Pod可通过挂载相同hostPath目录实现数据共享;
- 局限性:
- 数据与特定节点绑定,若Pod被调度到其他节点,无法访问原节点的hostPath数据;
- 节点故障或离线时,数据将无法访问,存在单点风险。
2.2.2 实战配置(YAML示例)
- 提前在所有节点创建统一目录(避免Pod调度到无目录的节点报错):
bash
# 在node01节点创建目录并写入标识
mkdir -p /data/pod/volume1
echo 'node01.benet.com' > /data/pod/volume1/index.html
# 在node02节点创建目录并写入标识
mkdir -p /data/pod/volume1
echo 'node02.benet.com' > /data/pod/volume1/index.html
- 编写hostPath测试Pod配置:
yaml
vim pod-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-hostpath
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html # 与下方volumes.name对应
mountPath: /usr/share/nginx/html # 容器内挂载路径
readOnly: false # 读写模式(默认false)
volumes:
- name: html
hostPath:
path: /data/pod/volume1 # 宿主机目录路径
type: DirectoryOrCreate # 若目录不存在则自动创建
2.2.3 验证与结果
- 部署Pod并查看调度节点:
bash
kubectl apply -f pod-hostpath.yaml
# 查看Pod调度到的节点(NODE列)
kubectl get pods pod-hostpath -o wide
预期输出:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-hostpath 1/1 Running 0 9s 10.244.1.60 node01 <none> <none>
- 访问Nginx验证数据:
bash
curl 10.244.1.60
预期输出(根据调度节点显示对应标识):
node01.benet.com
- 验证持久化特性:
bash
# 删除Pod后重新部署
kubectl delete -f pod-hostpath.yaml && kubectl apply -f pod-hostpath.yaml
# 若调度到同一节点,数据仍保留
kubectl get pods pod-hostpath -o wide
curl 新Pod的IP
2.3 NFS:跨节点共享存储
NFS(Network File System)是一种网络存储协议,通过独立的NFS服务端提供存储资源,所有K8s节点均可通过网络访问,解决了hostPath跨节点数据共享的局限性,是集群级共享存储的常用方案。
2.3.1 核心特点
- 跨节点共享:数据集中存储在NFS服务端,所有K8s节点(客户端)可通过网络访问,Pod调度到任意节点均能获取相同数据;
- 持久化可靠:数据独立于K8s集群,Pod删除、节点故障均不影响数据安全性;
- 支持RWX模式:允许多个节点的多个Pod同时读写存储,适合分布式应用、共享配置等场景;
- 依赖外部服务:需单独部署和维护NFS服务端,增加了运维成本。
2.3.2 实战配置(分服务端与客户端)
步骤1:部署NFS服务端(独立节点,如stor01)
bash
# 安装NFS服务端组件(rpcbind为依赖)
yum install -y nfs-utils rpcbind
# 创建NFS共享目录
mkdir -p /data/volumes
chmod 777 /data/volumes # 赋予读写权限,避免容器访问权限不足
# 编辑NFS配置文件(允许K8s集群网段访问)
vim /etc/exports
/data/volumes 192.168.10.0/24(rw,no_root_squash)
# 启动服务并设置开机自启
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs && systemctl enable nfs
# 验证配置(输出共享目录信息即为成功)
showmount -e
预期输出:
Export list for stor01:
/data/volumes 192.168.10.0/24
步骤2:K8s节点安装NFS客户端(所有节点)
bash
# 所有K8s节点(master、node01、node02)执行
yum install -y nfs-utils
# 将NFS服务端IP与主机名映射添加到/etc/hosts(便于配置引用)
echo "192.168.10.17 stor01" >> /etc/hosts
# 验证NFS服务端连通性(能看到共享目录即为成功)
showmount -e stor01
步骤3:编写NFS Volume测试Pod配置
yaml
vim pod-nfs-vol.yaml
kind: Pod
apiVersion: v1
metadata:
name: pod-vol-nfs
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html # 容器内挂载路径
volumes:
- name: html
nfs:
path: /data/volumes # NFS服务端共享目录
server: stor01 # NFS服务端主机名(或IP)
步骤4:验证与结果
- 部署Pod并创建测试数据:
bash
kubectl apply -f pod-nfs-vol.yaml
# 在NFS服务端创建测试文件
echo "<h1>nfs stor01</h1>" > /data/volumes/index.html
- 访问Pod验证数据:
bash
# 获取Pod的IP和调度节点
kubectl get pods pod-vol-nfs -o wide
# 访问Pod,验证数据读取
curl 10.244.2.68
预期输出:
<h1>nfs stor01</h1>
- 验证跨节点共享能力:
bash
# 修改Pod配置,强制调度到其他节点(如node01)
vim pod-nfs-vol.yaml
spec:
nodeName: node01 # 强制绑定节点
...
# 重新部署Pod
kubectl delete -f pod-nfs-vol.yaml && kubectl apply -f pod-nfs-vol.yaml
# 再次访问,仍能获取NFS服务端数据
curl 新Pod的IP
三、PV与PVC:K8s持久化存储的核心机制
基础Volume(如NFS)需要在Pod中直接配置存储细节(如服务端IP、目录路径),导致开发与运维职责耦合------开发者需关注底层存储配置,不符合"关注点分离"的设计原则。PV与PVC机制通过"存储资源抽象"解耦职责,是生产环境中推荐的持久化方案。
3.1 PV与PVC的核心概念
PV与PVC的本质是"存储资源的供给与需求",分工明确,职责分离:
| 概念 | 名称 | 核心作用 | 维护者 |
|---|---|---|---|
| PV(Persistent Volume) | 持久化存储卷 | 定义底层存储资源(如NFS路径、容量、访问模式),是"存储资源模板" | 运维工程师 |
| PVC(Persistent Volume Claim) | 持久化存储请求 | 描述应用对存储的需求(如容量、访问模式),是"存储资源申请单" | 应用开发者 |
| StorageClass | 存储类 | 定义动态PV创建规则,实现PV自动创建与回收 | 运维工程师 |
PVC的使用逻辑:开发者无需关注底层存储细节,仅需通过PVC声明存储需求(如2Gi容量、RWX访问模式),K8s会自动匹配符合条件的PV并完成绑定,Pod通过PVC即可使用存储资源。
3.2 PV与PVC的生命周期
PV与PVC的交互遵循"配置→绑定→使用→释放→回收"5个阶段,对应PV的4种核心状态。
3.2.1 生命周期流程
- Provisioning(配置):运维手动创建PV(静态配置),或通过StorageClass自动创建PV(动态配置);
- Binding(绑定) :开发者创建PVC,K8s根据PVC的需求(容量、访问模式)匹配可用PV,绑定后PV状态变为
Bound; - Using(使用) :Pod通过引用PVC挂载存储,K8s通过
StorageProtection机制阻止删除正在使用的PVC; - Releasing(释放) :Pod删除且PVC被删除后,PV与PVC解绑,状态变为
Released(数据仍保留); - Recycling(回收) :K8s根据PV的回收策略处理
Released状态的PV(如保留数据、删除数据或清空数据)。
3.2.2 PV的核心状态
| 状态 | 触发条件 | 说明 |
|---|---|---|
Available |
PV已创建,未被任何PVC绑定 | 处于待分配状态 |
Bound |
PV已成功绑定到PVC | 正常提供存储服务 |
Released |
PVC被删除,PV与PVC解绑 | 数据仍保留,等待回收 |
Failed |
PV自动回收过程失败 | 需手动干预处理 |
3.2.3 一个PV从创建到销毁的具体流程
- 运维创建PV后,其状态变为
Available,等待PVC绑定; - 开发者创建PVC,K8s匹配到符合条件的PV后完成绑定,PV状态变为
Bound; - Pod通过PVC挂载并使用存储资源,期间PV持续处于
Bound状态; - 应用停止使用后,开发者删除PVC,PV状态变为
Released; - K8s根据PV的回收策略执行回收操作:
- 若为
Retain(保留):PV状态保持Released,数据保留,需手动清理; - 若为
Delete(删除):自动删除PV及关联的底层存储资源; - 若为
Recycle(回收):清空数据后PV状态恢复Available,可重新绑定PVC。
- 若为
3.3 PV的核心配置项
创建PV时需关注3个核心配置项,直接影响PVC的匹配逻辑和存储使用效果:
3.3.1 访问模式(accessModes)
定义PV支持的访问方式,PVC的访问模式必须是PV的子集(如PV支持RWX,PVC可申请RWX或RWO),不同存储类型支持的访问模式不同:
- RWO(ReadWriteOnce):可读可写,仅允许单个节点的多个Pod挂载(支持所有存储类型);
- ROX(ReadOnlyMany):只读,允许多个节点的Pod同时挂载(支持NFS、Ceph等);
- RWX(ReadWriteMany):可读可写,允许多个节点的Pod同时挂载(仅支持NFS、Ceph等共享存储)。
3.3.2 容量(capacity)
定义PV的存储容量,格式为存储大小+单位(如2Gi、10Gi),PVC申请的容量必须≤PV的容量,否则无法匹配。
3.3.3 回收策略(persistentVolumeReclaimPolicy)
定义PVC删除后PV的处理方式,默认策略为Retain:
- Retain(保留) :PV状态变为
Released,数据保留,需手动清理数据并删除PV; - Delete(删除):自动删除PV及关联的底层存储资源(仅支持云存储如AWS EBS、Azure Disk等);
- Recycle(回收) :清空PV数据(执行
rm -rf /path/*),状态恢复Available(仅支持NFS、hostPath,已逐步废弃)。
3.4 PV 的示例
yaml
# 查看PV定义格式(可通过kubectl explain pv查看详细字段)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003 # PV名称(集群唯一)
labels:
name: pv003 # 标签,用于筛选匹配
spec:
nfs: # 存储类型(此处为NFS)
server: stor01 # NFS服务端地址
path: /data/volumes/v3 # NFS共享目录
accessModes: ["ReadWriteMany", "ReadWriteOnce"] # 支持的访问模式
capacity:
storage: 2Gi # 存储容量
storageClassName: "" # 存储类名称(为空表示无存储类,仅静态匹配)
persistentVolumeReclaimPolicy: Retain # 回收策略
PVC定义示例:
yaml
# 查看PVC定义格式(kubectl explain pvc)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc # PVC名称(命名空间内唯一)
namespace: default
spec:
accessModes: ["ReadWriteMany"] # 申请的访问模式(需为PV子集)
resources:
requests:
storage: 2Gi # 申请的存储容量
storageClassName: "" # 需与PV的storageClassName一致
四、NFS + PV + PVC 实战演练
本节基于NFS存储实现PV与PVC的静态绑定,验证持久化存储的全流程配置与使用。
4.1 前置准备(NFS服务端配置)
在NFS服务端(stor01)创建5个独立目录,分别对应5个PV的存储路径:
bash
# 创建5个PV对应的NFS目录
mkdir -p /data/volumes/v{1,2,3,4,5}
# 编辑NFS配置,共享所有子目录
vim /etc/exports
/data/volumes/v1 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.10.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.10.0/24(rw,no_root_squash)
# 生效配置
exportfs -arv
# 验证共享目录
showmount -e
4.2 手动创建PV(静态配置)
编写5个PV的YAML配置,分别对应NFS的5个目录,配置不同的容量和访问模式:
yaml
vim pv-demo.yaml
# PV001:1Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
server: stor01
path: /data/volumes/v1
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 1Gi
---
# PV002:2Gi,仅支持RWO
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
server: stor01
path: /data/volumes/v2
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
# PV003:2Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
server: stor01
path: /data/volumes/v3
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 2Gi
---
# PV004:4Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
server: stor01
path: /data/volumes/v4
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 4Gi
---
# PV005:5Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
server: stor01
path: /data/volumes/v5
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 5Gi
部署PV并查看状态:
bash
kubectl apply -f pv-demo.yaml
# 查看PV状态(初始均为Available)
kubectl get pv
预期输出:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 12s
pv002 2Gi RWO Retain Available 12s
pv003 2Gi RWO,RWX Retain Available 12s
pv004 4Gi RWO,RWX Retain Available 12s
pv005 5Gi RWO,RWX Retain Available 12s
4.3 创建PVC并绑定PV
4.3.1 编写PVC与Pod配置
开发者创建PVC,申请2Gi容量、RWX访问模式的存储,K8s会自动匹配符合条件的PV(此处为pv003):
yaml
vim pod-vol-pvc.yaml
# PVC:申请2Gi,RWX访问模式
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"] # 申请RWX模式
resources:
requests:
storage: 2Gi # 申请2Gi容量
---
# Pod:通过PVC挂载存储
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-pvc
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html # 与下方volumes.name对应
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc # 引用上述PVC名称
4.3.2 部署与验证
- 部署PVC与Pod:
bash
kubectl apply -f pod-vol-pvc.yaml
- 查看绑定状态:
bash
# 查看PVC状态(变为Bound,VOLUME列显示绑定的PV)
kubectl get pvc
# 查看PV003状态(变为Bound,CLAIM列显示绑定的PVC)
kubectl get pv pv003
预期输出:
# PVC状态
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv003 2Gi RWO,RWX 36s
# PV003状态
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv003 2Gi RWO,RWX Retain Bound default/mypvc 19m
- 验证数据持久化:
bash
# 在NFS服务端的v3目录创建测试文件
echo "welcome to use pv3" > /data/volumes/v3/index.html
# 获取Pod的IP
kubectl get pods pod-vol-pvc -o wide
# 访问Pod验证数据
curl 10.244.2.69
预期输出:
welcome to use pv3
- 验证释放流程:
bash
# 删除PVC
kubectl delete pvc mypvc
# 查看PV003状态(变为Released,数据仍保留)
kubectl get pv pv003
五、StorageClass:实现NFS动态PV创建
静态PV需要运维手动创建,当集群规模较大(如数百个应用需独立存储)时,手动管理PV的效率极低,且易出现配置失误。StorageClass通过动态配置机制,实现PV的自动创建、绑定与回收,大幅提升运维效率,是大规模集群的优选方案。
5.1 StorageClass的核心组件
动态PV创建依赖3个核心组件,协同完成存储资源的自动化管理:
- StorageClass:定义动态PV的"模板",包含存储类型、Provisioner(存储分配器)、回收策略等配置;
- Provisioner :存储分配器,负责根据StorageClass的配置自动创建PV和底层存储资源(NFS需使用
nfs-client-provisioner第三方组件); - RBAC权限:Provisioner需要操作PV、PVC、StorageClass等集群资源,需通过RBAC配置授予相应权限。
5.2 部署NFS动态存储(全流程)
5.2.1 前置修复(K8s 1.20版本)
K8s 1.20+版本默认启用RemoveSelfLink=false特性,导致nfs-client-provisioner动态生成PV时报错,需手动修复:
bash
# 编辑API Server配置文件
vim /etc/kubernetes/manifests/kube-apiserver.yaml
# 在spec.containers.command中添加以下配置:
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false # 添加此行
- --advertise-address=192.168.10.14 # 原有配置,无需修改
...
# 重启API Server(修改配置文件后自动重启,可验证状态)
kubectl delete pods kube-apiserver -n kube-system
kubectl get pods -n kube-system | grep apiserver
5.2.2 在stor01节点上安装nfs,并配置nfs服务
bash
# 创建NFS共享目录(用于动态PV存储)
mkdir /opt/k8s
chmod 777 /opt/k8s/
# 编辑NFS配置文件
vim /etc/exports
/opt/k8s 192.168.10.0/24(rw,no_root_squash,sync)
# 生效配置(或重启NFS服务)
exportfs -arv
# 验证共享
showmount -e stor01
5.2.3 配置RBAC权限
创建ServiceAccount和集群角色,授予Provisioner操作存储资源的权限:
yaml
vim nfs-client-rbac.yaml
# 1. 创建ServiceAccount(用于Provisioner身份认证)
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
# 2. 创建集群角色(定义权限范围)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-clusterrole
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: ["list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
# 3. 集群角色绑定(将权限授予ServiceAccount)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-client-provisioner-clusterrolebinding
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-clusterrole
apiGroup: rbac.authorization.k8s.io
部署RBAC权限配置:
bash
kubectl apply -f nfs-client-rbac.yaml
# 验证配置(查看ServiceAccount和角色绑定)
kubectl get ServiceAccount,ClusterRole,ClusterRoleBinding | grep provisioner
5.2.4 部署 NFS Provisioner
nfs-client-provisioner是NFS动态存储的核心组件,负责自动创建PV和NFS挂载点:
yaml
vim nfs-client-provisioner.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1 # 单副本部署(生产环境可根据需求调整)
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate # 重建策略(避免多副本冲突)
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 引用上述ServiceAccount
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest # 官方镜像
imagePullPolicy: IfNotPresent
env:
- name: PROVISIONER_NAME
value: nfs-storage # Provisioner名称(需与StorageClass配置一致)
- name: NFS_SERVER
value: stor01 # NFS服务端主机名
- name: NFS_PATH
value: /opt/k8s # NFS共享目录
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes # 容器内挂载路径
volumes:
- name: nfs-client-root
nfs:
server: stor01
path: /opt/k8s
部署Provisioner并验证:
bash
kubectl apply -f nfs-client-provisioner.yaml
# 查看Pod状态(确保Running)
kubectl get pods | grep nfs-client-provisioner
5.2.5 创建StorageClass
定义动态PV的模板,指定Provisioner、回收策略等:
yaml
vim nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client-storageclass # StorageClass名称
provisioner: nfs-storage # 与Provisioner的PROVISIONER_NAME一致
parameters:
archiveOnDelete: "false" # 删除PVC时是否存档数据(false直接删除数据)
部署StorageClass并验证:
bash
kubectl apply -f nfs-client-storageclass.yaml
# 查看StorageClass状态
kubectl get storageclass
预期输出:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client-storageclass nfs-storage Delete Immediate false 43s
5.2.6 测试动态PV创建
创建PVC,验证StorageClass是否自动创建PV并完成绑定:
yaml
vim test-pvc-pod.yaml
# PVC:引用StorageClass,申请1Gi存储
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-pvc
spec:
accessModes: ["ReadWriteMany"]
storageClassName: nfs-client-storageclass # 引用上述StorageClass
resources:
requests:
storage: 1Gi
---
# Pod:使用PVC存储
apiVersion: v1
kind: Pod
metadata:
name: test-storageclass-pod
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "sleep 3600"] # 保持Pod运行
volumeMounts:
- name: nfs-pvc
mountPath: /mnt # 容器内挂载路径
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-nfs-pvc # 引用上述PVC
部署并验证:
- 部署PVC与Pod:
bash
kubectl apply -f test-pvc-pod.yaml
- 验证PVC绑定状态:
bash
kubectl get pvc test-nfs-pvc
预期输出(STATUS变为Bound,自动创建PV):
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-nfs-pvc Bound pvc-11670f39-782d-41b8-a842-eabe1859a456 1Gi RWX nfs-client-storageclass 2s
- 验证NFS服务端目录:
bash
# 查看NFS服务端是否自动创建挂载目录(目录名格式:namespace-pvcName-pvName)
ls /opt/k8s/
预期输出:
default-test-nfs-pvc-pvc-11670f39-782d-41b8-a842-eabe1859a456
- 验证数据写入:
bash
# 进入Pod,在挂载目录创建测试文件
kubectl exec -it test-storageclass-pod sh
/mnt # echo "dynamic pv test" > test.txt
/mnt # exit
# 在NFS服务端查看文件是否存在
cat /opt/k8s/default-test-nfs-pvc-pvc-11670f39-782d-41b8-a842-eabe1859a456/test.txt
预期输出:
dynamic pv test
- 验证自动回收:
bash
# 删除PVC和Pod
kubectl delete -f test-pvc-pod.yaml
# 查看PV是否自动删除(StorageClass回收策略为Delete)
kubectl get pv
# 查看NFS服务端目录是否自动删除
ls /opt/k8s/
六、存储方案对比与选型建议
存储方案对比
| 存储类型 | 生命周期 | 共享性 | 持久性 | 核心优势 | 局限性 | 典型场景 |
|---|---|---|---|---|---|---|
| emptyDir | 与Pod绑定 | 同Pod内容器 | ❌ | 配置简单、无额外依赖 | 数据易失 | 临时缓存、容器间数据交换 |
| hostPath | 与节点绑定 | 同节点多Pod | ✅ | 节点级持久化、配置简单 | 跨节点不可用、节点故障风险 | 节点本地日志存储、单节点应用 |
| NFS | 独立服务 | 多节点多Pod | ✅ | 跨节点共享、支持RWX | 需维护NFS服务端 | 集群共享存储、分布式应用 |
| PV + PVC | 集群级别 | 按需分配 | ✅ | 解耦开发与运维、资源可控 | 静态PV需手动创建 | 生产环境固定存储需求 |
| StorageClass | 动态创建 | 动态分配 | ✅ | 自动化管理、高效扩展 | 配置复杂、依赖Provisioner | 大规模集群、动态存储需求 |
选型建议
- 临时数据场景:选择emptyDir,如应用临时缓存、Pod内多容器数据共享;
- 单节点持久化场景:选择hostPath,如节点本地日志存储、无需跨节点访问的应用;
- 跨节点共享场景:选择NFS(基础需求)或PV+PVC(生产环境),如分布式应用、多节点共享配置;
- 大规模集群场景:选择StorageClass+NFS,如数百个应用的动态存储需求、CI/CD流水线动态分配存储;
- 云原生场景:优先选择云厂商提供的StorageClass(如AWS EBS、Azure Disk),无需维护底层存储服务。
总结
Kubernetes的持久化存储体系围绕"解耦、灵活、高效"的设计原则,从基础Volume抽象到PV/PVC的资源分离,再到StorageClass的动态管理,形成了覆盖不同场景的完整解决方案。emptyDir和hostPath适用于简单场景,NFS提供了集群级共享能力,PV/PVC实现了开发与运维的职责解耦,而StorageClass则通过自动化手段大幅提升了大规模集群的存储管理效率。