1、在k8s中为什么要做持久化存储?
在k8s中部署的应用都是以pod容器的形式运行的,假如我们部署MySQL、Redis等数据库,需要对这些数据库产生的数据做备份。因为Pod是有生命周期的,如果pod不挂载数据卷,那pod被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod数据持久化存储。
1.1 临时卷(生命周期与 Pod 绑定)
这类卷在 Pod 创建时产生,Pod 删除时数据随之销毁(容器崩溃重启不影响数据),适合临时缓存、配置文件注入等场景。
| 卷类型 | 状态 | 用途说明 |
|---|---|---|
emptyDir |
稳定 | Pod 级别的空目录,所有容器可读写。可用 medium: Memory 将存储介质切换为内存(tmpfs)。适合临时缓存、排序中间数据、多容器间共享文件。 |
configMap |
稳定 | 将 ConfigMap 的键值对以文件形式挂载到容器,通常用于注入配置文件。始终以只读方式挂载。 |
secret |
稳定 | 与 configMap 类似,但用于挂载敏感数据(密码、Token、证书)。数据在节点上以 tmpfs 存储,不会持久化到磁盘。 |
downwardAPI |
稳定 | 将 Pod 自身的元数据(如 Pod IP、节点名、标签、注解、资源限制)以文件形式暴露给容器,供应用读取。 |
projected |
稳定 | 将多个卷源(secret、configMap、downwardAPI、serviceAccountToken)投射到同一个目录中,便于统一管理多种配置来源。 |
ephemeral |
稳定 | 由普通存储驱动(通常是 CSI 驱动)处理的临时卷,生命周期与 Pod 绑定。与 emptyDir 的区别在于数据可以落到外部存储系统,而非仅节点本地。 |
1.2 持久卷(生命周期独立于 Pod)
这类卷的数据在 Pod 删除后仍然存在,适合数据库、有状态应用等需要持久化数据的场景。
| 卷类型 | 状态 | 用途说明 |
|---|---|---|
persistentVolumeClaim |
稳定 | 生产环境最推荐的持久化方式。 通过 PVC 引用已绑定的 PV,将持久存储挂载到 Pod。解耦了存储供给与 Pod 定义。 |
csi |
稳定 | 现代存储集成标准。 通过 CSI 驱动接入外部存储系统(如云盘、Ceph、NFS CSI 等),支持动态供给、快照、扩容等高级能力。 |
local |
稳定 | 直接使用节点上的本地磁盘、分区或目录。只能通过 PVC 引用(不能内联在 Pod 中),性能高但数据与节点强绑定,节点故障时数据可能不可用。 |
hostPath |
稳定(但不建议生产使用) | 将宿主机文件或目录挂载到 Pod。单节点测试可用,多节点生产环境禁用------Pod 被调度到其他节点后无法访问原数据,且存在安全风险。 |
1.3 网络存储卷(in-tree,多数已弃用或移除)
这些是早期内置在 Kubernetes 代码中的存储插件,目前官方已全面转向 CSI。以下列出仍可见于 API 中的类型,多数已弃用,不建议在新部署中使用。
| 卷类型 | 状态 | 用途说明 |
|---|---|---|
awsElasticBlockStore |
已弃用 | AWS EBS 块存储,只能以读写一次(RWO)方式挂载,需与 kubelet 在同一可用区。已由 EBS CSI 驱动替代。 |
azureDisk |
已弃用 | Azure Data Disk,绑定挂载到 Pod。已由 Azure Disk CSI 驱动替代。 |
azureFile |
已弃用 | Azure File Service(SMB 文件共享),支持多节点读写(RWX)。已由 Azure File CSI 驱动替代。 |
cephfs |
已移除 | Ceph 文件系统挂载,支持 RWX。已于较新版本中移除,应使用 CephFS CSI 驱动。 |
cinder |
已弃用 | OpenStack Cinder 卷,需与 kubelet 在同一区域。已弃用。 |
gcePersistentDisk |
已弃用/移除 | GCP 持久磁盘,v1.17 弃用,v1.28 移除。由 GCP PD CSI 驱动替代。 |
glusterfs |
已弃用 | GlusterFS 网络文件系统,不支持所有权管理和 SELinux 重标记。 |
iscsi |
稳定(legacy) | iSCSI 块存储,只能读写一次。仍可用,但建议评估 CSI 替代方案。 |
nfs |
稳定(legacy) | NFS 网络文件系统,支持 RWX。配置简单,但性能敏感场景需谨慎评估。 |
rbd |
稳定(legacy) | Ceph Rados Block Device,支持所有权管理和 SELinux 重标记。建议迁移至 Ceph CSI。 |
vsphereVolume |
已弃用 | VMware vSphere 卷,已由 vSphere CSI 驱动替代。 |
portworxVolume |
已弃用 | Portworx 卷资源。 |
photonPersistentDisk |
已弃用 | Photon Controller 持久磁盘。 |
quobyte |
已弃用 | Quobyte 挂载,不支持所有权管理和 SELinux 重标记。 |
scaleIO |
已弃用 | ScaleIO 持久卷。 |
storageos |
已弃用 | StorageOS 持久卷。 |
flexVolume |
已弃用 | 基于 exec 的通用插件机制,v1.23 起弃用,由 CSI 替代。 |
flocker |
已移除 | Flocker 代理挂载的卷,已从 Kubernetes 中移除。 |
1.4 特殊用途卷
| 卷类型 | 状态 | 用途说明 |
|---|---|---|
fc(Fibre Channel) |
稳定(legacy) | 光纤通道块存储,只能读写一次。需预先配置 FC SAN Zoning 和目标 WWN。 |
gitRepo |
已禁用/移除 | 将 Git 仓库内容克隆到卷中。自 v1.11 弃用,v1.35 后不再提供。官方推荐做法:用 InitContainer 克隆仓库到 emptyDir,再将 emptyDir 挂载到业务容器。 |
1.5 选型优先级
-
持久化存储 :首选
persistentVolumeClaim+ CSI 驱动(或云厂商 CSI 驱动),这是生产环境的推荐做法。 -
配置文件/敏感数据注入 :使用
configMap、secret、projected、downwardAPI。 -
临时缓存/多容器共享 :使用
emptyDir(需要内存速度时设medium: Memory)。 -
本地高性能持久存储 :使用
local(通过 PVC 引用),适用于对 IO 延迟要求极高的场景,但需接受数据与节点绑定的风险。 -
避免 :
hostPath(生产多节点)、所有已弃用/移除的 in-tree 网络存储卷(如awsElasticBlockStore、azureDisk、gcePersistentDisk等)。
2、k8s持久化存储:emptyDir
emptyDir类型的Volume是在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个
目录,因此无需指定宿主机Node上对应的目录文件。这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被永久删除。emptyDir Volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。
bash
# 创建 emptydir.yaml
vi emptydir.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 Pod 等核心资源
kind: Pod # 资源类型为 Pod,直接创建一个 Pod
metadata: # Pod 的元数据信息
name: pod-empty # Pod 的名称
spec: # Pod 的期望状态
containers: # 容器列表
- name: container-empty # 容器名称
image: nginx # 容器使用的镜像为 nginx
volumeMounts: # 容器内的卷挂载配置
- mountPath: /cache # 将卷挂载到容器内的 /cache 目录
name: cache-volume # 挂载的卷名称,需与下方 volumes 中的 name 一致
volumes: # Pod 的卷列表
- name: cache-volume # 卷的名称,供 volumeMounts 引用
emptyDir: {} # 定义一个 emptyDir 类型的卷,{} 表示使用默认配置
bash
# 启动pod
kubectl apply -f emptydir.yaml
# 查看pod状态
kubectl get pods -o wide | grep empty
# 查看pod的uid
kubectl get pods pod-empty -o yaml | grep uid

