Kubernetes持久化存储:从Volume到PV/PVC与StorageClass动态存储

前言

在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 验证与结果
  1. 部署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>
  1. 访问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
...
  1. 小结:emptyDir仅适用于临时数据场景,Pod删除后数据会彻底丢失,无法用于持久化存储需求。

2.2 hostPath:节点级持久化存储

hostPath将节点(宿主机)的本地目录直接挂载到容器,实现节点级别的数据持久化,Pod删除后节点目录中的数据仍会保留。

2.2.1 核心特点
  • 节点绑定:数据存储在宿主机目录,与Pod生命周期解耦,Pod删除后数据不丢失;
  • 跨Pod共享:同一节点上的多个Pod可通过挂载相同hostPath目录实现数据共享;
  • 局限性:
    • 数据与特定节点绑定,若Pod被调度到其他节点,无法访问原节点的hostPath数据;
    • 节点故障或离线时,数据将无法访问,存在单点风险。
2.2.2 实战配置(YAML示例)
  1. 提前在所有节点创建统一目录(避免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
  1. 编写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 验证与结果
  1. 部署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>
  1. 访问Nginx验证数据:
bash 复制代码
curl 10.244.1.60

预期输出(根据调度节点显示对应标识):

复制代码
node01.benet.com
  1. 验证持久化特性:
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:验证与结果
  1. 部署Pod并创建测试数据:
bash 复制代码
kubectl apply -f pod-nfs-vol.yaml

# 在NFS服务端创建测试文件
echo "<h1>nfs stor01</h1>" > /data/volumes/index.html
  1. 访问Pod验证数据:
bash 复制代码
# 获取Pod的IP和调度节点
kubectl get pods pod-vol-nfs -o wide
# 访问Pod,验证数据读取
curl 10.244.2.68

预期输出:

复制代码
<h1>nfs stor01</h1>
  1. 验证跨节点共享能力:
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 生命周期流程
  1. Provisioning(配置):运维手动创建PV(静态配置),或通过StorageClass自动创建PV(动态配置);
  2. Binding(绑定) :开发者创建PVC,K8s根据PVC的需求(容量、访问模式)匹配可用PV,绑定后PV状态变为Bound
  3. Using(使用) :Pod通过引用PVC挂载存储,K8s通过StorageProtection机制阻止删除正在使用的PVC;
  4. Releasing(释放) :Pod删除且PVC被删除后,PV与PVC解绑,状态变为Released(数据仍保留);
  5. 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从创建到销毁的具体流程
  1. 运维创建PV后,其状态变为Available,等待PVC绑定;
  2. 开发者创建PVC,K8s匹配到符合条件的PV后完成绑定,PV状态变为Bound
  3. Pod通过PVC挂载并使用存储资源,期间PV持续处于Bound状态;
  4. 应用停止使用后,开发者删除PVC,PV状态变为Released
  5. 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可申请RWXRWO),不同存储类型支持的访问模式不同:

  • RWO(ReadWriteOnce):可读可写,仅允许单个节点的多个Pod挂载(支持所有存储类型);
  • ROX(ReadOnlyMany):只读,允许多个节点的Pod同时挂载(支持NFS、Ceph等);
  • RWX(ReadWriteMany):可读可写,允许多个节点的Pod同时挂载(仅支持NFS、Ceph等共享存储)。
3.3.2 容量(capacity)

定义PV的存储容量,格式为存储大小+单位(如2Gi10Gi),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 部署与验证
  1. 部署PVC与Pod:
bash 复制代码
kubectl apply -f pod-vol-pvc.yaml
  1. 查看绑定状态:
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
  1. 验证数据持久化:
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
  1. 验证释放流程:
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

部署并验证:

  1. 部署PVC与Pod:
bash 复制代码
kubectl apply -f test-pvc-pod.yaml
  1. 验证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
  1. 验证NFS服务端目录:
bash 复制代码
# 查看NFS服务端是否自动创建挂载目录(目录名格式:namespace-pvcName-pvName)
ls /opt/k8s/

预期输出:

复制代码
default-test-nfs-pvc-pvc-11670f39-782d-41b8-a842-eabe1859a456
  1. 验证数据写入:
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
  1. 验证自动回收:
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 大规模集群、动态存储需求

选型建议

  1. 临时数据场景:选择emptyDir,如应用临时缓存、Pod内多容器数据共享;
  2. 单节点持久化场景:选择hostPath,如节点本地日志存储、无需跨节点访问的应用;
  3. 跨节点共享场景:选择NFS(基础需求)或PV+PVC(生产环境),如分布式应用、多节点共享配置;
  4. 大规模集群场景:选择StorageClass+NFS,如数百个应用的动态存储需求、CI/CD流水线动态分配存储;
  5. 云原生场景:优先选择云厂商提供的StorageClass(如AWS EBS、Azure Disk),无需维护底层存储服务。

总结

Kubernetes的持久化存储体系围绕"解耦、灵活、高效"的设计原则,从基础Volume抽象到PV/PVC的资源分离,再到StorageClass的动态管理,形成了覆盖不同场景的完整解决方案。emptyDir和hostPath适用于简单场景,NFS提供了集群级共享能力,PV/PVC实现了开发与运维的职责解耦,而StorageClass则通过自动化手段大幅提升了大规模集群的存储管理效率。

相关推荐
螺旋小蜗2 小时前
docker-compose文件属性(3)顶部元素networks
运维·docker·容器
ICT董老师3 小时前
Kubernetes从私有镜像仓库拉取容器镜像时的身份验证
ubuntu·docker·云原生·容器·kubernetes
goodlook01234 小时前
open-java21镜像构建
java·运维·docker·容器
2501_901164414 小时前
“一次性沙箱”把开发内耗降到了0。
kubernetes
MoFe14 小时前
【Docker】windows系统wsl如何操作DOCKER
云原生·eureka
星环处相逢5 小时前
Kubernetes 核心指南:Pod 控制器与配置资源管理全解析
云原生·容器·kubernetes
蜗牛^^O^5 小时前
传统网关与云原生网关
java·服务器·云原生
张小凡vip5 小时前
数据挖掘(四) -----JupyterHub on k8s安装
人工智能·数据挖掘·kubernetes
别多香了5 小时前
k8s管理
docker·容器·kubernetes