14、k8s持久化存储

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 选型优先级

  1. 持久化存储 :首选 persistentVolumeClaim + CSI 驱动(或云厂商 CSI 驱动),这是生产环境的推荐做法。

  2. 配置文件/敏感数据注入 :使用 configMap、secret、projected、downwardAPI。

  3. 临时缓存/多容器共享 :使用 emptyDir(需要内存速度时设 medium: Memory)。

  4. 本地高性能持久存储 :使用 local(通过 PVC 引用),适用于对 IO 延迟要求极高的场景,但需接受数据与节点绑定的风险。

  5. 避免 :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。

注意点

  1. PV 和 PVC 是一对一绑定

    一个 PV 只能被一个 PVC 绑定。即使访问模式是 ReadWriteMany,也只是表示这个 PVC 可以被多个 Pod 同时挂载,不表示一个 PV 可以绑定多个 PVC。

  2. 如果没有匹配的 PV

    PVC 会一直处于 Pending 状态,直到有满足条件的 PV 出现,或者集群有默认 StorageClass 时动态创建 PV。

  3. 默认 StorageClass 可能影响匹配

    如果集群里存在默认 StorageClass,而你创建 PVC 时没有写 storageClassName,准入控制器可能会自动给 PVC 加上默认 StorageClass。这样它就只会匹配同 StorageClass 的 PV,无法绑定到 storageClassName 为空的 PV。

    如果需要明确匹配空 StorageClass,可以写:storageClassName: ""

  4. 手动指定绑定

如果你想让 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

相关推荐
上火的金鱼妹2 天前
higress网关访问+ 模型访问的安全策略管控
k8s·higress·ai网关
weixin_444579303 天前
Containerd
容器·k8s
Dragon_qu·x4 天前
MongoDB 高可用集群部署
运维·数据库·mongodb·k8s·helm
此冬歌咏11 天前
K8s 节点故障实战:优雅驱逐 31 秒,硬故障 331 秒,以及那个永远 Pending 的 Pod
运维·k8s
玉&心11 天前
通过Arthas在线诊断K8S中的内存及JVM等使用情况
docker·k8s·arthas
流烟默11 天前
K8s StatefulSet 详解:有状态应用的“身份证”与“固定住址”
k8s·statefulset
cg.family11 天前
K8S集群手动巡检
k8s
九皇叔叔12 天前
Kubernetes 资源管理方式详解:命令式与声明式管理
docker·容器·k8s
九皇叔叔13 天前
K8S 资源菜单
docker·容器·kubernetes·k8s