这是我我们发现pod-empty已经在k8s-node-1上部署了,并且uid: 36411ec7-3274-4cf8-b2f6-945acc3064c5,于是我们登录上k8s-node-1
然后执行如下命令
bash
tree /var/lib/kubelet/pods/36411ec7-3274-4cf8-b2f6-945acc3064c5

由此可见,临时目录位于当前pod部署在哪个节点上的对应目录。
临时目录在本地的/var/lib/kubelet/pods/36411ec7-3274-4cf8-b2f6-945acc3064c5/volumes/kubernetes.io~empty-dir/cache-volume/下
这时候我们删除这个pod,然后再查看这个临时目录是否存在,验证emptydir这种方式,是跟随pod生命周期存在,即pod存在则临时目录存在,pod不存在临时目录将不存在
bash
# k8s-master上执行
kubectl delete -f emptydir.yaml
# k8s-node-1上执行
tree /var/lib/kubelet/pods/36411ec7-3274-4cf8-b2f6-945acc3064c5


由此可见删除pod后,临时目录也不存在,验证通过。
3、k8s持久化存储:hostPath
hostPath Volume是指Pod挂载宿主机上的目录或文件。hostPath Volume使得容器可以使用宿主
机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在pod被删除,这个存储卷还是存在的,不会被删除,所以只要同一个pod被调度到同一个节点上来,在pod被删除重新被调度到这个节点之后,对应的数据依然是存在的。
bash
# 创建hostpath.yaml
vi hostpath.yaml
#填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 Pod 等核心资源
kind: Pod # 资源类型为 Pod,直接创建一个 Pod
metadata: # Pod 的元数据信息
name: test-hostpath # Pod 的名称
spec: # Pod 的期望状态
containers: # 容器列表,该 Pod 包含两个容器
- image: nginx # 第一个容器使用的镜像为 nginx
imagePullPolicy: IfNotPresent # 镜像拉取策略:本地不存在时才拉取
name: test-nginx # 第一个容器名称
volumeMounts: # 第一个容器内的卷挂载配置
- mountPath: /test-nginx # 将卷挂载到容器内的 /test-nginx 目录
name: test-volume # 挂载的卷名称,需与下方 volumes 中的 name 一致
- image: tomcat:8.5-jre8-alpine # 第二个容器使用的镜像为 tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent # 镜像拉取策略:本地不存在时才拉取
name: test-tomcat # 第二个容器名称
volumeMounts: # 第二个容器内的卷挂载配置
- mountPath: /test-tomcat # 将卷挂载到容器内的 /test-tomcat 目录
name: test-volume # 挂载的卷名称,需与下方 volumes 中的 name 一致
volumes: # Pod 的卷列表
- name: test-volume # 卷的名称,供上方两个容器的 volumeMounts 引用
hostPath: # 使用 hostPath 类型的卷,挂载节点上的目录或文件
path: /data1 # 节点上的路径,Pod 调度到哪个节点就使用哪个节点的 /data1
type: DirectoryOrCreate # 如果 /data1 目录不存在,则自动创建
bash
# 启动pod
kubectl apply -f hostpath.yaml
#查看pod调度到了哪个物理节点
kubectl get pods -o wide | grep hostpath


