为什么需要持久化存储?
Kubernetes 中部署的应用都是以 Pod 容器的形式运行的。假如我们部署 MySQL、Redis 等数据库,需要对这些数据库产生的数据做备份。但 Pod 是有生命周期的,如果 Pod 不挂载数据卷,那 Pod 被删除或重启后这些数据会随之消失。
如果想要长久的保留这些数据,就要用到 Pod 数据持久化存储。
一、Kubernetes 持久化存储基础
1.1 查看 Kubernetes 支持的存储类型
bash
# 查看 k8s 支持哪些存储
[root@hd1 ~]# kubectl explain pods.spec.volumes
FIELDS:
cephfs <Object>
csi <Object>
downwardAPI <Object>
emptyDir <Object>
glusterfs <Object>
hostPath <Object> #宿主机绝对路径
nfs <Object>
persistentVolumeClaim <Object>
vsphereVolume <Object>
......
1.2 使用存储卷的基本步骤
使用存储卷需要经历以下两个步骤:
- 定义 Pod 的 volume:指明这个 volume 要关联到哪个存储上
- 在容器中使用 volumeMounts:挂载对应的存储到容器的指定路径
二、emptyDir 存储卷
2.1 什么是 emptyDir?
一句话总结:它是一种显式定义的、生命周期与 Pod 绑定的、用于 Pod 内多容器间共享临时数据的存储卷
emptyDir 类型的 Volume 是在 Pod 分配到 Node 上时被创建的,Kubernetes 会在 Node 上自动分配一个目录,因此无需指定宿主机上对应的目录文件。
特点:
- 目录的初始内容为空
- 当 Pod 从 Node 上移除时,
emptyDir中的数据会被同步删除 - 可用于Pod 内多容器间共享临时数据
- 存储来源:默认情况下使用的是节点(Node)的本地磁盘 (如 kubelet 的根目录)。但它也支持将数据存储在内存中,只需设置 emptyDir.medium 字段为 "Memory" 即可
- 只要 Pod 还存在 ,即使里面的容器因故崩溃并被重启,emptyDir 中的数据也依然会保留
官方文档: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
2.2 emptyDir 实战
bash
[root@hd1 ~]# vim emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-empty
spec:
containers:
- name: container-empty
image: docker.io/library/nginx:latest
volumeMounts: # 容器内的挂载点列表(将 volume 挂载到容器中)
- mountPath: /cache # 容器内的挂载路径(容器中访问 /cache 目录即可读写该存储卷)
name: cache-volume # 引用的 volume 名称(必须与 volumes 中定义的 name 一致)
volumes: # Pod级别的存储卷定义列表(在 Pod 内所有容器间共享)
- emptyDir: {} # emptyDir 类型的存储卷(空目录,Pod 创建时自动生成)
name: cache-volume # 该存储卷的名称(供容器通过 volumeMounts 引用)
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f emptydir.yaml
pod/pod-empty created
# 查看 pod 调度到哪个节点
[root@hd1 ~]# kubectl get pods -o wide | grep empty
pod-empty 1/1 Running 0 10.244.209.152 hd2
emptyDir 存在的意义就是让容器无感使用存储,把 emptyDir 当成 Pod 内部的共享内存/临时硬盘即可,无需关心它在宿主机上的真实物理位置。宿主机路径是 Kubelet 自动生成的随机 UUID 目录,对运维排障没有任何参考价值。
三、hostPath 存储卷
3.1 什么是 hostPath?
hostPath Volume 是指 Pod 挂载宿主机上的目录或文件 ,使得容器可以使用宿主机的文件系统进行存储。
特点:
- 节点级别的存储卷:数据存储在特定节点的文件系统上
- 持久性:Pod 被删除后,存储卷依然存在,不会被删除
- 前提条件:同一个 Pod 被重新调度到同一个节点上,对应的数据依然是存在的
3.2 hostPath 字段说明
bash
# 查看 hostPath 存储卷的用法
[root@hd1 ~]# kubectl explain pods.spec.volumes.hostPath
FIELDS:
path <string> -required- # 宿主机上的目录路径
type <string> # 目录类型(如 DirectoryOrCreate)
常见 type 值一览:
| type 值 | 本质属性 | 不存在时 Kubelet 的反应 |
|---|---|---|
DirectoryOrCreate |
执行动作 | 自动创建目录(权限 0755,属主为 root/kubelet) |
FileOrCreate |
执行动作 | 自动创建空文件 |
Directory |
仅声明/校验 | 报错拒绝启动(必须存在) |
File |
仅声明/校验 | 报错拒绝启动(必须存在) |
3.3 hostPath 实战
yaml
[root@hd1 ~]# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
name: test-nginx
volumeMounts: # 容器内的挂载点
- mountPath: /test-nginx # 把名为test-volume的卷挂载到容器中的/test-nginx目录
name: test-volume # 引用下面定义的 volume 名称
- image: docker.io/library/tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
name: test-tomcat
volumeMounts:
- mountPath: /test-tomcat #挂载到 Tomcat 容器的 /test-tomcat 目录
name: test-volume # 引用同一个 volume(共享存储)
volumes:
- name: test-volume # 存储卷名称(被上面的容器引用)
hostPath: # hostPath 类型:挂载宿主机目录
path: /data1 # 宿主机上的路径(必须存在,或根据 type 自动创建)
type: DirectoryOrCreate # 目录类型:如果不存在则自动创建
要注意大小写:hostPath
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f hostpath.yaml
pod/test-hostpath created
# 查看 pod 调度到了哪个物理节点
[root@hd1 ~]# kubectl get pods -o wide | grep hostpath
test-hostpath 2/2 Running 0 27s 10.244.169.82 hd3
# Pod 调度到了 hd3,登录到 hd3 机器查看是否创建了存储目录
[root@hd3 ~]# ll /data1/
total 0 # 已创建存储目录 /data1
# 在 hd3 上的 /data1 下创建一个目录测试
[root@hd3 ~]# cd /data1/
[root@hd3 data1]# mkdir aa
# 登录到 nginx 容器验证存储卷
[root@hd1 ~]# kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
root@test-hostpath:/# cd /test-nginx/ # /test-nginx/ 目录存在,说明宿主机目录已挂载到容器
root@test-hostpath:/test-nginx# ls
aa
# 登录到 tomcat 容器验证存储卷
[root@hd1 ~]# kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash
bash-4.4# cd /test-tomcat/
bash-4.4# ls
aa
结论: 同一个节点(宿主机)的 test-nginx 和 test-tomcat 两个容器是可以共享存储卷的。
3.4 hostPath 的缺点
- 单节点限制:Pod 删除之后,重新创建必须调度到同一个 Node 节点,数据才不会丢失
- 解决方案:可以使用分布式存储(如 NFS、CephFS、GlusterFS)
四、NFS 持久化存储
4.1 为什么使用 NFS?
hostPath 存储存在单点故障问题,Pod 挂载 hostPath 时只有调度到同一个节点数据才不会丢失。使用 NFS 作为持久化存储可以解决这个问题,因为 NFS 支持多个客户端同时挂载。
4.2 搭建 NFS 服务
bash
# 以 k8s 控制节点 hd1 作为 NFS 服务端
[root@hd1 ~]# yum -y install nfs-utils
# 在宿主机创建 NFS 需要的共享目录
[root@hd1 ~]# mkdir /data/volumes -p
# 配置 NFS 共享服务器上的 /data/volumes 目录
[root@hd1 ~]# vim /etc/exports
/data/volumes 192.168.1.0/24(rw,no_root_squash)
# 让修改后的 /etc/exports 配置文件立即生效(无需重启nfs)
[root@hd1 ~]# exportfs -arv
exporting 192.168.1.0/24:/data/volumes
# 启动 NFS 服务
[root@hd1 ~]# systemctl start nfs
[root@hd1 ~]# systemctl enable nfs
# 查看 NFS 是否启动成功
[root@hd1 ~]# systemctl status nfs
Active: active # 看到 active 说明 NFS 正常启动
# hd3 和 hd2 上也安装 NFS 驱动
[root@hd2 ~]# yum install nfs-utils -y
[root@hd2 ~]# systemctl enable nfs
# 在 hd2 上临时挂载测试
[root@hd2 ~]# mkdir /test
[root@hd2 ~]# mount 192.168.1.11:/data/volumes /test/
[root@hd2 ~]# df -h
192.168.1.11:/data/volumes 50G 5.2G 45G 11% /test # NFS 可以被正常挂载
# 手动卸载
[root@hd2 ~]# umount /test
4.3 Pod 挂载 NFS
官方文档: https://kubernetes.io/zh/docs/concepts/storage/volumes/
yaml
[root@hd1 ~]# cat nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-volume
spec:
containers:
- name: test-nfs
image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
volumeMounts: # 容器内的挂载点
- name: nfs-volumes # 引用的 volume 名称(必须与 volumes 中定义的一致)
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volumes # 存储卷名称
nfs: # 存储类型:NFS
path: /data/volumes # NFS 的共享目录
server: 192.168.1.11 # NFS 服务器的 IP 地址
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f nfs.yaml
# 查看 pod 是否创建成功
[root@hd1 volumes]# kubectl get pods -o wide | grep nfs
test-nfs-volume 1/1 Running 0 17s 10.244.169.83 hd3
# 登录到 NFS 服务器,在共享目录创建一个 index.html
[root@hd1 volumes]# cd /data/volumes/
[root@hd1 volumes]# cat index.html
Hello World
# 请求 pod,查看结果
[root@hd1 volumes]# curl 10.244.169.83
Hello World # 共享目录创建的 index.html 已经被 pod 挂载
# 登录到 pod 验证
[root@hd1 volumes]# kubectl exec -it test-nfs-volume -- /bin/bash
root@test-nfs-volume:/# cat /usr/share/nginx/html/index.html
Hello World
NFS 的优点: 支持多个客户端挂载,可以创建多个 Pod 挂载同一个 NFS 服务器共享出来的目录。
NFS 的缺点: NFS 服务本身存在单点故障风险------如果 NFS 服务器宕机,数据虽然不会丢失(数据仍存储在磁盘上),但所有依赖该 NFS 的 Pod 将无法正常读写数据,服务会中断。因此,生产环境建议使用**分布式存储(如 GlusterFS、CephFS)**来解决这个问题。
五、PV 和 PVC
5.1 什么是 PV?
PersistentVolume(PV,意为持久卷) 是 Kubernetes 集群中的一块存储资源,由管理员配置或使用存储类动态配置。是集群里"预先划分好的存储空间池"
- PV 是容量插件,本质上就是一块 "网络硬盘"
- 其生命周期独立于使用 PV 的任何单个 Pod
- PV 是集群级别的存储资源,独立于任何 Pod 存在
注意!PV与PVC是1:1绑定的,一个PV在同一时间只能被一个PVC调用!
5.2 什么是 PVC?
PersistentVolumeClaim(PVC,意为持久卷声明) ,是用户向 Kubernetes 集群提交的存储资源请求(Request),Pod 则通过引用 PVC 来使用与之绑定的 PV(持久卷),从而实现对底层存储的挂载。
PVC 在申请 PV 时可以请求特定的存储容量,访问模式
注意!与pv和pvc一对一不同,一个 PVC 可以在满足 PV 的 accessModes(访问模式)下被多个 Pod 同时调用
5.3 PV 和 PVC 的工作原理
- PV 是群集中的资源,PVC 是对这些资源的请求
- 生命周期概述:PV 和 PVC 遵循"供应(Provisioning)-> 绑定(Binding)-> 使用(Using)-> 回收(Reclaiming)"四个阶段。PVC 会与满足其容量和访问模式要求的 PV 进行一一绑定,直到被解绑前,该 PV 无法被其他 PVC 使用
使用 PV/PVC 的步骤如下:
- 管理员准备存储:在集群中预先创建好 PV(持久卷),需在 PV 中明确指定后端存储的地址(如 NFS IP 和路径)、容量大小及访问模式(如 ReadWriteMany)。
- 用户申请资源:创建 PVC(持久卷声明),在 PVC 中声明应用所需的存储容量和访问模式。
- 系统自动绑定:Kubernetes 会将 PVC 与集群中满足其容量和访问模式要求的 Available(空闲)状态 PV 进行一对一绑定。一旦绑定,该 PV 即被该 PVC 独占,无法再被其他 PVC 使用。
- Pod 挂载使用:在 Pod 的 YAML 中通过 persistentVolumeClaim 字段引用 PVC 名称,并将该卷挂载到容器的指定目录(如 /data)
注意:PVC 对 PV 的匹配是绝对的硬性要求,严格的一对一映射 ,匹配条件必须是全部满足,缺一不可。
若集群中没有任何 PV 能满足 PVC 的请求(容量不足或访问模式不匹配),则 PVC 会一直处于 Pending(等待) 状态,直到有合适的 PV 出现为止
PV资源使用的三层架构:
第一层(底层资源)管理员定义 PV :存储类型(nfs:、hostPath:、ceph:),总容量 ,访问模式(ReadWriteMany)
第二层(使用申请)用户定义 PVC :申请多大容量(storage: 2Gi),访问模式(ReadWriteMany)
第三层(挂载动作)Pod 定义挂载点:容器内路径(mountPath: /usr/share/nginx/html),引用哪个 PVC(claimName: my-pvc)
5.4 访问模式(ReadWriteMany)
在定义pv,以及pvc请求时,都要填写**访问模式(ReadWriteMany)**这一项
| 访问模式 | 缩写 | 核心含义 | 典型场景 |
|---|---|---|---|
| ReadWriteOnce | RWO | 只能被单个节点以读写方式挂载(该节点上的多个 Pod 可同时使用) | 数据库(MySQL/PostgreSQL) 需要本地持久化的中间件 |
| ReadOnlyMany | ROX | 可以被多个节点以只读方式挂载 | 共享配置文件 静态网站资源 公共只读数据 |
| ReadWriteMany | RWX | 可以被多个节点以读写方式挂载 | 共享文件存储(网盘后端) 多 Pod 协作处理的文件 日志收集共享目录 |
| ReadWriteOncePod | RWOP | (Kubernetes v1.29 稳定版) 只能被单个 Pod 以读写方式挂载(整个集群中唯一) | 需要严格独占的存储 防止多 Pod 并发写入损坏数据 |
注意事项:
- RWO 的"陷阱" :多个 Pod 可以 同时使用一个 RWO 卷,但必须全部调度到同一个节点上。如果 K8s 自动调度到不同节点,Pod 会挂载失败。
- 匹配逻辑 :PV 的
accessModes是"能力列表"(支持哪些模式),PVC 的accessModes是"请求"(只要一种)。绑定条件:PVC 请求的模式必须被 PV 支持(包含)。 - 后端限制 :即使 PV 声明支持 RWX,如果底层存储(如部分 NFS 配置)实际不支持多节点并发写入,仍可能数据损坏。
accessModes是 K8s 层面的准入控制,不强制校验后端能力。
5.5 回收策略(Reclaim Policy)
当删除 PVC 时,PVC 和 PV 的绑定会解除。解除之后,和 PVC 绑定的 PV 卷以及存储在其中的数据需要怎么处理?有两种策略:
| 回收策略 | 静态 PV(管理员手动创建) | 动态 PV(StorageClass 自动创建) |
|---|---|---|
| Retain(保留) | 默认 。PVC 删除后,PV 转为 Released(已释放), 数据保留 ,但 PV 无法自动复用,需管理员手动清理并重置状态。 |
可手动设置(极少使用)。 删除 PVC 后,后端存储资产(如云盘/NFS 子目录)不会被清理。 |
| Delete(删除) | 可手动设置(不推荐 ,易误删生产数据)。 删除 PVC 后,后端数据通常不会自动清理,易产生孤儿文件。 | 默认 。删除 PVC 后,PV 对象被删除, 后端存储资产同步自动清理。 |
六、PV 和 PVC 实战
6.1 准备 NFS 存储
bash
# 在宿主机创建 NFS 需要的共享目录
[root@hd1 ~]# mkdir /data/volume_test/v{1,2,3,4,5,6,7,8,9,10} -p
# 配置 NFS 共享宿主机上的 /data/volume_test/v1..v10 目录
[root@hd1 ~]# cat /etc/exports
/data/volumes 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v1 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v2 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v3 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v4 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v5 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v6 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v7 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v8 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v9 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v10 192.168.1.0/24(rw,no_root_squash)
# 重新加载配置,使配置生效
[root@hd1 ~]# exportfs -arv
6.2 查看 PV 定义字段
bash
# 查看定义 PV 需要的字段
[root@hd1 ~]# kubectl explain pv
FIELDS:
apiVersion <string>
kind <string>
metadata <Object>
spec <Object>
status <Object>
# 查看定义 NFS 类型的 PV 需要的字段
[root@hd1 ~]# kubectl explain pv.spec.nfs
FIELDS:
path <string> -required-
readOnly <boolean>
server <string> -required-
6.3 创建 PV
PV中我们需要指定存储容量,使用方式,存储类型这三个关键属性
参考文档: https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#reclaiming
bash
[root@hd1 ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume # 资源类型:持久化存储卷
metadata:
name: v1
spec:
capacity: # 存储容量
storage: 1Gi
accessModes: ["ReadWriteOnce"] #只允许一台客户机挂载这个共享目录,允许读写
nfs: # 存储类型:NFS
path: /data/volume_test/v1 # NFS 的共享目录
server: 192.168.1.11 # NFS 服务器的地址
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v2
spec:
capacity:
storage: 2Gi
accessModes: ["ReadWriteMany"] #允许多台客户机挂载这个共享目录,支持读写
nfs:
path: /data/volume_test/v2
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v3
spec:
capacity:
storage: 3Gi
accessModes: ["ReadOnlyMany"] #多节点挂载,只读
nfs:
path: /data/volume_test/v3
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v4
spec:
capacity:
storage: 4Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v4
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v5
spec:
capacity:
storage: 5Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v5
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v6
spec:
capacity:
storage: 6Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v6
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v7
spec:
capacity:
storage: 7Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v7
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v8
spec:
capacity:
storage: 8Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v8
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v9
spec:
capacity:
storage: 9Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v9
server: 192.168.1.11
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v10
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteOnce","ReadWriteMany"]
nfs:
path: /data/volume_test/v10
server: 192.168.1.11
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f pv.yaml
persistentvolume/v1 created
persistentvolume/v2 created
persistentvolume/v3 created
persistentvolume/v4 created
persistentvolume/v5 created
persistentvolume/v6 created
persistentvolume/v7 created
persistentvolume/v8 created
persistentvolume/v9 created
persistentvolume/v10 created
# 查看 PV 资源
[root@hd1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 21s
v10 10Gi RWO,RWX Retain Available 21s
v2 2Gi RWX Retain Available 21s
v3 3Gi ROX Retain Available 21s
v4 4Gi RWO,RWX Retain Available 21s
v5 5Gi RWO,RWX Retain Available 21s
v6 6Gi RWO,RWX Retain Available 21s
v7 7Gi RWO,RWX Retain Available 21s
v8 8Gi RWO,RWX Retain Available 21s
v9 9Gi RWO,RWX Retain Available 21s
#状态是Available,表示pv是可用的
访问模式说明:
| 访问模式 | 缩写 | 说明 |
|---|---|---|
ReadWriteOnce |
RWO | 卷可以被一个节点以读写方式挂载 |
ReadOnlyMany |
ROX | 卷可以被多个节点以只读方式挂载 |
ReadWriteMany |
RWX | 卷可以被多个节点以读写方式挂载 |
6.4 创建 PVC
创建pvc,和符合条件的pv绑定
yaml
[root@hd1 ~]#cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim # 资源类型:持久化存储卷申请(PVC)
metadata:
name: my-pvc
spec:
accessModes: ["ReadWriteMany"] # 请求的访问模式
resources: # 资源请求
requests:
storage: 2Gi # 请求的存储大小
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/my-pvc created
# 查看 PV 和 PVC
[root@hd1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 6m55s
v10 10Gi RWO,RWX Retain Available 6m55s
v2 2Gi RWX Retain Bound default/my-pvc 6m55s
#可以看到最符合我们要求的pv已经被我们定义的pvc绑定
v3 3Gi ROX Retain Available 6m55s
v4 4Gi RWO,RWX Retain Available 6m55s
v5 5Gi RWO,RWX Retain Available 6m55s
v6 6Gi RWO,RWX Retain Available 6m55s
v7 7Gi RWO,RWX Retain Available 6m55s
v8 8Gi RWO,RWX Retain Available 6m55s
v9 9Gi RWO,RWX Retain Available 6m55s
[root@hd1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound v2 2Gi RWX 18s
# STATUS 是 Bound,表示这个 PV 已经被 my-pvc 绑定
# PVC 绑定到了 v2 这个 PV,可使用的容量是 2Gi
PVC 匹配 PV 的规则:
- PVC 会从所有
Available状态的 PV 中寻找容量 >= PVC 请求容量的 PV - 同时要求访问模式必须完全匹配
- 当多个 PV 满足条件时,会优先选择容量最接近的进行绑定
6.5 创建 Pod 挂载 PVC
yaml
# cat pod_pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-pvc
spec:
containers:
- name: nginx
image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
volumeMounts: # 容器内的挂载点
- name: nginx-html # 引用的 volume 名称(与下方 volumes 中的 name 对应)
mountPath: /usr/share/nginx/html # 容器内的挂载路径
volumes:
- name: nginx-html # 存储卷名称
persistentVolumeClaim: # 存储卷类型:PVC(持久化存储卷申请)
claimName: my-pvc # 引用的 PVC 名称(必须与已创建的 PVC 名称一致)
bash
# 更新资源清单文件
[root@hd1 ~]# kubectl apply -f pod_pvc.yaml
pod/pod-pvc created
# 查看 pod 状态
[root@hd1 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-pvc 1/1 Running 0 69s 10.244.169.84 hd3 <none> <none>
# 测试存储卷
[root@hd1 ~]# cd /data/volume_test/v2
[root@hd1 v2]# echo 333 > index.html
# 访问 pod-pvc
[root@hd1 v2]# curl 10.244.169.84
333
6.6 删除Pod和PVC
bash
#先删除pod
[root@hd1 ~]# kubectl delete pod pod-pvc
#删除pvc
[root@hd1 ~]# kubectl delete -f pvc.yaml
#或
[root@hd1 ~]# kubectl delete pvc my-pvc
#查看pv的状态为Released,表示已经释放,此状态的pv不会被其他pvc所绑定
[root@hd1 ~]# kubectl get pv | grep v2
v2 2Gi RWX Retain Released default/my-pvc
#删除pv之后,/data/volume_test/v2目录中的数据依然还在
[root@hd1 ~]# cd /data/volume_test/v2
[root@hd1 v2]# ls
index.html
K8s 存储资源的删除顺序永远是 先删 Pod -> 再删 PVC -> 最后处理 PV,不然就会一直卡死
6.7 Released状态的PV恢复可用
只要pv状态不是Available,就不可被其他的pvc所使用
方法一:使用 kubectl patch(最快,适合脚本化)
kubectl patch pv <PV_NAME> -p '{"spec":{"claimRef": null}}'
bash
[root@hd1 ~]# kubectl patch pv v2 -p '{"spec":{"claimRef": null}}'
#查看v2的状态,已经恢复
[root@hd1 v4]# kubectl get pv | grep v2
v2 2Gi RWX Retain Available 37m
方法二:使用 kubectl edit(最安全,适合新手观察)
kubectl edit pv <PV_NAME>
在打开的编辑器中,找到spec下的 claimRef 字段(通常在最底部),将其整段删除,然后保存退出即可。

将以下代码全部删除即可:
yaml
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: my-pvc
namespace: default
resourceVersion: "152540"
uid: 2291ca68-dc06-471f-82e8-6e70f78e6c6e
七、K8s 存储类 StorageClass
7.1 什么是 StorageClass?
前面介绍的 PV 和 PVC 模式都需要先创建好 PV,然后定义 PVC 和 PV 进行一对一的绑定。
但是,如果 PVC 请求成千上万,那么就需要创建成千上万的 PV,对于运维人员来说维护成本很高。
Kubernetes 提供了一种自动创建 PV 的机制 ,叫做 StorageClass ,它的作用就是创建 PV 的模板。
K8s 集群管理员通过创建 StorageClass 可以动态生成存储卷 PV 供 K8s PVC 使用。
7.2 StorageClass 字段说明
bash
# 查看定义的 storageclass 需要的字段
[root@hd1 ~]# kubectl explain storageclass
FIELDS:
parameters <map[string]string>
Provisioner <string> -required- # 供应商,确定使用什么样的存储来创建 PV
什么是Provisioner?
-
配置层面:它是一个"名字(字符串)",是一个标识符(ID),K8s 通过这个名字去集群里找对应的、真正能干活的那个程序;
- 这个名字,必须和我们定义的 Pod 里的环境变量: PROVISIONER_NAME 一模一样
-
物理层面:它是一个"常驻后台的软件进程(Pod)"
- 它是运行在集群里的一个或多个 Pod (通常以 Deployment 或 StatefulSet 形式部署 ),是自定义的 Kubernetes 控制器(Controller),专门监听 API Server 发来的"创建 PVC"事件,根据 PVC 请求自动创建对应的 PV 和底层存储资源
-
工作流程:
- 当用户创建 PVC 时,这个软件会执行脚本逻辑,比如:
- 登录 NFS 服务器,执行 mkdir 创建子目录;
- 调用云厂商的 API(如阿里云/ AWS) ,执行 CreateVolume 接口创建一块新云盘。
- 操作完成后,Provisioner 会将该存储资源的信息封装成一个 PV 对象,并提交给 Kubernetes API Server,完成 PVC 与 PV 的自动绑定。
- 当用户创建 PVC 时,这个软件会执行脚本逻辑,比如:
常见 Provisioner(供应商):
| 存储类型 | Provisioner 名称 |
|---|---|
| NFS(外部) | nfs-subdir-external-provisioner |
| Ceph RBD | ceph.com/rbd |
| GlusterFS | kubernetes.io/glusterfs |
| AWS EBS | kubernetes.io/aws-ebs |
| GCE PD | kubernetes.io/gce-pd |
完整的 Provisioner 列表请参考:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/
7.3 StorageClass 工作流程图对照
这是StorageClass 工作流程图:
bash
┌─────────────────────────────────────────────────────────────────┐
│ StorageClass 工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 前置条件:管理员已提前搭建好存储后端(如 NFS 服务器) │
│ │
│ 1. 管理员创建 StorageClass(指定 provisioner) │
│ ↓ │
│ 2. 用户创建 PVC(指定 storageClassName: nfs) │
│ ↓ │
│ 3. StorageClass 调用 provisioner 自动创建 PV │
│ ↓ │
│ 4. PVC 自动绑定到新创建的 PV │
│ ↓ │
│ 5. Pod 使用 PVC 作为存储卷 │
│ │
└─────────────────────────────────────────────────────────────────┘
对比之前手动创建PV的流程图对照:
bash
┌─────────────────────────────────────────────────────────────────┐
│ 手动创建 PV 工作流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 管理员准备存储后端(如 NFS 目录) │
│ ↓ │
│ 2. 管理员手动创建多个 PV(指定容量、访问模式、存储路径) │
│ ↓ │
│ 3. 用户创建 PVC(声明所需容量和访问模式) │
│ ↓ │
│ 4. K8s 系统自动匹配并绑定到合适的 PV │
│ ↓ │
│ 5. Pod 通过 PVC 挂载存储卷 │
│ │
└─────────────────────────────────────────────────────────────────┘
7.4 StorageClass 实战:基于 NFS 的动态存储
步骤1:加载 NFS Provisioner 镜像
bash
# 把 nfs-subdir-external-provisioner.tar.gz 上传到 hd2 和 hd3 上,手动解压
[root@hd3 ~]# docker load -i nfs-subdir-external-provisioner.tar.gz
[root@hd2 ~]# docker load -i nfs-subdir-external-provisioner.tar.gz
步骤2:创建 ServiceAccount(SA)账号
什么是ServiceAccount(SA)账号?
ServiceAccount(SA,服务账号)是 Kubernetes 的一种标准 API 资源,是 Kubernetes 给 Pod 颁发的"身份证"和"工作证"。
指定了 ServiceAccount 之后,Pod 就有了合法的"身份"和相应的"权限",让它能安全地调用 Kubernetes API 来管理 PV 和 PVC。
如果 Pod 需要和 Kubernetes 主控(API Server)沟通,或者要操作集群内的资源,就必须挂载一个 ServiceAccount 来证明自己有这个权限。否则,默认账号 default 没有任何操作权限,Pod 里的程序想调用 API 创建
PV 时会直接报错"Forbidden(禁止访问)"。
yaml
[root@hd1 ~]# vim serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
bash
[root@hd1 nfs]# kubectl apply -f serviceaccount.yaml
serviceaccount/nfs-provisioner created
步骤3:对 ServiceAccount 账号授权
bash
[root@hd1 ~]# kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding \
--clusterrole=cluster-admin \
--serviceaccount=default:nfs-provisioner
把 Kubernetes 集群的超级管理员(也是就是 cluster-admin)的权限,授予给 default 命名空间下的 nfs-provisioner 服务账号(对应我们之前定义的SA中的name)
步骤4:准备 NFS 共享目录
安装nfs供应商程序
bash
[root@hd1 ~]# mkdir /data/nfs_pro -p
# 配置 NFS 共享
[root@hd1 ~]# cat /etc/exports
/data/volumes 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v1 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v2 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v3 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v4 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v5 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v6 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v7 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v8 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v9 192.168.1.0/24(rw,no_root_squash)
/data/volume_test/v10 192.168.1.0/24(rw,no_root_squash)
#上面是我们之前实验的共享目录
/data/nfs_pro 192.168.1.0/24(rw,no_root_squash) #这里是新增的共享目录
#刷新配置
[root@hd1 ~]# exportfs -arv
步骤5:部署 NFS Provisioner
bash
[root@hd1 ~]# cat nfs-deployment.yaml
kind: Deployment # 资源类型:Deployment(无状态应用)
apiVersion: apps/v1 # API 版本
metadata:
name: nfs-provisioner # Deployment 名称
spec:
selector: # 标签选择器(管理 Pod)
matchLabels:
app: nfs-provisioner
replicas: 1 # 副本数(只需 1 个实例)
strategy:
type: Recreate # 更新策略:重建(先删旧 Pod,再创建新 Pod)
template: # Pod 模板
metadata:
labels:
app: nfs-provisioner # Pod 标签(与 selector 匹配)
spec:
serviceAccount: nfs-provisioner # 使用的 ServiceAccount 账号(用于授权访问 API Server)
containers:
- name: nfs-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
imagePullPolicy: IfNotPresent
# ---------- 挂载点 ----------
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes # 容器内的挂载路径
# ---------- 环境变量(核心配置) ----------
env:
- name: PROVISIONER_NAME
value: example.com/nfs # 供应器名称(StorageClass 通过此名称找到它)
- name: NFS_SERVER
value: 192.168.1.11 # NFS 服务器地址
- name: NFS_PATH
value: /data/nfs_pro/ # NFS 共享目录(在此目录下自动创建子目录)
# ---------- 存储卷 ----------
volumes:
- name: nfs-client-root
nfs: # 存储类型:NFS
server: 192.168.1.11
path: /data/nfs_pro/ # 挂载 NFS 的共享目录
bash
#更新资源清单文件
[root@hd1 ~]# kubectl apply -f nfs-deployment.yaml
deployment.apps/nfs-provisioner created
#查看nfs-provisioner是否正常运行
[root@hd1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-provisioner-9877d685d-v8gp6 1/1 Running 0 75s
步骤6:创建 StorageClass
bash
[root@hd1 ~]# vim nfs-storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: nfs
provisioner: example.com/nfs # 必须与 NFS Provisioner 的 PROVISIONER_NAME 保持一致
bash
[root@hd1 ~]# kubectl apply -f nfs-storageclass.yaml
[root@hd1 ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs example.com/nfs Delete Immediate false 15s
# StorageClass 创建成功
步骤7:创建 PVC(通过 StorageClass 动态生成 PV)
使用StorageClass 动态生成 PV,就不用我们提前创建PV了,直接在PVC中申请对应规格的存储资源即可
yaml
[root@hd1 ~]# vim claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim1 # PVC 名称
spec:
accessModes: ["ReadWriteMany"] # 访问模式:允许集群中多个节点同时以读写方式挂载(RWX)
resources: # 资源请求块
requests:
storage: 1Gi
storageClassName: nfs # 指定使用哪个 StorageClass
bash
#应用
[root@hd1 ~]# kubectl apply -f claim.yaml
persistentvolumeclaim/test-claim1 created
#查看pvc
[root@hd1 nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim1 Bound pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a 1Gi RWX nfs 10s
#查看PV
[root@hd1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON
pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a 1Gi RWX Delete Bound default/test-claim1 nfs
通过上面可以看到名为 test-claim1 的 PVC 已经成功创建,绑定的 PV 是 pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a。这个 PV 是由 StorageClass 调用 NFS Provisioner 自动生成的。
步骤8:创建 Pod 挂载 PVC
yaml
[root@hd1 ~]# vim read-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: read-pod
spec:
containers:
- name: read-pod
image: docker.io/library/nginx:latest
imagePullPolicy: IfNotPresent
volumeMounts: # 容器内的挂载点列表
- name: nfs-pvc # 要挂载的卷名称(必须与下方 volumes 中的 name 一致)
mountPath: /usr/share/nginx/html # 容器内的挂载路径
restartPolicy: "Never" # 重启策略:Pod 退出后不再重启
volumes:
- name: nfs-pvc
persistentVolumeClaim: # 存储卷类型:PVC(持久卷声明)
claimName: test-claim1 # 引用的 PVC 名称(必须与已创建的 PVC 名称一致)
bash
[root@hd1 nfs]# kubectl apply -f read-pod.yaml
pod/read-pod created
[root@hd1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
read-pod 1/1 Running 0 10s
步骤9:挂载验证
StorageClass 动态创建 PV 时,Provisioner 会在 NFS 服务器的共享根目录下,为每一个 PVC 单独创建一个唯一的子目录。这个子目录就是后端真正的物理存储空间 。
动态创建的子目录和物理路径,与手动 PV 里指定的路径(如 /data/volume_test/v2)的作用完全一致,本质上都是存放数据的 NFS 文件夹 ,也都是 Kubernetes 的 PV ,唯一的区别是:手动 PV 需要管理员提前'造好',而动态 PV 是由 Provisioner 按需'即时生成'的。
bash
#查看我们挂载的共享目录
[root@hd1 ~]# ls /data/nfs_pro/
default-test-claim1-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a #这个就是StorageClass为PVC自动生成的子目录
这个子目录命名非常长,比较复杂,如果业务量比较大,可用这两种方式查找对应的目录:
bash
#方法一,直接查看 PVC 绑定了哪个 PV
#这个命令可通过pvc名称查看绑定的PV名称,也就是VOLUME项
[root@hd1 ~]# kubectl get pvc test-claim1
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
test-claim1 Bound pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a 1Gi RWX nfs
#查看这个 PV 的详细信息
[root@hd1 ~]# kubectl describe pv pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
Name: pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: example.com/nfs
Finalizers: [kubernetes.io/pv-protection]
StorageClass: nfs
Status: Bound
Claim: default/test-claim1
Reclaim Policy: Delete
Access Modes: RWX
VolumeMode: Filesystem
Capacity: 1Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 192.168.1.11
#输出内容的path项就是那个具体的子目录路径
Path: /data/nfs_pro/default-test-claim1-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
ReadOnly: false
Events: <none>
#方法二,在 NFS 服务器上利用命名规则反查
#NFS Provisioner 自动创建的子目录命名格式通常为:
#<命名空间>-<PVC名称>-<PV的UID>
#所以在服务器上直接找前缀就行:
[root@hd1 ~]# ls -d /data/nfs_pro/default-test-claim1-*
/data/nfs_pro/default-test-claim1-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
挂载验证
bash
[root@hd1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
read-pod 1/1 Running 0 11m 10.244.169.87 hd3
#可以看到pod的IP地址是10.244.169.87
#切入我们的挂载子目录
[root@hd1 ~]# cd /data/nfs_pro/default-test-claim1-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
#创建一个首页文件
[root@hd1 nfs_pro]# echo "Hello World" > index.html
#访问验证
[root@hd1 default-test-claim1-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a]# curl 10.244.169.87
Hello World
步骤10:回收策略验证
通过存储类创建的pvc以及pv,默认的回收策略是delete(kubectl get sc)删除pvc的时候也会删除pv
bash
[root@hd1 ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs example.com/nfs Delete Immediate false 63m
所以在删除PVC之后,默认PV也会被删除
bash
#先删除pod
[root@hd1 nfs_pro]# kubectl delete pod read-pod
pod "read-pod" deleted
#再删除pvc
[root@hd1 ~]# kubectl delete pvc test-claim1
persistentvolumeclaim "test-claim1" deleted
#过滤一下pv,没有输出,说明对应的pv也被删除了
[root@hd1 nfs_pro]# kubectl get pv | grep pvc
但是即使pv随着pvc一起被删除,对应的目录以及数据依旧存在
bash
#在共享目录下查看,之前的pv目录依旧存在
[root@hd1 nfs_pro]# ls
archived-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a
[root@hd1 nfs_pro]# ls archived-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a/
index.html
[root@hd1 nfs_pro]# cat archived-pvc-8cde1663-5c56-4121-a507-91ac2ea5f82a/index.html
Hello World
PV 是 Kubernetes 里的一种资源规格和记录指针。删除 PV 对象(或 PVC 连带 PV)时,物理数据是否保留,完全取决于后端的存储插件/回收脚本是否执行成功------即使在策略为 Delete 的情况下,如果 Provisioner 配置为归档(Archive)或因故障未能执行清理,数据依然会完好无损地留在磁盘上。因此,绝不能认为'删了 PV 就等于删了数据'。
回收策略深入剖析
sc默认的回收策略明明是delete,根据我们上面的实验,为什么 Delete 策略下,物理数据依然保留?
K8s 的 Delete 策略与后端物理删除的关系:Delete 策略触发的是 Kubernetes 对底层存储资产的清理动作,但像 nfs-subdir-external-provisioner 这样的常用插件,为了防止生产事故,默认往往采用**"归档(Archive)/重命名"**机制,而不是直接执行粗暴的 rm -rf。这也是为什么我们的物理数据依旧被保留的原因。
生产运维警示 :绝不能把 Kubernetes 的 Delete 策略当成绝对安全的物理销毁手段,在生产环境中,如果要彻底清理空间,往往需要运维人员手动去 NFS 服务器上清理归档目录。
深入理解:
- 对象与存储解耦:PV 只是 Kubernetes 集群中的一个元数据对象和指针。删除 PV/PVC 仅仅是清除了 Kubernetes 内部的管控记录,并不能直接等同于销毁底层物理介质。
- Provisioner 的安全保护机制 :像 nfs-subdir-external-provisioner 这样的动态供应器,在处理 Delete 策略时,为了防止生产环境中因为误删 PVC导致数据彻底丢失、造成灾难性后果,其默认行为通常是将目录重命名归档(Archive),而不是直接执行 rm -rf。
- 运维结论:生产环境中如果确认不需要这些数据,需要运维人员登录 NFS 服务器手动清理这些归档目录释放磁盘空间。
踩坑记录
在实施步骤5:部署 NFS Provisioner的时候,更新玩资源清单文件,却卡在这里:
bash
[root@hd1 ~]# kubectl get pods | grep nfs
nfs-provisioner-9877d685d-k9xlm 0/1 ContainerCreating 0 3m3s
一直是0/1,说明没有正常运行起来
决定先查看一下这个pod的详细信息,看看发生了什么
bash
[root@hd1 ~]# kubectl describe pod nfs-provisioner-9877d685d-k9xlm
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 16m default-scheduler Successfully assigned default/nfs-provisioner-9877d685d-k9xlm to hd3
Warning FailedMount 16m kubelet MountVolume.SetUp failed for volume "nfs-client-root" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.1.11:/data/nfs_pro/ /var/lib/kubelet/pods/6768a6d2-90f3-4234-9c76-032a90ec5806/volumes/kubernetes.io~nfs/nfs-client-root
Output: Created symlink /run/systemd/system/remote-fs.target.wants/rpc-statd.service → /usr/lib/systemd/system/rpc-statd.service.
mount.nfs: mounting 192.168.1.11:/data/nfs_pro/ failed, reason given by server: No such file or directory
Warning FailedMount 5m45s kubelet Unable to attach or mount volumes: unmounted volumes=[nfs-client-root], unattached volumes=[kube-api-access-smt5v nfs-client-root]: timed out waiting for the condition
Warning FailedMount 71s (x6 over 14m) kubelet Unable to attach or mount volumes: unmounted volumes=[nfs-client-root], unattached volumes=[nfs-client-root kube-api-access-smt5v]: timed out waiting for the condition
Warning FailedMount 28s (x15 over 16m) kubelet MountVolume.SetUp failed for volume "nfs-client-root" : mount failed: exit status 32
Mounting command: mount
Mounting arguments: -t nfs 192.168.1.11:/data/nfs_pro/ /var/lib/kubelet/pods/6768a6d2-90f3-4234-9c76-032a90ec5806/volumes/kubernetes.io~nfs/nfs-client-root
Output: mount.nfs: mounting 192.168.1.11:/data/nfs_pro/ failed, reason given by server: No such file or directory
发现核心错误是:mount.nfs: mounting 192.168.1.11:/data/nfs_pro/ failed, reason given by server: No such file or directory
意思是对应的共享目录:/data/nfs_pro 不存在。
查看,发现是存在的
bash
[root@hd1 ~]# ls /data/
nfs_pro
但是明明已经创建,为什么会报错?先看看是不是 /etc/exports 是否包含正确的共享配置:
bash
[root@hd1 ~]# cat /etc/exports | grep nfs_pro
发现问题了,原来是我们忘记配置了...
加入配置,重载配置:
bash
[root@hd1 ~]# cat /etc/exports | grep nfs_pro
/data/nfs_pro 192.168.1.0/24(rw,no_root_squash)
#重载配置
[root@hd1 ~]# exportfs -arv
删掉卡死的pod,让deployment自己重建
bash
[root@hd1 ~]# kubectl delete pod nfs-provisioner-9877d685d-k9xlm
删除完毕,再次查看,已经成功创建
bash
[root@hd1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-provisioner-9877d685d-v8gp6 1/1 Running 0 75s
八、核心知识点总结
8.1 四种存储方案对比
| 存储类型 | 数据持久性 | 适用范围 | 适用场景 |
|---|---|---|---|
| emptyDir | ❌ Pod 删除即丢失 | 单 Pod | 临时缓存、共享目录 |
| hostPath | ✅ 节点级持久化 | 单节点 | 节点本地存储,需固定调度 |
| NFS | ✅ 集群级持久化 | 多节点共享 | 多 Pod 共享读写,数据集中管理 |
| PV/PVC | ✅ 集群级持久化 | 多节点共享 | 存储资源抽象,解耦应用与存储 |
| StorageClass | ✅ 集群级持久化 | 多节点共享 | 动态自动创建 PV,降低运维成本 |
8.2 核心概念层级关系
StorageClass(自动创建 PV 的模板)
↓ 动态生成
PV(集群存储资源)
↓ 绑定
PVC(存储资源请求)
↓ 挂载
Pod(应用容器)
8.3 核心命令速查
| 命令 | 作用 |
|---|---|
kubectl explain pods.spec.volumes |
查看支持的存储类型 |
kubectl get pv |
查看所有 PersistentVolume |
kubectl get pvc |
查看所有 PersistentVolumeClaim |
kubectl get storageclass |
查看所有 StorageClass |
kubectl describe pv <名称> |
查看 PV 详细信息 |
kubectl describe pvc <名称> |
查看 PVC 详细信息 |
8.4 重要概念速查
| 概念 | 说明 |
|---|---|
| emptyDir | 临时存储,Pod 删除数据丢失 |
| hostPath | 宿主机目录挂载,数据保存在节点本地 |
| PV(PersistentVolume) | 集群级别的存储资源抽象 |
| PVC(PersistentVolumeClaim) | 用户对存储资源的请求 |
| StorageClass | 动态生成 PV 的模板 |
| Provisioner | StorageClass 的供应商,负责实际创建存储 |
| Reclaim Policy | PV 回收策略:Retain(保留)或 Delete(删除) |
| Access Mode | 访问模式:RWO(单节点读写)、ROX(多节点只读)、RWX(多节点读写) |