K8S(九)—— Kubernetes持久化存储深度解析:从Volume到PV/PVC与动态存储
文章目录
- 前言
- 一、容器存储的核心挑战与K8s Volume解决方案
- 1.1 容器存储的短暂性问题
- 1.2 K8s Volume的核心价值
- 二、K8s基础存储卷实践
- 2.1 emptyDir:Pod内临时共享存储
- 2.1.1 核心特点
- 2.1.2 实战配置(YAML示例)
- 2.1.3 验证与结果
- 2.2 hostPath:节点级持久化存储
- 2.2.1 核心特点
- 2.2.2 实战配置(YAML示例)
- 2.2.3 验证与结果
- 2.3 NFS:跨节点共享存储
- 2.3.1 核心特点
- 2.3.2 实战配置(分服务端与客户端)
- 步骤1:部署NFS服务端(独立节点,如stor01)
- 步骤2:K8s节点安装NFS客户端(所有节点)
- 步骤3:编写NFS Volume测试Pod配置
- 步骤4:验证与结果
- 三、PV与PVC:K8s持久化存储的核心机制
- 3.1 PV与PVC的核心概念
- 3.2 PV与PVC的生命周期
- 3.2.1 生命周期流程
- 3.2.2 PV的核心状态
- 3.2.3 一个PV从创建到销毁的具体流程如下
- 3.3 PV的核心配置项
- 3.3.1 访问模式(accessModes)
- 3.3.2 容量(capacity)
- 3.3.3 回收策略(persistentVolumeReclaimPolicy)
- 3.4 PV 的示例
- 四、NFS + PV + PVC 实战演练
- 4.1 前置准备(NFS服务端配置)
- 4.2 手动创建PV(静态配置)
- 4.3 创建PVC并绑定PV
- 4.3.1 编写PVC与Pod配置
- 4.3.2 部署与验证
- 五、StorageClass:实现NFS动态PV创建
- 5.1 StorageClass的核心组件
- 5.2 部署NFS动态存储(全流程)
- 5.2.1 前置修复(K8s 1.20版本)
- 5.2.2 在stor01节点上安装nfs,并配置nfs服务
- 5.2.3 配置RBAC权限
- 5.2.4 部署 NFS Provisioner
- 5.2.5 创建StorageClass
- 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类型,适用于不同场景。本节将重点讲解emptyDir
、hostPath
和NFS
三种常用类型的特点与实战。
2.1 emptyDir:Pod内临时共享存储
emptyDir是最基础的Volume类型,其生命周期与Pod完全绑定,适合临时缓存或Pod内多容器数据共享场景。
2.1.1 核心特点
- 自动创建:Pod调度到节点时,K8s自动在节点本地创建emptyDir目录;
- 随Pod销毁:Pod删除时,emptyDir内的数据也会被彻底清理;
- 适合场景:仅适合临时缓存或容器间数据共享。
- 存储介质:默认使用节点的本地磁盘,也可配置为内存(
medium: Memory
,适合高性能临时缓存,但需注意内存溢出风险)。
2.1.2 实战配置(YAML示例)
# 创建目录用于存放YAML文件
mkdir /opt/volumes && cd /opt/volumesvim pod-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-emptydirnamespace: defaultlabels:app: myapptier: frontend
spec:containers:# 应用容器:Nginx,挂载emptyDir到/html目录- name: myappimage: ikubernetes/myapp:v1imagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 80#定义容器挂载内容volumeMounts:#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷- name: html#挂载至容器中哪个目录mountPath: /usr/share/nginx/html/# 辅助容器:BusyBox,定期写入日期到共享目录- name: busyboximage: busybox:latestimagePullPolicy: IfNotPresentvolumeMounts:- 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并查看状态:
kubectl apply -f pod-emptydir.yaml
# 确认Pod的2个容器均正常运行(READY为2/2)
kubectl get pods pod-emptydir -o wide
2、访问Nginx验证数据共享:
# 在上面定义了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调度到无目录的节点报错):
1)在 node01 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node01.simon.com' > /data/pod/volume1/index.html2)在 node02 节点上创建挂载目录
mkdir -p /data/pod/volume1
echo 'node02.simon.com' > /data/pod/volume1/index.html
2、 编写hostPath测试Pod配置:
vim pod-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-hostpathnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1#定义容器挂载内容volumeMounts:#使用的存储卷名称,如果跟下面volume字段name值相同,则表示使用volume的这个存储卷- name: html#挂载至容器中哪个目录mountPath: /usr/share/nginx/html#读写挂载方式,默认为读写模式falsereadOnly: false#volumes字段定义了paues容器关联的宿主机或分布式文件系统存储卷volumes:#存储卷名称- name: html#路径,为宿主机存储路径hostPath:#在宿主机上目录的路径path: /data/pod/volume1#定义类型,这表示如果宿主机没有此目录则会自动创建type: DirectoryOrCreate
2.2.3 验证与结果
1、部署Pod并查看调度节点:
kubectl apply -f pod-hostpath.yaml
# 查看Pod调度到的节点(NODE列)
kubectl get pods pod-hostpath -o wide
2、访问Nginx验证数据:
curl POD_IP # 输出对应节点的标识(如node02.benet.com)
3、 验证持久化:删除Pod后重新部署,若调度到同一节点,数据仍保留;若调度到其他节点,数据为新节点的标识。
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服务:
# 安装NFS服务端组件(rpcbind为依赖)
yum install -y nfs-utils rpcbind
2、配置NFS共享目录:
# 创建共享目录
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、验证配置
netstat -anpu | grep rpc
# 验证共享(输出共享目录信息即为成功)
showmount -e
步骤2:K8s节点安装NFS客户端(所有节点)
# 所有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配置
vim pod-nfs-vol.yaml
kind: Pod
apiVersion: v1
metadata:name: pod-vol-nfsnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/htmlvolumes:- name: htmlnfs:path: /data/volumes # NFS共享目录server: stor01 # NFS服务端地址
步骤4:验证与结果
1、部署Pod并访问:
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中的数据,证明跨节点共享生效。
# 修改 pod-nfs-vol.yaml,通过nodeName:强制绑定节点
kind: Pod
apiVersion: v1
metadata:name: pod-vol-nfsnamespace: default
spec:nodeName: node01 # 强制调度到node01containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: htmlmountPath: /usr/share/nginx/htmlvolumes:- name: htmlnfs: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(回收)
- Provisioning(配置):运维手动创建PV(静态配置),或通过StorageClass自动创建PV(动态配置);
- Binding(绑定):开发者创建PVC,K8s根据PVC的需求(容量、访问模式)匹配可用PV,绑定后PV状态变为
Bound
; - Using(使用):Pod通过PVC挂载存储,K8s通过
StorageProtection
机制阻止删除正在使用的PVC; - Releasing(释放):Pod 释放 Volume 并删除 PVC,PV状态变为
Released
(数据仍保留); - 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从创建到销毁的具体流程如下
- 一个PV创建完后状态会变成Available,等待被PVC绑定。
- 一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。
- Pod使用完后会释放PV,PV的状态变成Released。
- 变成Released的PV会根据定义的回收策略做相应的回收工作。有三种回收策略,Retain、Delete和Recycle。
3.3 PV的核心配置项
创建PV时需关注3个核心配置项,直接影响PVC的匹配逻辑:
3.3.1 访问模式(accessModes)
定义PV支持的访问方式,PVC的访问模式必须是PV的子集(如PV支持RWX
,PVC可申请RWX
或RWO
):
- 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 的示例
kubectl explain pv #查看pv的定义方式
FIELDS:apiVersion: v1kind: PersistentVolumemetadata: #由于 PV 是集群级别的资源,即 PV 可以跨 namespace 使用,所以 PV 的 metadata 中不用配置 namespacename: speckubectl 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 支持)
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):
# 创建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 -arvshowmount -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个目录:
vim pv-demo.yaml
# PV001:1Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:name: pv001labels:name: pv001
spec:nfs:server: stor01path: /data/volumes/v1accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 1Gi
---
# PV002:2Gi,仅支持RWO
apiVersion: v1
kind: PersistentVolume
metadata:name: pv002labels:name: pv002
spec:nfs:server: stor01path: /data/volumes/v2accessModes: ["ReadWriteOnce"]capacity:storage: 2Gi
---
# PV003:2Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:name: pv003labels:name: pv003
spec:nfs:server: stor01path: /data/volumes/v3accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 2Gi
---
# PV004:4Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:name: pv004labels:name: pv004
spec:nfs:server: stor01path: /data/volumes/v4accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 4Gi
---
# PV005:5Gi,支持RWO、RWX
apiVersion: v1
kind: PersistentVolume
metadata:name: pv005labels:name: pv005
spec:nfs:server: stor01path: /data/volumes/v5accessModes: ["ReadWriteMany","ReadWriteOnce"]capacity:storage: 5Gi
部署PV并查看状态:
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配置
vim pod-vol-pvc.yaml
# PVC:申请2Gi,RWX访问模式
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: mypvcnamespace: default
spec:accessModes: ["ReadWriteMany"] # 申请RWX模式resources:requests:storage: 2Gi # 申请2Gi容量
---
# Pod:通过PVC挂载存储
apiVersion: v1
kind: Pod
metadata:name: pod-vol-pvcnamespace: default
spec:containers:- name: myappimage: ikubernetes/myapp:v1volumeMounts:- name: html # 与下方volumes.name对应mountPath: /usr/share/nginx/html# 通过PVC引用存储volumes:- name: htmlpersistentVolumeClaim:claimName: mypvc # 与PVC名称一致
4.3.2 部署与验证
1、部署PVC与Pod:
kubectl apply -f pod-vol-pvc.yaml
2、查看绑定状态:
# PVC状态变为Bound,VOLUME列显示绑定的PV(pv003)
kubectl get pvc
# PV003状态变为Bound,CLAIM列显示绑定的PVC(default/mypvc)
kubectl get pv pv003
3、验证数据持久化:
# 在NFS服务端的v3目录创建测试文件
echo "welcome to use pv3" > /data/volumes/v3/index.htmlkubectl 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、验证释放流程:
# 删除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个核心组件:
- 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版本)
由于 1.20 版本启用了 selfLink,所以 k8s 1.20+ 版本通过 nfs provisioner 动态生成pv会报错,解决方法如下:
# 编辑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服务
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 等的规则
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: ServiceAccountname: nfs-client-provisionernamespace: default
roleRef:kind: ClusterRolename: nfs-client-provisioner-clusterroleapiGroup: rbac.authorization.k8s.io
部署RBAC:
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 的挂载点建立关联。
vim demo2-nfs-client-provisioner.yaml
kind: Deployment
apiVersion: apps/v1
metadata:name: nfs-client-provisioner
spec:replicas: 1selector:matchLabels:app: nfs-client-provisionerstrategy:type: Recreatetemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisioner #指定Service Account账户containers:- name: nfs-client-provisioner# 使用官方Provisioner镜像image: quay.io/external_storage/nfs-client-provisioner:latestimagePullPolicy: IfNotPresentvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: nfs-storage #配置provisioner的Name,确保该名称与StorageClass资源中的provisioner名称保持一致- name: NFS_SERVERvalue: stor01 #配置绑定的nfs服务器- name: NFS_PATHvalue: /opt/k8s #配置绑定的nfs服务器目录volumes: #申明nfs数据卷- name: nfs-client-rootnfs:server: stor01path: /opt/k8s
部署Provisioner并验证:
kubectl apply -f demo2-nfs-client-provisioner.yaml
# 确认Provisioner Pod正常运行
kubectl get pods -owide
5.2.5 创建StorageClass
创建 StorageClass,负责建立 PVC 并调用 NFS provisioner 进行预定的工作,并让 PV 与 PVC 建立关联:
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并查看:
kubectl apply -f demo3-nfs-client-storageclass.yaml
# 查看StorageClass
kubectl get storageclass
5.2.6 测试动态PV创建
创建PVC时指定StorageClass,验证PV是否自动创建:
vim demo4-test-pvc-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: test-nfs-pvc
spec:accessModes:- ReadWriteManystorageClassName: nfs-client-storageclass #关联StorageClass对象resources:requests:storage: 1Gi
---
# Pod:使用动态PVC
apiVersion: v1
kind: Pod
metadata:name: test-storageclass-pod
spec:containers:- name: busyboximage: busybox:latestimagePullPolicy: IfNotPresentcommand:- "/bin/sh"- "-c"args:- "sleep 3600"volumeMounts:- name: nfs-pvcmountPath: /mntrestartPolicy: Nevervolumes:- name: nfs-pvcpersistentVolumeClaim:claimName: test-nfs-pvc #与PVC名称保持一致
部署并验证:
1、部署PVC与Pod:
kubectl apply -f demo4-test-pvc-pod.yaml
2、查看动态PV:
# PVC状态变为Bound,VOLUME列显示自动生成的PV名称
kubectl get pvc
# 查看自动创建的PV(名称以pvc-开头)
kubectl get pv
3、验证NFS目录:
# 查看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,运维高效 | 大规模集群、动态存储需求(如微服务) |
选型建议
- 临时数据:优先选择
emptyDir
(如Pod内多容器共享临时文件); - 单节点持久化:选择
hostPath
(如节点本地监控数据); - 多节点共享:基础场景用
NFS
,生产环境推荐PV + PVC
; - 大规模集群:必须使用
StorageClass
(动态PV创建,减少运维成本); - 高可靠性需求:推荐使用Ceph、GlusterFS等分布式存储(替代NFS,支持高可用)。
总结
本文从容器存储的痛点出发,逐步讲解了K8s中Volume、PV、PVC及StorageClass的核心技术:
- 基础Volume:emptyDir解决临时共享,hostPath实现节点级持久化,NFS突破跨节点限制;
- PV/PVC机制:通过“供给-需求”模型解耦运维与开发职责,是生产环境持久化的基础;
- 动态存储:StorageClass + nfs-client-provisioner实现PV自动创建,大幅提升运维效率。
掌握这些技术,能够帮助我们构建可靠、高效的K8s存储架构,适应从单节点到大规模集群的不同需求。在实际应用中,需结合业务场景(如数据持久性、共享需求、集群规模)选择合适的存储方案,同时关注存储的性能与可靠性(如分布式存储的高可用配置)。
希望本文对大家理解K8s持久化存储有所帮助,如有疑问或补充,欢迎在评论区交流!