可以看到pod调度到了k8s-node-1上,现在登录k8s-node-1,然后执行命令查看是否创建了存储目录
bash
# 登录k8s-node-1服务器
ll /data1/
#在k8s-node-1上的/data1下创建一个目录
mkdir -p /data1/aa


测试存储卷是否可以正常使用,登录到nginx容器
bash
# k8s-master上执行命令
kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
# 查看/test-nginx目录下是否有aa目录
ls /test-nginx

测试存储卷是否可以正常使用,登录到nginx容器
bash
kubectl exec -it test-hostpath -c test-tomcat -- /bin/bash
ls /test-tomcat

通过上面测试可以看到,同一个pod里的test-nginx和test-tomcat这两个容器是共享存储卷的
最后我们删除hostpath.yaml资源,然后再查看k8s-node-1节点上的/data1/aa目录是否存在
bash
# k8s-master执行命令
kubectl delete -f hostpath.yaml
# k8s-node-1 执行命令
ls /data1


可见就算是pod被删除了k8s-node-1上的/data1/aa目录依旧存在,验证通过。
hostpath存储卷缺点:单节点
pod删除之后重新创建必须调度到同一个node节点,数据才不会丢失
可以用分布式存储:nfs,cephfs,glusterfs
4、k8s持久化存储:nfs
hostPath存储,存在单点故障,pod挂载hostPath时,只有调度到同一个节点,数据才不
会丢失。那可以使用nfs作为持久化存储。
4.1 搭建nfs服务
以k8s-master的控制节点作为NFS服务端
bash
# k8s-master、k8s-node-1、k8s-node-2 上同时安装nfs-utils软件
yum install nfs-utils -y

bash
# k8s-master上执行
mkdir /data/volumes -pv

bash
# k8s-master、k8s-node-1、k8s-node-2 同时执行 配置nfs共享服务器上的/data/volumes目录
systemctl start nfs
systemctl enable nfs
#k8s-master上执行命令,no_root_squash:用户具有根目录的完全管理访问权限
vi /etc/exports
/data/volumes *(rw,no_root_squash)
#k8s-master上执行命令使NFS配置生效
exportfs -arv

k8s-node-1和k8s-node-2挂载共享目录
bash
# k8s-node-1、k8s-node-2同时执行如下命令
mkdir /test
mount 192.168.138.135:/data/volumes /test/
# 卸载卷命令 这个命令可以不用执行
umount /test

