K8S(九)—— Kubernetes持久化存储深度解析:从Volume到PV/PVC与动态存储

文章目录

  • 前言
  • [一、容器存储的核心挑战与K8s Volume解决方案](#一、容器存储的核心挑战与K8s Volume解决方案)
    • [1.1 容器存储的短暂性问题](#1.1 容器存储的短暂性问题)
    • [1.2 K8s Volume的核心价值](#1.2 K8s Volume的核心价值)
  • 二、K8s基础存储卷实践
    • [2.1 emptyDir:Pod内临时共享存储](#2.1 emptyDir:Pod内临时共享存储)
      • [2.1.1 核心特点](#2.1.1 核心特点)
      • [2.1.2 实战配置(YAML示例)](#2.1.2 实战配置(YAML示例))
      • [2.1.3 验证与结果](#2.1.3 验证与结果)
    • [2.2 hostPath:节点级持久化存储](#2.2 hostPath:节点级持久化存储)
      • [2.2.1 核心特点](#2.2.1 核心特点)
      • [2.2.2 实战配置(YAML示例)](#2.2.2 实战配置(YAML示例))
      • [2.2.3 验证与结果](#2.2.3 验证与结果)
    • [2.3 NFS:跨节点共享存储](#2.3 NFS:跨节点共享存储)
  • 三、PV与PVC:K8s持久化存储的核心机制
    • [3.1 PV与PVC的核心概念](#3.1 PV与PVC的核心概念)
    • [3.2 PV与PVC的生命周期](#3.2 PV与PVC的生命周期)
      • [3.2.1 生命周期流程](#3.2.1 生命周期流程)
      • [3.2.2 PV的核心状态](#3.2.2 PV的核心状态)
      • [3.2.3 一个PV从创建到销毁的具体流程如下](#3.2.3 一个PV从创建到销毁的具体流程如下)
    • [3.3 PV的核心配置项](#3.3 PV的核心配置项)
      • [3.3.1 访问模式(accessModes)](#3.3.1 访问模式(accessModes))
      • [3.3.2 容量(capacity)](#3.3.2 容量(capacity))
      • [3.3.3 回收策略(persistentVolumeReclaimPolicy)](#3.3.3 回收策略(persistentVolumeReclaimPolicy))
    • [3.4 PV 的示例](#3.4 PV 的示例)
  • [四、NFS + PV + PVC 实战演练](#四、NFS + PV + PVC 实战演练)
    • [4.1 前置准备(NFS服务端配置)](#4.1 前置准备(NFS服务端配置))
    • [4.2 手动创建PV(静态配置)](#4.2 手动创建PV(静态配置))
    • [4.3 创建PVC并绑定PV](#4.3 创建PVC并绑定PV)
      • [4.3.1 编写PVC与Pod配置](#4.3.1 编写PVC与Pod配置)
      • [4.3.2 部署与验证](#4.3.2 部署与验证)
  • 五、StorageClass:实现NFS动态PV创建
    • [5.1 StorageClass的核心组件](#5.1 StorageClass的核心组件)
    • [5.2 部署NFS动态存储(全流程)](#5.2 部署NFS动态存储(全流程))
      • [5.2.1 前置修复(K8s 1.20版本)](#5.2.1 前置修复(K8s 1.20版本))
      • [5.2.2 在stor01节点上安装nfs,并配置nfs服务](#5.2.2 在stor01节点上安装nfs,并配置nfs服务)
      • [5.2.3 配置RBAC权限](#5.2.3 配置RBAC权限)
      • [5.2.4 部署 NFS Provisioner](#5.2.4 部署 NFS Provisioner)
      • [5.2.5 创建StorageClass](#5.2.5 创建StorageClass)
      • [5.2.6 测试动态PV创建](#5.2.6 测试动态PV创建)
  • 六、存储方案对比与选型建议
  • 总结

前言

在K8s集群中,容器的临时性本质带来了两个关键问题:一是容器崩溃重启后数据丢失,二是同一Pod内多容器无法直接共享文件。

为解决这些问题,K8s设计了Volume抽象,并在此基础上衍生出PV(Persistent Volume)与PVC(Persistent Volume Claim)机制,进一步通过StorageClass实现动态存储管理。本文将从基础存储卷实践出发,逐步深入PV/PVC核心原理,并结合NFS存储进行实战演练,帮助大家掌握K8s持久化存储的选型与配置。

一、容器存储的核心挑战与K8s Volume解决方案

在深入技术细节前,我们首先明确容器存储的核心痛点,以及K8s Volume抽象如何初步解决这些问题。

1.1 容器存储的短暂性问题

容器的文件系统基于镜像层和可写层,其本质是临时性的,主要体现在两个方面:

  • 数据易失性:容器因故障重启后,可写层的数据会完全丢失(例如Nginx容器内的日志文件、应用配置等);
  • 共享困难:同一Pod内的多个容器(如应用容器+日志收集容器)无法直接访问彼此的文件系统,需额外机制实现数据共享。

1.2 K8s Volume的核心价值

K8s通过Volume抽象 打破了容器的存储边界,其核心设计是通过Pause容器(Pod的基础容器)实现:

  • 所有容器挂载同一个Volume,实现Pod内多容器的数据共享;
  • Volume的生命周期独立于单个容器(但默认与Pod生命周期绑定),解决容器重启数据丢失问题;
  • 支持多种存储类型(本地存储、网络存储等),适配不同场景需求。

二、K8s基础存储卷实践

K8s提供了多种基础Volume类型,适用于不同场景。本节将重点讲解emptyDirhostPathNFS三种常用类型的特点与实战。

2.1 emptyDir:Pod内临时共享存储

emptyDir是最基础的Volume类型,其生命周期与Pod完全绑定,适合临时缓存或Pod内多容器数据共享场景。

2.1.1 核心特点

  • 自动创建:Pod调度到节点时,K8s自动在节点本地创建emptyDir目录;
  • 随Pod销毁:Pod删除时,emptyDir内的数据也会被彻底清理;
  • 适合场景:仅适合临时缓存或容器间数据共享。
  • 存储介质 :默认使用节点的本地磁盘,也可配置为内存(medium: Memory,适合高性能临时缓存,但需注意内存溢出风险)。

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:
	#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷
    - name: html
	  #挂载至容器中哪个目录
      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: {}

2.1.3 验证与结果

1、部署Pod并查看状态:

bash 复制代码
kubectl apply -f pod-emptydir.yaml
# 确认Pod的2个容器均正常运行(READY为2/2)
kubectl get pods pod-emptydir -o wide

2、访问Nginx验证数据共享:

bash 复制代码
# 在上面定义了2个容器,其中一个容器是输入日期到index.html中,然后验证访问nginx的html是否可以获取日期。以验证两个容器之间挂载的emptyDir实现共享。
# 获取Pod的IP(从上方命令的IP列获取)
kubectl get pod -o wide
# 访问Nginx,可见时间持续追加(证明两容器共享数据)
curl POD_IP

3、小结:emptyDir仅适合临时数据场景,若Pod删除,数据会丢失,无法用于持久化存储。

2.2 hostPath:节点级持久化存储

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

2.2.1 核心特点

  • 绑定节点目录:直接使用宿主机的目录,数据不随Pod删除而销毁;
  • 跨Pod持久化:同一节点上的多个Pod可通过挂载相同hostPath共享数据;
  • 局限性
    • 数据绑定到特定节点,若Pod调度到其他节点,无法访问原节点数据;
    • 节点故障会导致数据丢失。

2.2.2 实战配置(YAML示例)

1、提前在所有节点创建目录(避免Pod调度到无目录的节点报错):

bash 复制代码
1)在 node01 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node01.simon.com' > /data/pod/volume1/index.html

2)在 node02 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node02.simon.com' > /data/pod/volume1/index.html

2、 编写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:
	#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷
    - name: html
	  #挂载至容器中哪个目录
      mountPath: /usr/share/nginx/html
	  #读写挂载方式,默认为读写模式false
	  readOnly: false
  #volumes字段定义了paues容器关联的宿主机或分布式文件系统存储卷
  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

2、访问Nginx验证数据:

bash 复制代码
curl POD_IP  # 输出对应节点的标识(如node02.benet.com)

3、 验证持久化:删除Pod后重新部署,若调度到同一节点,数据仍保留;若调度到其他节点,数据为新节点的标识。

bash 复制代码
kubectl delete -f pod-hostpath.yaml && kubectl apply -f pod-hostpath.yaml 

kubectl get pods -o wide

2.3 NFS:跨节点共享存储

NFS(Network File System)是一种网络存储协议,可实现多节点共享数据,解决hostPath跨节点的局限性,是K8s集群中常用的共享存储方案。

2.3.1 核心特点

  • 跨节点共享:数据存储在NFS服务端,所有K8s节点均可访问;
  • 持久化可靠:数据独立于K8s集群,Pod删除、节点故障均不影响数据;
  • 支持RWX:支持多Pod同时读写(ReadWriteMany),适合分布式应用共享数据。

2.3.2 实战配置(分服务端与客户端)

步骤1:部署NFS服务端(独立节点,如stor01)

1、安装NFS服务:

bash 复制代码
# 安装NFS服务端组件(rpcbind为依赖)
yum install -y nfs-utils rpcbind

2、配置NFS共享目录:

bash 复制代码
# 创建共享目录
mkdir -p /data/volumes
chmod 777 /data/volumes  # 避免容器权限问题

# 编辑NFS配置文件(允许192.168.10.0/24网段访问)
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
  • rw:读写权限;
  • no_root_squash:容器以root用户访问时,映射为NFS服务端的root用户(避免权限不足);

3、验证配置

bash 复制代码
netstat -anpu | grep rpc    
# 验证共享(输出共享目录信息即为成功)
showmount -e 
步骤2:K8s节点安装NFS客户端(所有节点)
bash 复制代码
# 所有K8s节点(master、node01、node02)执行
yum install -y nfs-utils
# 将store01加入/etc/hosts
echo "192.168.10.17 stor01" >> /etc/hosts
# 验证NFS服务端连通性(能看到共享目录即为成功)
showmount -e stor01  # stor01为NFS服务端 hostname/IP
步骤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服务端地址
步骤4:验证与结果

1、部署Pod并访问:

bash 复制代码
kubectl apply -f pod-nfs-vol.yaml
# 在NFS服务端创建测试文件
echo "<h1> nfs stor01</h1>" >/data/volumes/index.html
# 访问Pod验证数据
kubectl get po -owide
NAME           READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
pod-hostpath   1/1     Running   0          29m     10.244.1.61   node01   <none>           <none>
pod-vol-nfs    1/1     Running   0          2m19s   10.244.2.68   node02   <none>           <none>
curl POD_IP  # 输出"nfs stor01"

2、验证跨节点能力:删除Pod后重新部署(可能调度到其他节点),再次访问仍能获取NFS中的数据,证明跨节点共享生效。

bash 复制代码
# 修改 pod-nfs-vol.yaml,通过nodeName:强制绑定节点
kind: Pod
apiVersion: v1
metadata:
  name: pod-vol-nfs
  namespace: default
spec:
  nodeName: node01  # 强制调度到node01
  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服务端地址

#删除nfs相关pod,再重新创建,可以得到数据的持久化存储
kubectl delete -f pod-nfs-vol.yaml && kubectl apply -f pod-nfs-vol.yaml


三、PV与PVC:K8s持久化存储的核心机制

基础Volume(如NFS)需要在Pod中直接配置存储细节(如NFS服务端IP、路径),导致开发与运维职责耦合(开发者需关注存储配置)。PV与PVC机制通过"资源抽象"解耦职责,是生产环境中推荐的持久化方案。

3.1 PV与PVC的核心概念

PV与PVC的本质是"存储资源的供给与需求",分工明确:

概念 名称 核心作用 维护者
PV(Persistent Volume) 持久化存储卷 定义底层存储资源(如NFS路径、容量、访问模式),是"存储资源的模板" 运维工程师
PVC(Persistent Volume Claim) 持久化存储的请求 描述应用对存储的需求(如容量、访问模式),是"存储资源的申请单" 应用开发者
StorageClass 存储类 自动创建 PV 模板 运维工程师

PVC 的使用逻辑:管理员在 Pod 中定义PV,定义的时候直接指定大小,PVC 必须与对应的 PV 建立关系,PVC 会根据配置的定义去 PV 申请,而 PV 是由存储空间创建出来的。PV 和 PVC 是 Kubernetes 抽象出来的一种存储资源

3.2 PV与PVC的生命周期

PV与PVC的交互遵循"配置→绑定→使用→释放→回收"5个阶段,对应PV的4种核心状态:

3.2.1 生命周期流程

PV和PVC之间的相互作用遵循这个生命周期Provisioning(配置)--> Binding(绑定)--> Using(使用)--> Releasing(释放)--> Recycling(回收)

  1. Provisioning(配置):运维手动创建PV(静态配置),或通过StorageClass自动创建PV(动态配置);
  2. Binding(绑定) :开发者创建PVC,K8s根据PVC的需求(容量、访问模式)匹配可用PV,绑定后PV状态变为Bound
  3. Using(使用) :Pod通过PVC挂载存储,K8s通过StorageProtection机制阻止删除正在使用的PVC;
  4. Releasing(释放) :Pod 释放 Volume 并删除 PVC,PV状态变为Released(数据仍保留);
  5. Reclaiming(回收) :K8s根据PV的回收策略处理Released状态的PV(如保留数据、删除数据、清空数据)。

3.2.2 PV的核心状态

状态 触发条件
Available PV已创建,未被任何PVC绑定
Bound PV已成功绑定到PVC
Released PVC被删除,PV与PVC解绑,但数据未被回收
Failed PV自动回收失败(如NFS服务端不可用)

3.2.3 一个PV从创建到销毁的具体流程如下

  1. 一个PV创建完后状态会变成Available,等待被PVC绑定。
  2. 一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。
  3. Pod使用完后会释放PV,PV的状态变成Released。
  4. 变成Released的PV会根据定义的回收策略做相应的回收工作。有三种回收策略,Retain、Delete和Recycle。

3.3 PV的核心配置项

创建PV时需关注3个核心配置项,直接影响PVC的匹配逻辑:

3.3.1 访问模式(accessModes)

定义PV支持的访问方式,PVC的访问模式必须是PV的子集(如PV支持RWX,PVC可申请RWXRWO):

  • RWO(ReadWriteOnce):仅允许单个节点的多个Pod读写(最常用,支持所有存储类型);
  • ROX(ReadOnlyMany):允许多个节点的Pod只读访问;
  • RWX(ReadWriteMany):允许多个节点的Pod读写访问(仅支持NFS、Ceph等共享存储)。

3.3.2 容量(capacity)

定义PV的存储容量(如storage: 2Gi),PVC申请的容量必须≤PV的容量。

3.3.3 回收策略(persistentVolumeReclaimPolicy)

定义PVC删除后PV的处理方式:

  • Retain(保留) :默认策略,PV状态变为Released,数据保留,需手动清理数据并删除PV;
  • Delete(删除):自动删除PV及关联的底层存储(如云存储EBS、S3,仅支持部分存储类型);
  • Recycle(回收) :清空PV数据(执行rm -rf /path/*),状态变为Available(仅支持NFS、hostPath,已逐步废弃)。

3.4 PV 的示例

bash 复制代码
kubectl explain pv    #查看pv的定义方式
FIELDS:
	apiVersion: v1
	kind: PersistentVolume
	metadata:    #由于 PV 是集群级别的资源,即 PV 可以跨 namespace 使用,所以 PV 的 metadata 中不用配置 namespace
	  name: 
	spec
	
kubectl explain pv.spec    #查看pv定义的规格
spce:
  nfs:(定义存储类型)
    path:(定义挂载卷路径)
    server:(定义服(定义访问模型,务器名称)
  accessModes:有以下三种访问模型,以列表的方式存在,也就是说可以定义多个访问模式) * * *
    - ReadWriteOnce          #(RWO)存储可读可写,但只支持被单个 Pod 挂载
	- ReadOnlyMany           #(ROX)存储可以以只读的方式被多个 Pod 挂载
	- ReadWriteMany          #(RWX)存储可以以读写的方式被多个 Pod 共享         注:官网
#nfs 支持全部三种;iSCSI 不支持 ReadWriteMany(iSCSI 就是在 IP 网络上运行 SCSI 协议的一种网络存储技术);HostPath 不支持 ReadOnlyMany 和 ReadWriteMany。
  capacity:(定义存储能力,一般用于设置存储空间)
    storage: 2Gi (指定大小)
  storageClassName: (自定义存储类名称,此配置用于绑定具有相同类别的PVC和PV)
  persistentVolumeReclaimPolicy: Retain    #回收策略(Retain/Delete/Recycle) * * *
#Retain(保留):当删除与之绑定的PVC时候,这个PV被标记为released(PVC与PV解绑但还没有执行回收策略)且之前的数据依然保存在该PV上,但是该PV不可用,需要手动来处理这些数据并删除该PV。
#Delete(删除):删除与PV相连的后端存储资源(只有 AWS EBS, GCE PD, Azure Disk 和 Cinder 支持)
#Recycle(回收):删除数据,效果相当于执行了 rm -rf /thevolume/* (只有 NFS 和 HostPath 支持)
bash 复制代码
kubectl explain pvc   #查看PVC的定义方式
KIND:     PersistentVolumeClaim
VERSION:  v1
FIELDS:
   apiVersion	<string>
   kind	<string>  
   metadata	<Object>
   spec	<Object>

#PV和PVC中的spec关键字段要匹配,比如存储(storage)大小、访问模式(accessModes)、存储类名称(storageClassName)
kubectl explain pvc.spec
spec:
  accessModes: (定义访问模式,必须是PV的访问模式的子集)
  resources:
    requests:
      storage: (定义申请资源的大小)
  storageClassName: (定义存储类名称,此配置用于绑定具有相同类别的PVC和PV)

四、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

官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolume

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

4.3 创建PVC并绑定PV

开发者创建PVC,申请2Gi容量、支持RWX访问模式(多路读写)的存储,此时PVC会自动去匹配多路读写且大小为2Gi的PV,匹配成功获取PVC的状态即为Bound(pv003)。

4.3.1 编写PVC与Pod配置

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
  # 通过PVC引用存储
  volumes:
  - name: html
    persistentVolumeClaim:
      claimName: mypvc  # 与PVC名称一致

4.3.2 部署与验证

1、部署PVC与Pod:

bash 复制代码
kubectl apply -f pod-vol-pvc.yaml

2、查看绑定状态:

bash 复制代码
# PVC状态变为Bound,VOLUME列显示绑定的PV(pv003)
kubectl get pvc
# PV003状态变为Bound,CLAIM列显示绑定的PVC(default/mypvc)
kubectl get pv pv003

3、验证数据持久化:

bash 复制代码
# 在NFS服务端的v3目录创建测试文件
echo "welcome to use pv3" > /data/volumes/v3/index.html

kubectl get pods -o wide
[root@master01 pv]# kubectl get po -owide
NAME           READY   STATUS    RESTARTS   AGE    IP            NODE     NOMINATED NODE   READINESS GATES
pod-hostpath   1/1     Running   0          164m   10.244.1.61   node01   <none>           <none>
pod-vol-nfs    1/1     Running   0          130m   10.244.1.62   node01   <none>           <none>
pod-vol-pvc    1/1     Running   0          92s    10.244.2.69   node02   <none>           <none>

# 访问Pod验证
curl POD_IP  # 输出"welcome to use pv3"

PVC 自动绑定到 PV003。访问 Pod 可获取 /data/volumes/v3 中内容。

4、验证释放流程:

bash 复制代码
# 删除PVC
kubectl delete pvc mypvc
# 查看PV状态(变为Released,数据仍保留在NFS的v3目录)
kubectl get pv pv003

五、StorageClass:实现NFS动态PV创建

Kubernetes 本身支持的动态 PV 创建不包括 NFS,所以需要使用外部存储卷插件分配PV。详见:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/

静态PV需要运维手动创建,当集群规模较大时(如数百个应用需存储),手动管理效率极低。StorageClass通过动态配置机制,实现PV的自动创建与回收,大幅提升运维效率。

5.1 StorageClass的核心组件

动态PV创建依赖3个核心组件:

  1. StorageClass:定义动态PV的"模板"(如存储类型、Provisioner、回收策略);
  2. Provisioner :存储分配器,负责根据StorageClass的配置自动创建PV(NFS需使用nfs-client-provisioner第三方组件);
  3. RBAC权限:Provisioner需要操作PV、PVC、StorageClass等资源,需配置对应的RBAC权限。

5.2 部署NFS动态存储(全流程)

5.2.1 前置修复(K8s 1.20版本)

由于 1.20 版本启用了 selfLink,所以 k8s 1.20+ 版本通过 nfs 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 apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
kubectl delete pods kube-apiserver -n kube-system 
kubectl get pods -n kube-system | grep apiserver

5.2.2 在stor01节点上安装nfs,并配置nfs服务

bash 复制代码
mkdir /opt/k8s
chmod 777 /opt/k8s/

vim /etc/exports
/opt/k8s 192.168.10.0/24(rw,no_root_squash,sync)

# 生效配置
exportfs -arv
# 或者
systemctl restart nfs

5.2.3 配置RBAC权限

创建 Service Account,用来管理 NFS Provisioner 在 k8s 集群中运行的权限,设置 nfs-client 对 PV,PVC,StorageClass 等的规则

yaml 复制代码
vim demo1-nfs-client-rbac.yaml
# 1. 创建 Service Account 账户,用来管理 NFS Provisioner 在 k8s 集群中运行的权限
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
# 2. 创建集群角色(ClusterRole(定义权限))
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. 集群角色绑定
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 demo1-nfs-client-rbac.yaml
# 查看账户与绑定关系
kubectl get ServiceAccount,ClusterRole,ClusterRoleBinding|grep provisioner

5.2.4 部署 NFS Provisioner

NFS Provisione(即 nfs-client),有两个功能:一个是在 NFS 共享目录下创建挂载点(volume),另一个则是将 PV 与 NFS 的挂载点建立关联。

yaml 复制代码
vim demo2-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   	  #指定Service Account账户
      containers:
        - name: nfs-client-provisioner
          # 使用官方Provisioner镜像
          image: quay.io/external_storage/nfs-client-provisioner:latest
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: nfs-storage       #配置provisioner的Name,确保该名称与StorageClass资源中的provisioner名称保持一致
            - name: NFS_SERVER
              value: stor01           #配置绑定的nfs服务器
            - name: NFS_PATH
              value: /opt/k8s          #配置绑定的nfs服务器目录
      volumes:              #申明nfs数据卷
        - name: nfs-client-root
          nfs:
            server: stor01
            path: /opt/k8s

部署Provisioner并验证:

bash 复制代码
kubectl apply -f demo2-nfs-client-provisioner.yaml
# 确认Provisioner Pod正常运行
kubectl get pods -owide

5.2.5 创建StorageClass

创建 StorageClass,负责建立 PVC 并调用 NFS provisioner 进行预定的工作,并让 PV 与 PVC 建立关联:

yaml 复制代码
vim demo3-nfs-client-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client-storageclass
provisioner: nfs-storage     #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
  archiveOnDelete: "false"   #false表示在删除PVC时不会对数据进行存档,即删除数据

部署StorageClass并查看:

bash 复制代码
kubectl apply -f demo3-nfs-client-storageclass.yaml
# 查看StorageClass
kubectl get storageclass

5.2.6 测试动态PV创建

创建PVC时指定StorageClass,验证PV是否自动创建:

yaml 复制代码
vim demo4-test-pvc-pod.yaml
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"
    args:
    - "sleep 3600"
    volumeMounts:
    - name: nfs-pvc
      mountPath: /mnt
  restartPolicy: Never
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-nfs-pvc      #与PVC名称保持一致

部署并验证:

1、部署PVC与Pod:

bash 复制代码
kubectl apply -f demo4-test-pvc-pod.yaml

2、查看动态PV:

bash 复制代码
# PVC状态变为Bound,VOLUME列显示自动生成的PV名称
kubectl get pvc 
# 查看自动创建的PV(名称以pvc-开头)
kubectl get pv

3、验证NFS目录:

bash 复制代码
# 查看NFS服务端的/opt/k8s目录(自动创建以PVC命名的目录)
# ${namespace}-${pvcName}-${pvName} 的目录格式放到 NFS 服务器上
ls /opt/k8s/
default-test-nfs-pvc-pvc-1d78c05b-601a-426b-b64f-91c44d0f6c2c

# 进入Pod写入数据,验证NFS目录同步
kubectl exec -it test-storageclass-pod sh
echo "this is test file" > /mnt/test.txt
exit

# 在NFS服务端查看文件(存在即为成功)
cat /opt/k8s/.../test.txt

六、存储方案对比与选型建议

通过前文的讲解,我们已掌握K8s中多种存储方案的特点与实战。下表对比各类方案的核心差异,并给出选型建议:

存储类型 生命周期 跨节点共享 持久化能力 核心优势 典型应用场景
emptyDir 与Pod绑定 无需配置,临时共享 容器间临时数据共享、缓存
hostPath 与节点绑定 ✅(节点级) 简单易用,节点内持久化 节点本地日志存储、单节点应用
NFS(直接使用) 独立于K8s 跨节点共享,配置简单 多节点共享数据(如日志、配置)
PV + PVC(静态) 集群级 解耦开发与运维职责 生产环境固定存储需求
StorageClass(动态) 集群级 自动创建PV,运维高效 大规模集群、动态存储需求(如微服务)

选型建议

  1. 临时数据 :优先选择emptyDir(如Pod内多容器共享临时文件);
  2. 单节点持久化 :选择hostPath(如节点本地监控数据);
  3. 多节点共享 :基础场景用NFS,生产环境推荐PV + PVC
  4. 大规模集群 :必须使用StorageClass(动态PV创建,减少运维成本);
  5. 高可靠性需求:推荐使用Ceph、GlusterFS等分布式存储(替代NFS,支持高可用)。

总结

本文从容器存储的痛点出发,逐步讲解了K8s中Volume、PV、PVC及StorageClass的核心技术:

  1. 基础Volume:emptyDir解决临时共享,hostPath实现节点级持久化,NFS突破跨节点限制;
  2. PV/PVC机制:通过"供给-需求"模型解耦运维与开发职责,是生产环境持久化的基础;
  3. 动态存储:StorageClass + nfs-client-provisioner实现PV自动创建,大幅提升运维效率。

掌握这些技术,能够帮助我们构建可靠、高效的K8s存储架构,适应从单节点到大规模集群的不同需求。在实际应用中,需结合业务场景(如数据持久性、共享需求、集群规模)选择合适的存储方案,同时关注存储的性能与可靠性(如分布式存储的高可用配置)。

希望本文对大家理解K8s持久化存储有所帮助,如有疑问或补充,欢迎在评论区交流!

相关推荐
熙客19 小时前
阿里云监控:SLS的使用
运维·阿里云·云原生·云计算
xiaolu28919 小时前
k8s学习 - 命令记录
学习·docker·kubernetes
做运维的阿瑞19 小时前
Kubernetes 存储核心理论:深入理解 PVC 静态迁移与动态扩容
运维·容器·kubernetes
小任今晚几点睡19 小时前
Docker 完整指南:从入门到企业实战
运维·docker·容器
●VON20 小时前
重生之我在大学自学鸿蒙开发第七天-《AI语音朗读》
学习·华为·云原生·架构·harmonyos
Zz_waiting.20 小时前
服务注册 / 服务发现 - Eureka
spring cloud·云原生·eureka·服务发现
qq_2642208921 小时前
K8s-Pod控制器
容器
追梦者12321 小时前
k8s项目实战篇 kubesphere安装
云原生·容器·kubernetes
瑶总迷弟21 小时前
使用 Docker 和 docker-compose 快速部署 openGauss
linux·数据库·云原生·eureka