Kubernetes-PV存储卷
Kubernetes 作为容器编排领域的领导者,其存储系统设计解决了容器化应用中数据持久化的核心需求。PV(PersistentVolume)和 PVC(PersistentVolumeClaim)是 Kubernetes 存储体系的两大基石。
PV概念
PersistentVolume (PV) 是 Kubernetes 集群中的存储资源单元,由集群管理员预先配置,代表底层物理存储的逻辑抽象。PV 独立于 Pod 生命周期存在,支持多种存储类型包括本地存储(HostPath)、网络存储(NFS、iSCSI、Ceph)以及云存储(AWS EBS、Azure Disk 等)
PV 的核心属性包括:
- 容量:指定存储空间大小(如 1Gi、5Gi)
- 访问模式:定义读写权限(ReadWriteOnce/RWO、ReadOnlyMany/ROX、ReadWriteMany/RWX)
- 回收策略:决定 PVC 释放后 PV 的处理方式(Retain、Delete、Recycle)
- 存储类别:通过 storageClassName 实现动态供应
PVC 概念
PersistentVolumeClaim (PVC) 是用户对存储资源的声明(申请),描述应用所需的存储特性(容量、访问模式等),不关心底层实现细节。PVC 与 Pod 同生命周期,当 Pod 被删除时,关联的 PVC 通常也会被删除(除非显式保留)。PVC 通过匹配机制与 PV 绑定,绑定后 PV 进入专有状态,不能被其他 PVC 使用
StorageClass概念
K8S有两种存储资源的供应模式:静态模式和动态模式,资源供应的最终目的就是将适合的PV与PVC绑 定:
- 静态模式:管理员预先创建许多各种各样的PV,等待PVC申请使用。
- 动态模式:管理员无须预先创建PV,而是通过StorageClass自动完成PV的创建以及与PVC的绑定。
PV 与 PVC 的关系
- PV 是仓库:由管理员预先建立的存储资源池
- PVC 是租约:用户通过声明获取存储使用权
- StorageClass 是自动化系统:动态按需创建 PV 的模板机制
PV 与 PVC 生命周期
PV 和 PVC 的交互遵循明确的五阶段生命周期:
Provisioning(配置)
- 静态配置:管理员手动创建 PV,适用于固定规模存储需求
- 动态配置:通过 StorageClass 自动创建 PV,响应 PVC 请求
Binding(绑定)
- Kubernetes 控制平面匹配 PVC 与符合条件的 PV
- 绑定后形成一对一独占关系,PV 状态变为 Bound
Using(使用)
- Pod 通过 volumes 字段引用 PVC
- 存储卷被挂载到容器指定路径
- 启用 StorageProtection 准入控制可防止误删使用中的 PVC
Releasing(释放)
- Pod 删除后,PVC 被释放
- PV 根据回收策略进入 Released 或 Failed 状态
Recycling(回收)
Retain:保留 PV 和数据,需手动清理(生产环境推荐)
Delete:自动删除 PV 及底层存储(需存储插件支持)
Recycle(已弃用):删除数据后重置 PV 为 Available
访问模式
| 模式 | 缩写 | 描述 | 适用存储类型 |
|---|---|---|---|
| ReadWriteOnce | RWO | 单节点读写 | 本地磁盘、云盘 |
| ReadOnlyMany | ROX | 多节点只读 | 配置文件、日志 |
| ReadWriteMany | RWX | 多节点读写 | NFS、Ceph |
注意:不同存储类型支持的访问模式不同,例如 HostPath 不支持 ROX/RWX,iSCSI 不支持 RWX
回收策略
决定 PVC 删除后 PV 和数据的命运
- Retain
- 保留 PV 和存储数据
- 管理员需手动执行:1) 清理数据 2) 删除 PV 3) 重建 PV
- 适用场景:数据敏感型应用(如数据库)
- Delete
- 自动删除 PV 及后端存储资源
- 依赖存储插件支持(AWS EBS、GCE PD 等)
- 风险:数据不可逆丢失
- Recycle(Kubernetes 1.15+ 已弃用)
- 自动删除卷内数据(执行 rm -rf /volume/*)
- 仅适用于 NFS 和 HostPath
- 替代方案:使用动态配置的 StorageClass
- 当删除pvc后塔会自动下载一个镜像
apiVersion: v1 # Kubernetes API 版本
kind: PersistentVolume # 资源类型为持久卷(PV)
metadata:
name: nfs-pv # PV 的名称,集群内唯一标识
spec:
persistentVolumeReclaimPolicy: Retain/Delete/Recycle # 回收策略NFS 服务器部署
在 Kubernetes 中使用 NFS 存储前,需要先部署和配置 NFS 服务器。以下是基于 openEuler/CentOS 系统的标准配置流程:
1. NFS 服务器安装
# 安装NFS服务器软件包
sudo yum install -y nfs-utils
# 创建共享目录并设置权限
sudo mkdir -p /datanfs/k8s
sudo chown nobody:nobody /datanfs/k8s2. 配置共享目录
编辑 /etc/exports 文件,添加共享配置:
cat > /etc/exports << EOF
/datanfs/k8s 192.168.148.0/24(rw,sync,all_squash,anonuid=65534,anongid=65534,no_subtree_check)
EOF关键参数解析:
rw:允许读写操作sync:同步写入,保证数据一致性all_squash:将所有客户端用户映射为匿名用户anonuid/anongid:指定映射的UID/GID(通常为nobody用户)no_subtree_check:提高性能,不检查子目录权限
3. 启动NFS服务
# 应用导出配置并启动服务
sudo exportfs -a
sudo systemctl enable nfs-server --now
# 验证共享配置
exportfs -v4. 客户端节点配置
所有 Kubernetes 节点需要安装 NFS 客户端工具:
sudo yum install -y nfs-utils可通过手动挂载测试NFS共享是否可用:
mkdir -p /mnt/nfs
mount -t nfs 192.168.148.97:/datanfs/k8s /mnt/nfs/Pod 基础挂载NFS
资源清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-nfs
labels:
app: demo-nfs
spec:
replicas: 3
selector:
matchLabels:
app: demo-nfs
template:
metadata:
name: demo-nfs
labels:
app: demo-nfs
spec:
volumes:
- name: demo-nfs
nfs:
path: /datanfs/k8s # NFS挂载的目录
server: 192.168.148.183 # NFS 服务
containers:
- name: demo-nfs
image: nginx
imagePullPolicy: Never
volumeMounts:
- name: demo-nfs
mountPath: /usr/share/nginx/html/测试挂载
NFS服务端创建测试文件
bashhostname -I | awk '{print $1}' > /datanfs/k8s/index.htmlkubernetes集群测试
bash# 查看刚才创建的pod的IP地址 kubectl get pod -o wide # http协议请求网页 curl 10.244.58.100
静态 PV 配置
创建 PV(NFS 示例)
apiVersion: v1 # Kubernetes API 版本
kind: PersistentVolume # 资源类型为持久卷(PV)
metadata:
name: nfs-pv # PV 的名称,集群内唯一标识
spec:
storageClassName: demo-nfs-pv #
capacity: # 定义存储容量
storage: 50Mi # 分配存储空间
accessModes: # 访问模式配置
- ReadWriteMany # 允许多个节点同时读写(RWX)
persistentVolumeReclaimPolicy: Retain # PVC释放后PV的处理策略:保留数据
nfs: # NFS存储配置段
path: /data/volumes/v1 # NFS服务器上的导出路径
server: 192.168.148.183 # NFS服务器IP地址创建 PVC
apiVersion: v1 # Kubernetes API 版本
kind: PersistentVolumeClaim # 资源类型为持久卷声明(PVC)
metadata:
name: app-pvc # PVC 的名称,在命名空间内唯一
spec:
storageClassName: demo-nfs-pv # 这里要和PV的storageClassName保持相同
accessModes: # 请求的访问模式
- ReadWriteMany # 需要支持多节点读写(RWX)
resources: # 资源请求配置
requests:
storage: 50Mi # 申请50MB存储空间(需匹配PV容量)
# 注意:此示例未指定storageClassName,将匹配可用的未分类PVPod 挂载 PVC
apiVersion: v1 # Kubernetes API 版本
kind: Pod # 资源类型为Pod
metadata:
name: web-server # Pod名称
spec:
containers:
- name: nginx # 容器名称
image: nginx # 使用官方nginx镜像
imagePullPolicy: Never
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 200m
memory: 200Mi
volumeMounts: # 卷挂载配置
- name: data # 引用下面定义的卷名称
mountPath: /usr/share/nginx/html # 挂载到容器内的路径
volumes: # Pod级存储卷定义
- name: data # 卷名称,与volumeMounts对应
persistentVolumeClaim: # PVC类型卷
claimName: app-pvc # 使用名为app-pvc的PVC
# readOnly: false # 默认可读写(可显式声明)测试
# 写入测试用例
hostname -I > /data/volumes/v1/index.html
# 获取pod的IP地址
kubectl get pod -o wide
# curl请求NFS挂载的文件(NFS属于网络存储会有延迟)
curl 10.224.85.248动态 PV 配置(StorageClass)
因为Kubernetes自己不自带NFS动态供给的驱动,所以我们需要下载第三方的NFS动态供给驱动。Kubernetes官方推荐了两个第三方的驱动可供选择。
安装 nfs-subdir-external-provisioner
helm安装
# 创建命名空间
kubectl create ns nfs-provisioner
# 安装
helm install nfs-subdir-external-provisioner \
nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--namespace kube-system \
--set nfs.server=<NFS-SERVER-IP> \
--set nfs.path=<NFS-SHARE-PATH> \
--set storageClass.name=nfs-client \
--set storageClass.defaultClass=true \
--set image.repository=registry.k8s.io/sig-storage/nfs-subdir-external-provisioner \
--set image.tag=v4.0.2
#注:需要替换国内镜像
crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisionerv4.0.2
# 卸载nfs-subdir-external-provisioner
helm uninstall nfs-subdir-external-provisioner \
-n nfs-provisioner参数说明:
nfs.server: NFS 服务器 IP 地址nfs.path: NFS 共享路径storageClass.name: 存储类名称storageClass.defaultClass: 设为默认存储类image.repository: 使用官方镜像仓库image.tag: 指定版本
源码部署
wget https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/releases/download/nfs-subdir-external-provisioner-4.0.18/nfs-subdir-external-provisioner-4.0.18.tgz
tar -zxvf nfs-subdir-external-provisioner-4.0.18.tgz
# 解压后进入到deploy目录中操作修改命名空间
# 创建命名空间
kubectl create namespace nfs-provisioner
# 替换默认的default命名空间
sed -i 's/namespace: default/namespace: nfs-provisioner/g' `grep -rl 'namespace:
default' ./`修改deployment.yaml
# 与RBAC文件中的namespace保持一致
# 注:需要替换国内镜像
crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2
# 或
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisionerv4.0.2
# 下面是需要修改的地方
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default # 需与RBAC中的namespace保持一致
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # 必须与RBAC中创建的ServiceAccount一致
containers:
- name: nfs-client-provisioner
image: crpi-vseztbia2u1dmfxq.cn-beijing.personal.cr.aliyuncs.com/linux_library/nfs-subdir-external-provisioner:v4.0.2 # 修正镜像地址格式
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 需与StorageClass的provisioner字段匹配
- name: NFS_SERVER
value: 192.168.148.183 # NFS服务器IP
- name: NFS_PATH
value: /datanfs/k8s # NFS共享路径
volumes:
- name: nfs-client-root
nfs:
server: 192.168.148.183 # 需与env中NFS_SERVER一致
path: /datanfs/k8s # 需与env中NFS_PATH一致部署
# 批量安装
kubectl apply -k .查看部署情况
kubectl get all -o wide -n nfs-provisioner查询动态供应存储类
kubectl get storageclass创建一个测试pod
apiVersion: v1
kind: Pod
metadata:
name: demo-pv-pod # Pod名称,在命名空间内需唯一
spec:
volumes: # 定义Pod级别的存储卷
- name: demo-pv # 卷名称,与volumeMounts对应
persistentVolumeClaim: # 使用PVC类型的存储卷
claimName: demo-nfs-pv # 引用的PVC名称,必须与下面定义的PVC一致
containers:
- name: demo-pod # 容器名称
image: nginx # 使用官方nginx镜像
imagePullPolicy: Never # 镜像拉取策略:Never表示使用本地镜像
resources: # 资源限制配置
limits: # 容器最大可用资源
cpu: 200m # 0.2个CPU核心
memory: 200Mi # 200MB内存
requests: # 容器启动最小需求资源
cpu: 200m
memory: 200Mi
volumeMounts: # 容器内的卷挂载点
- name: demo-pv # 引用上面定义的卷名称
mountPath: /usr/share/nginx/html # 挂载到容器内的路径(nginx默认网站目录)
# readOnly: false # 默认可读写,如需只读可设置为true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-nfs-pv # PVC名称,需与Pod中引用的名称一致
spec:
storageClassName: nfs-client # 指定使用的StorageClass名称(需提前创建)
accessModes: # 访问模式配置
- ReadWriteOnce # 单节点读写模式(RWO)
resources:
requests:
storage: 30Mi # 申请30MB存储空间
# 注意:实际分配空间取决于StorageClass和PV配置
# 动态配置时会自动创建符合要求的PV
# ----------------------------
# 使用说明:
# 1. 需提前部署NFS Provisioner并创建名为nfs-client的StorageClass
# 2. 应用配置:
# kubectl apply -f demo-pvc-pod.yaml
# 3. 验证:
# kubectl get pvc demo-nfs-pv # 查看PVC是否Bound
# kubectl exec -it demo-pv-pod -- ls /usr/share/nginx/html # 查看挂载内容
# 4. 清理:
# kubectl delete pod demo-pv-pod
# kubectl delete pvc demo-nfs-pv测试
# 写入测试用例
hostname -I > /datanfs/k8s/default-demo-nfs-pv-pvc-578cb0b6-5661-4382-9f82-b6c0f7c9bc2c/index.html
# 获取pod的IP地址
kubectl get pod -o wide
# curl请求NFS挂载的文件(NFS属于网络存储会有延迟)
curl 10.224.58.207默认存储
默认存储是当PVC未指定storageClassName的情况下默认使用的存储
创建默认存储
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client # 这里的名字如果存在的话会直接将原有的存储变成默认的
namespace: nfs-provisioner
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 设为默认存储类
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 必须与deployment中的PROVISIONER_NAME一致
parameters:
archiveOnDelete: "false"创建测试pod
apiVersion: v1
kind: Pod
metadata:
name: demo-default-pv-pod
spec:
volumes:
- name: demo-default-pv
persistentVolumeClaim:
claimName: demo-default-nfs-pv
containers:
- name: demo-pod
image: nginx
imagePullPolicy: Never
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 200m
memory: 200Mi
volumeMounts:
- name: demo-default-pv
mountPath: /usr/share/nginx/html
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-default-nfs-pv
spec:
storageClassName: nfs-default-client # 这里指定存储类名
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Mi