4.2 创建nfs.yaml
bash
# k8s-master 上创建nfs.yaml
vi nfs.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 Pod 等核心资源
kind: Pod # 资源类型为 Pod,直接创建一个 Pod
metadata: # Pod 的元数据信息
name: test-nfs-volume # Pod 的名称
spec: # Pod 的期望状态
containers: # 容器列表
- name: test-nfs # 容器名称
image: nginx # 容器使用的镜像为 nginx
imagePullPolicy: IfNotPresent # 镜像拉取策略:本地不存在时才拉取
ports: # 容器端口列表
- containerPort: 80 # Pod 中容器需要暴露的端口
protocol: TCP # 端口使用的协议为 TCP
volumeMounts: # 容器内的卷挂载配置
- name: nfs-volumes # 挂载的卷名称,需与下方 volumes 中的 name 一致
mountPath: /usr/share/nginx/html # 将卷挂载到容器内的 nginx 默认网站目录
volumes: # Pod 的卷列表
- name: nfs-volumes # 卷的名称,供上方容器的 volumeMounts 引用
nfs: # 使用 NFS 类型的卷
path: /data/volumes # NFS 服务器上共享的目录路径
server: 192.168.138.135 # NFS 服务器的 IP 地址
bash
# 启动pod
kubectl apply -f nfs.yaml
# 查看pod
kubectl get pods -o wide | grep nfs
# 在k8s-master上的/data/volumes共享目录创建一个index.html
cd /data/volumes
vi index.html
# 填入以下内容
Hello, Everyone
My name is Eminem
My Chat is 226171130

请求pod,看结果
bash
curl 10.244.109.120

通过上面可以看到,在共享目录创建的index.html已经被pod挂载了

上面说明挂载nfs存储卷成功了,nfs支持多个客户端挂载,可以创建多个pod,挂载同一个nfs
服务器共享出来的目录;但是nfs如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs
5、k8s持久化存储:PVC
5.1 k8s PV是什么?
PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。它是集群中的资源,就像pod是k8s集群资源一样。PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。
5.2 k8s PVC是什么?
PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储
卷。它类似于一个pod。Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资(CPU和内存)。pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。
5.3 k8s PVC和 PV 工作原理
PV是群集中的资源。PVC是对这些资源的请求。
PV和PVC之间的相互作用遵循以下生命周期:
(1)pv的供应方式
可以通过两种方式配置PV:静态或动态。
静态的:
集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于
Kubernetes API中,可供使用。
动态的:
当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,群集可能会尝试为PVC专门动
态配置卷。此配置基于StorageClasses,PVC必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。
(2)绑定
用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态
(3)使用
a)需要找一个存储服务器,把它划分成多个存储空间;
b)k8s管理员可以把这些存储空间定义成多个pv;
c)在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的
访问模式,找到合适的pv;
d)pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储
卷
e)pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了;
f)我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处
于pending状态。
(4)回收策略
当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会
解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除:Retain
Recycle(不推荐使用)
Delete
1、Retain
当删除pvc的时候,pv仍然存在,处于released状态,但是它不能被其他pvc绑定使用,里面的数
据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略
Delete
删除pvc时即会从Kubernetes中移除PV,也会从相关的外部设施中删除存储资产。
5.4 创建pod,使用pvc作为持久化存储卷
创建pod,使用pvc作为持久化存储卷
bash
# k8s-master上执行
mkdir /data/volume_test/v{1,2} -pv
# 修改/etc/exports文件
vi /etc/exports
#在末尾添加如下内容
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
#重新加载配置,使配置成效
exportfs -arv

5.4.1 编写pv的资源清单文件
bash
# k8s-master上创建pv.yaml文件
vi pv.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 PersistentVolume 等核心资源
kind: PersistentVolume # 资源类型为 PersistentVolume(PV),表示集群中的一块存储
metadata: # PV 的元数据信息
name: v1 # 第一个 PV 的名称(注意:YAML 中 name 与 v1 之间需要空格)
spec: # PV 的期望状态
capacity: # 存储容量配置
storage: 1Gi # PV 的存储空间容量为 1Gi
accessModes: ["ReadWriteOnce"] # 访问模式:ReadWriteOnce,表示只能被单个节点以读写方式挂载
nfs: # 使用 NFS 类型的存储
path: /data/volume_test/v1 # 把 NFS 的存储空间创建成 PV,对应 NFS 服务器上的路径
server: 192.168.138.135 # NFS 服务器的地址
---
apiVersion: v1 # 第二个资源,同样使用 v1 API 版本
kind: PersistentVolume # 资源类型为 PersistentVolume(PV)
metadata: # PV 的元数据信息
name: v2 # 第二个 PV 的名称(注意:YAML 中 name 与 v2 之间需要空格)
spec: # PV 的期望状态
capacity: # 存储容量配置
storage: 2Gi # PV 的存储空间容量为 2Gi
accessModes: ["ReadWriteMany"] # 访问模式:ReadWriteMany,表示可被多个节点以读写方式挂载
nfs: # 使用 NFS 类型的存储
path: /data/volume_test/v2 # 对应 NFS 服务器上的路径
server: 192.168.138.135 # NFS 服务器的地址
启动pv
bash
# 启动pv
kubectl apply -f pv.yaml
#查看pv资源,STATUS是Available,表示pv是可用的
kubectl get pv

5.4.2 创建pvc,和符合条件的pv绑定
bash
#k8s-master上创建pvc.yaml
vi pvc.yaml
#填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 PersistentVolumeClaim 等核心资源
kind: PersistentVolumeClaim # 资源类型为 PersistentVolumeClaim(PVC),表示对存储资源的申请
metadata: # PVC 的元数据信息
name: my-pvc # PVC 的名称
spec: # PVC 的期望状态
accessModes: ["ReadWriteMany"] # 申请访问模式:ReadWriteMany,表示可被多个节点以读写方式挂载
resources: # 资源请求配置
requests: # 请求的具体资源
storage: 2Gi # 请求的存储容量为 2Gi
启动pvc
bash
# 启动pvc
kubectl apply -f pvc.yaml
#查看pv和pvc
kubectl get pv
#STATUS是Bound,表示这个pv已经被my-pvc绑定了

pvc的名字-绑定到pv-绑定的是v2这个pv-pvc可使用的容量是2G
那为什么是绑定到v2这个pv呢?不是绑定到v1这个pv呢?
匹配过程:
-
PV v1
-
访问模式是
ReadWriteOnce,PVC 要的是ReadWriteMany→ 不满足。 -
容量
1Gi< 请求的2Gi→ 不满足。 -
所以 v1 被排除。
-
-
PV v2
-
访问模式是
ReadWriteMany,PVC 要的也是ReadWriteMany→ 满足。 -
容量
2Gi>= 请求的2Gi→ 满足。 -
PV 处于
Available→ 满足。
-
因此,PV 控制器会把 my-pvc 绑定到 v2。
注意点
-
PV 和 PVC 是一对一绑定
一个 PV 只能被一个 PVC 绑定。即使访问模式是
ReadWriteMany,也只是表示这个 PVC 可以被多个 Pod 同时挂载,不表示一个 PV 可以绑定多个 PVC。 -
如果没有匹配的 PV
PVC 会一直处于
Pending状态,直到有满足条件的 PV 出现,或者集群有默认 StorageClass 时动态创建 PV。 -
默认 StorageClass 可能影响匹配
如果集群里存在默认 StorageClass,而你创建 PVC 时没有写
storageClassName,准入控制器可能会自动给 PVC 加上默认 StorageClass。这样它就只会匹配同 StorageClass 的 PV,无法绑定到storageClassName为空的 PV。如果需要明确匹配空 StorageClass,可以写:storageClassName: ""
-
手动指定绑定
如果你想让 PVC 直接绑定某个 PV,可以在 PVC 中写:但通常不需要,让控制器自动匹配即可。
bash
spec:
volumeName: v2
5.4.3 创建pod,挂载pvc
bash
# k8s-master创建pod_pvc.yaml
vi pod_pvc.yaml
# 填入以下内容
apiVersion: v1 # 指定 Kubernetes API 版本,v1 用于 Pod 等核心资源
kind: Pod # 资源类型为 Pod,直接创建一个 Pod
metadata: # Pod 的元数据信息
name: pod-pvc # Pod 的名称
spec: # Pod 的期望状态
containers: # 容器列表
- name: nginx # 容器名称
image: nginx # 容器使用的镜像为 nginx
volumeMounts: # 容器内的卷挂载配置
- name: nginx-html # 挂载的卷名称,需与下方 volumes 中的 name 一致
mountPath: /usr/share/nginx/html # 将卷挂载到 nginx 默认网站目录
volumes: # Pod 的卷列表
- name: nginx-html # 卷的名称,供上方容器的 volumeMounts 引用
persistentVolumeClaim: # 使用 PVC 类型的卷,引用已创建的 PersistentVolumeClaim
claimName: my-pvc # 要绑定的 PVC 名称
启动pod
bash
# 启动pod
kubectl apply -f pod_pvc.yaml
# 查看
kubectl get pvc my-pvc
kubectl get pod pod-pvc
kubectl describe pod pod-pvc

注意:使用pvc和pv的注意事项
1、我们每次创建pvc的时候,需要事先有划分好的pv,这样可能不方便,那么可以在创建pvc的时
候直接动态创建一个pv这个存储类,pv事先是不存在的
2、pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv
里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。
5.5 k8s存储类:storageclass
PV和PVC模式都是需要先创建好PV,然后定义好PVC和pv进行一对一的Bond,但是如
果PVC请求成千上万,那么就需要创建成千上万的PV,对于运维人员来说维护成本很高,
Kubernetes提供一种自动创建PV的机制,叫StorageClass,它的作用就是创建PV的模板。k8s集群管理员通过创建storageclass可以动态生成一个存储卷pv供k8s pvc使用。
每个StorageClass都包含字段provisioner,parameters和reclaimPolicy。
具体来说,StorageClass会定义以下两部分:
1、PV的属性,比如存储的大小、类型等;
2、创建这种PV需要使用到的存储插件,比如Ceph、NFS等
有了这两部分信息,Kubernetes就能够根据用户提交的PVC,找到对应的StorageClass,然后
Kubernetes就会调用StorageClass声明的存储插件,创建出需要的PV。
provisioner:供应商,storageclass需要有一个供应者,用来确定我们使用什么样的存储来创建
pv,常见的provisioner如下
5.5.1 主流云厂商
| 类别 | 存储方案 | 推荐 Provisioner(CSI) | 旧版 In-Tree Provisioner | 说明 |
|---|---|---|---|---|
| AWS | EBS 块存储 | ebs.csi.aws.com |
kubernetes.io/aws-ebs |
块存储,通常 ReadWriteOnce |
| AWS | EFS 文件存储 | efs.csi.aws.com |
无 | 文件存储,支持 ReadWriteMany |
| GCP | Persistent Disk | pd.csi.storage.gke.io |
kubernetes.io/gce-pd |
GKE 块存储 |
| GCP | Filestore | filestore.csi.storage.gke.io |
无 | 文件存储,支持 ReadWriteMany |
| Azure | Managed Disk | disk.csi.azure.com |
kubernetes.io/azure-disk |
AKS 块存储 |
| Azure | Azure File | file.csi.azure.com |
kubernetes.io/azure-file |
SMB/NFS,支持 ReadWriteMany |
| OpenStack | Cinder | cinder.csi.openstack.org |
kubernetes.io/cinder |
OpenStack 块存储 |
| VMware | vSphere Volume | csi.vsphere.vmware.com |
kubernetes.io/vsphere-volume |
vSphere 存储 |
5.5.2 自建、开源与分布式存储
| 类别 | 存储方案 | 推荐 Provisioner | 旧版/其他 | 说明 |
|---|---|---|---|---|
| 网络存储 | NFS | nfs.csi.k8s.io |
无内置动态 provisioner | 需部署 csi-driver-nfs,指定 server/path |
| 本地存储 | Local Path | rancher.io/local-path |
无 | K3s/Rancher 常用,数据绑定节点 |
| 分布式块存储 | Longhorn | driver.longhorn.io |
无 | Rancher 生态,支持 RWX |
| 容器化存储 | OpenEBS | openebs.io/local、openebs.io/cstor 等 |
无 | 具体名称取决于 OpenEBS 引擎 |
| 分布式存储 | Ceph RBD | rbd.csi.ceph.com |
kubernetes.io/rbd |
Ceph 块存储,in-tree 已弃用 |
| 分布式存储 | CephFS | cephfs.csi.ceph.com |
kubernetes.io/cephfs |
Ceph 文件存储,支持 RWX |
| 分布式存储 | GlusterFS | 社区 CSI 驱动(名称以文档为准) | kubernetes.io/glusterfs |
in-tree 已移除,建议迁移 |
| 企业存储 | Portworx | pxd.portworx.com |
kubernetes.io/portworx-volume |
企业级容器存储 |
| 企业存储 | NetApp Trident | csi.trident.netapp.io |
无 | NetApp 存储集成 |
| 企业存储 | Pure Storage | pure-csi |
无 | Pure Storage CSI |
| 企业存储 | Dell EMC | 如 csi.vxflexos.dellemc.com |
无 | 具体取决于产品型号 |
5.5.3 特殊与手动场景
| 场景 | Provisioner | 说明 |
|---|---|---|
| 手动静态 PV | kubernetes.io/no-provisioner |
不动态创建 PV,需管理员手动创建 PV |
| 旧版 in-tree | 各种 kubernetes.io/* |
已逐步弃用/移除,仅旧集群可能见到 |
建议
-
新建集群 :优先使用 CSI provisioner,如
ebs.csi.aws.com、disk.csi.azure.com、rbd.csi.ceph.com。 -
自建 NFS :推荐
nfs.csi.k8s.io,配合csi-driver-nfs实现动态供给。 -
本地测试 :可用
rancher.io/local-path或kubernetes.io/no-provisioner手动创建 PV。 -
填写位置 :StorageClass 的
provisioner字段。
5.5.4 安装nfsprovisioner,用于配合存储类动态生成pv
以NFS为例,要想使用NFS,我们需要一个nfs-client的自动装载程序,称之为provisioner,这
个程序会使用我们已经配置好的NFS服务器自动创建持久卷,也就是自动帮我们创建PV。
5.5.4.1 首先将nfsprovisioner镜像pull下来
bash
#k8s-master执行命令
crictl pull registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2
#查看镜像
crtctl images

5.5.4.2 创建运行nfs-provisioner需要的sa账号
什么是sa?
sa的全称是serviceaccount。
serviceaccount是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的。
指定了serviceaccount之后,我们把pod创建出来了,我们在使用这个pod时,这个pod就有了
我们指定的账户的权限了。
bash
# 创建serviceaccount.yaml
vi serviceaccount.yaml
# 填入以下内容
apiVersion: v1 # Kubernetes API 版本,v1 表示核心 API 组
kind: ServiceAccount # 资源类型:ServiceAccount 服务账号
metadata: # 资源的元数据信息
name: nfs-provisioner # ServiceAccount 的名称
创建serviceaccount
bash
# 启动
kubectl apply -f serviceaccount.yaml
#查看sa
kubectl get sa


5.5.4.3 创建sa_rbac.yaml,对sa进行rbac授权
bash
# 创建sa_rbac.yaml
vi sa_rbac.yaml
# 填入以下内容
apiVersion: rbac.authorization.k8s.io/v1 # RBAC API 版本
kind: ClusterRoleBinding # 资源类型:集群角色绑定
metadata: # 元数据信息
name: nfs-provisioner-clusterrolebinding # ClusterRoleBinding 名称
roleRef: # 要绑定的角色引用
apiGroup: rbac.authorization.k8s.io # 角色所属 API 组
kind: ClusterRole # 角色类型:ClusterRole
name: cluster-admin # 引用的角色名称:cluster-admin
subjects: # 绑定主体列表
- kind: ServiceAccount # 主体类型:ServiceAccount
name: nfs-provisioner # ServiceAccount 名称
namespace: default # ServiceAccount 所在命名空间
最终效果:
让
default命名空间里的nfs-provisioner这个 ServiceAccount 拥有整个 Kubernetes 集群的管理员权限。
也就是说,任何使用这个 ServiceAccount 运行的 Pod,都可以几乎不受限制地操作整个集群,包括所有命名空间、所有资源。
这通常出现在 NFS provisioner 相关教程里,因为 NFS provisioner 可能需要创建、删除 PersistentVolume 等集群级资源。但直接绑定 cluster-admin 权限非常大,生产环境不建议这样做,应该按最小权限原则创建一个专用 ClusterRole,只授予它需要的权限。
创建rbac,并且查看rbac
bash
kubectl apply -f sa_rbac.yaml
kubectl get clusterrolebinding nfs-provisioner-clusterrolebinding -o yaml
kubectl describe clusterrolebinding nfs-provisioner-clusterrolebinding

把/data/nfs_pro变成nfs共享的目录
bash
mkdir /data/nfs_pro -pv
vi /etc/exports
#在末尾添加
/data/nfs_pro *(rw,no_root_squash)
#生效
exportfs -arv

5.5.4.4 创建 nfs-deployment.yaml
bash
# 创建nfs-deployment.yaml
vi nfs-deployment.yaml
#填入以下内容
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 资源类型:Deployment 部署
metadata: # 资源的元数据信息
name: nfs-provisioner # Deployment 的名称
spec: # Deployment 的期望状态
selector: # 选择器,用于匹配要管理的 Pod
matchLabels: # 标签匹配规则
app: nfs-provisioner # 匹配标签 app=nfs-provisioner
replicas: 1 # Pod 副本数量
strategy: # Pod 更新策略
type: Recreate # 策略类型为 Recreate,先删除旧 Pod 再创建新 Pod
template: # Pod 模板定义
metadata: # Pod 模板的元数据
labels: # Pod 标签
app: nfs-provisioner # 标签 app=nfs-provisioner
spec: # Pod 模板的规格
serviceAccount: nfs-provisioner # 指定 Pod 使用的 ServiceAccount
containers: # 容器列表
- name: nfs-provisioner # 容器名称
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2 # 容器镜像地址及版本
volumeMounts: # 容器内挂载卷的配置
- name: nfs-client-root # 挂载的卷名称,对应下方 volumes
mountPath: /persistentvolumes # 容器内挂载路径
env: # 容器环境变量列表
- name: PROVISIONER_NAME # 环境变量名:provisioner 名称
value: example.com/nfs # 环境变量值
- name: NFS_SERVER # 环境变量名:NFS 服务器地址
value: 192.168.138.135 # 环境变量值
- name: NFS_PATH # 环境变量名:NFS 共享路径
value: /data/nfs_pro # 环境变量值
volumes: # Pod 卷列表
- name: nfs-client-root # 卷名称
nfs: # 卷类型:NFS
server: 192.168.138.135 # NFS 服务器地址
path: /data/nfs_pro # NFS 共享路径
启动nsf-demployment.yaml
bash
#启动
kubectl apply -f nfs-deployment.yaml
#查看
kubectl get pods -o wide | grep nfs-provisioner

5.5.4.5 创建storageclass,动态供给pv
bash
vi nfs-storageclass.yaml
#填入以下内容
apiVersion: storage.k8s.io/v1 # StorageClass 使用的 API 版本
kind: StorageClass # 资源类型:StorageClass 存储类
metadata: # 资源的元数据信息
name: nfs # StorageClass 的名称
provisioner: example.com/nfs # 动态卷供应者名称,需与 Deployment 中 PROVISIONER_NAME 的值一致
启动,并查看
bash
kubectl apply -f nfs-storageclass.yaml
kubectl get storageclass

5.5.4.5 创建pvc,通过storageclass动态生成pv
bash
vi claim.yaml
# 填入以下内容
apiVersion: v1 # PVC 使用的 API 版本,v1 为核心 API 组
kind: PersistentVolumeClaim # 资源类型:PersistentVolumeClaim 持久卷声明
metadata: # 资源的元数据信息
name: test-claim1 # PVC 的名称
spec: # PVC 的期望状态
accessModes: # 访问模式列表
- ReadWriteMany # 允许被多个节点以读写方式挂载
resources: # 资源请求配置
requests: # 请求的资源量
storage: 1Gi # 请求 1Gi 存储容量
storageClassName: nfs # 指定使用的 StorageClass 名称,对应前面创建的 nfs
启动并查看状态
bash
kubectl apply -f claim.yaml
kubectl get pvc
kubectl dsecirbe pvc test-claim1


5.5.4.6 创建pod,挂载storageclass动态生成的pvc:test-claim1
bash
vi storageclass-pod.yaml
# 填入以下内容
apiVersion: v1 # Pod 使用的 API 版本,v1 为核心 API 组
kind: Pod # 资源类型:Pod
metadata: # 资源的元数据信息
name: read-pod # Pod 的名称
spec: # Pod 的期望状态
containers: # 容器列表
- name: read-pod # 容器名称
image: nginx # 容器使用的镜像
imagePullPolicy: IfNotPresent # 镜像拉取策略:本地不存在时才拉取
volumeMounts: # 容器内挂载卷的配置
- name: nfs-pvc # 挂载的卷名称,对应下方 volumes 中的名称
mountPath: /usr/share/nginx/html # 容器内挂载路径,即 nginx 默认网站根目录
restartPolicy: "Never" # Pod 重启策略:从不重启
volumes: # Pod 卷列表
- name: nfs-pvc # 卷名称
persistentVolumeClaim: # 卷类型:使用 PVC(持久卷声明)
claimName: test-claim1 # 绑定的 PVC 名称,对应之前创建的 test-claim1
启动并查看
bash
kubectl apply -f storageclass-pod.yaml
kubectl get pods | grep read
kubectl describe pods read-pod

进入容器验证挂载
bash
kubectl exec -it read-pod -- /bin/sh
#进入后执行:
df -h | grep nfs
mount | grep nfs
ls -l /usr/share/nginx/html
# 测试写入与读取,在容器内写入一个测试文件:
echo "hello nfs" > /usr/share/nginx/html/index.html
cat /usr/share/nginx/html/index.html

bash
# 验证数据持久化到 NFS
ls -l /data/nfs_pro

验证pod输出

删除 Pod 再重建,验证文件是否还在:
bash
kubectl delete pod read-pod
kubectl apply -f storageclass-pod.yaml
kubectl exec -it read-pod -- cat /usr/share/nginx/html/index.html

5.5.4.7 总结
步骤总结:
1、供应商:创建一个nfsprovisioner,然后给供应商赋权(rbac)
2、创建storageclass,storageclass指定刚才创建的供应商
3、创建pvc,这个pvc指定storageclass