Kubernetes PersistentVolume (PV) 配置详解
Kubernetes PersistentVolume (PV) 配置详解
完整配置示例
# PersistentVolume for MySQL
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
labels:
app: data-collection
component: mysql
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /data/data-collection/mysql
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: Exists
字段详解
1. apiVersion
apiVersion: v1
- 含义:Kubernetes API 版本
- 说明:PersistentVolume 是核心 API 对象,使用
v1版本(GA 稳定版本)
2. kind
kind: PersistentVolume
- 含义:资源类型
- 说明:声明这是一个 PersistentVolume(持久卷)资源
3. metadata(元数据)
metadata:
name: mysql-pv # PV 名称,集群内唯一
labels:
app: data-collection # 应用标签,用于分组识别
component: mysql # 组件标签,标识这是 MySQL 组件的 PV
| 字段 | 说明 |
|---|---|
name |
PV 的唯一标识符,用于 PVC 绑定和 kubectl 查询 |
labels |
键值对标签,可用于: |
- PVC 选择器匹配 (
selector.matchLabels) - kubectl 筛选 (
kubectl get pv -l component=mysql) - 资源分组管理 |
4. spec.capacity(容量)
spec:
capacity:
storage: 10Gi
- 含义:PV 的存储容量
- 说明:
10Gi= 10 Gibibyte = 10 × 1024³ bytes ≈ 10.7 GB- PVC 请求的存储不能超过此值
- 本地存储场景下,需确保宿主机目录有足够空间
存储单位对照表:
| 单位 | 含义 | 换算 |
|---|---|---|
Ki |
Kibibyte | 1024 bytes |
Mi |
Mebibyte | 1024 Ki |
Gi |
Gibibyte | 1024 Mi |
Ti |
Tebibyte | 1024 Gi |
5. spec.volumeMode(卷模式)
volumeMode: Filesystem
- 含义:卷的使用模式
- 可选值:
| 值 | 说明 |
|---|---|
Filesystem |
作为文件系统挂载到 Pod(默认值) |
Block |
作为原始块设备挂载,适用于数据库等需要直接访问块设备的场景 |
6. spec.accessModes(访问模式)
accessModes:
- ReadWriteOnce
- 含义:PV 的访问权限模式
- 可选值:
| 模式 | 缩写 | 说明 |
|---|---|---|
ReadWriteOnce |
RWO | 单节点读写,同一时间只能被一个节点挂载为读写模式 |
ReadOnlyMany |
ROX | 多节点只读,可被多个节点同时挂载为只读模式 |
ReadWriteMany |
RWX | 多节点读写,可被多个节点同时挂载为读写模式 |
ReadWriteOncePod |
RWOP | 单 Pod 读写,同一时间只能被一个 Pod 挂载(K8s 1.22+) |
- 本例选择 RWO 的原因:
- 本地存储(local)只能被一个节点访问
- MySQL 数据库不支持多节点同时写入
7. spec.persistentVolumeReclaimPolicy(回收策略)
persistentVolumeReclaimPolicy: Retain
- 含义:当 PVC 被删除后,PV 的处理方式
- 可选值:
| 策略 | 说明 | 适用场景 |
|---|---|---|
Retain |
保留数据,需手动清理 | 生产环境、重要数据 |
Delete |
自动删除 PV 和存储数据 | 云存储(AWS EBS、GCE PD) |
Recycle |
已废弃,清空数据后重新可用 | 旧版本 K8s |
- 本例选择 Retain 的原因:
- MySQL 数据是重要业务数据,不应自动删除
- 防止误删 PVC 导致数据丢失
8. spec.storageClassName(存储类名称)
storageClassName: local-storage
- 含义:关联的 StorageClass 名称
- 作用:
- PVC 通过
storageClassName匹配 PV - 动态 provisioning 时由 StorageClass 创建 PV
- PVC 通过
绑定流程:
PVC 请求 → storageClassName: local-storage
↓
匹配 PV → storageClassName: local-storage
↓
绑定成功
查看 Kubernetes 支持的 StorageClass:
# 查看所有 StorageClass
kubectl get storageclass
# 简写形式
kubectl get sc
# 查看详细信息
kubectl get sc -o wide
# 查看特定 StorageClass 详情
kubectl describe sc local-storage
# 查看默认 StorageClass(有 default 标注)
kubectl get sc -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}'
输出示例:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE AGE
local-storage kubernetes.io/no-provisioner Retain WaitForFirstConsumer 10d
standard (default) kubernetes.io/aws-ebs Delete Immediate 30d
字段说明:
| 字段 | 说明 |
|---|---|
NAME |
StorageClass 名称 |
PROVISIONER |
存储供应商(no-provisioner 表示手动管理) |
RECLAIMPOLICY |
回收策略(Retain/Delete) |
VOLUMEBINDINGMODE |
绑定模式 |
AGE |
创建时间 |
9. spec.local(本地存储配置)
local:
path: /data/data-collection/mysql
- 含义:本地存储的宿主机路径
- 说明:
- 必须配合
nodeAffinity使用 - 该目录必须在指定节点上预先创建
- 数据直接存储在宿主机磁盘上
- 必须配合
目录创建命令:
mkdir -p /data/data-collection/mysql
10. spec.nodeAffinity(节点亲和性)
什么是 nodeAffinity?
通俗解释:nodeAffinity 就像是给 PV 设置一个"住址要求",告诉 Kubernetes:“这个 PV 只能给住在特定节点的 Pod 使用”。
为什么需要它?
想象一个生活场景:
- 你有一本书(PV),这本书放在你家的书房(节点 A 的磁盘)
- 你的朋友(Pod)想借这本书
- 如果朋友住在你家(节点 A),他可以直接去书房拿书
- 如果朋友住在隔壁小区(节点 B),他根本无法进入你家书房拿书
结论:本地存储的 PV 数据只存在于特定节点的磁盘上,其他节点根本访问不到!所以必须告诉 Kubernetes:只有运行在这个节点的 Pod 才能使用这个 PV。
生活化类比
┌─────────────────────────────────────────────────────────────────┐
│ 餐厅用餐类比 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 🏠 服务器节点 = 餐厅的不同分店 │
│ 📦 PV(持久卷) = 某分店的储物柜 │
│ 🧑 Pod(容器) = 顾客 │
│ 🔑 nodeAffinity = "只允许在本分店用餐的顾客使用本店储物柜" │
│ │
│ ─────────────────────────────────────────────────────────────── │
│ │
│ 分店 A(node1) 分店 B(node2) │
│ ├─ 储物柜 A(PV) ├─ 储物柜 B(PV) │
│ │ 只给在 A店 │ 只给在 B店 │
│ │ 用餐的顾客 │ 用餐的顾客 │
│ │
│ 顾客在 A店用餐 → 可以用储物柜 A ✅ │
│ 顾客在 B店用餐 → 不能用储物柜 A ❌(物理上无法访问) │
│ │
└─────────────────────────────────────────────────────────────────┘
配置详解
nodeAffinity:
required: # required = 必须满足(硬性要求)
nodeSelectorTerms: # 节点选择条件列表
- matchExpressions: # 匹配表达式
- key: kubernetes.io/hostname # 节点标签的键(节点名称)
operator: Exists # 操作符:只要标签存在就匹配
逐层解析:
| 层级 | 字段 | 含义 | 通俗理解 |
|---|---|---|---|
| 1 | nodeAffinity |
节点亲和性 | “住址要求” |
| 2 | required |
硬性要求 | “必须满足,否则拒绝” |
| 3 | nodeSelectorTerms |
节点选择器 | “符合条件的节点列表” |
| 4 | matchExpressions |
匹配表达式 | “具体的匹配规则” |
| 5 | key |
标签键 | “要检查的标签名” |
| 5 | operator |
操作符 | “如何判断标签” |
operator 操作符详解
| 操作符 | 含义 | 生活类比 | 示例场景 |
|---|---|---|---|
Exists |
标签存在即匹配 | “只要有身份证就能进入” | 匹配任意节点 |
DoesNotExist |
标签不存在才匹配 | “没有会员卡的人才能享受优惠” | 排除特定节点 |
In |
标签值在列表中 | “只允许 VIP1、VIP2 会员进入” | 指定特定节点 |
NotIn |
标签值不在列表中 | “禁止黑名单用户进入” | 排除某些节点 |
不同配置的效果对比
配置1:operator: Exists(匹配任意节点)
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: Exists # 只要节点有 hostname 标签就匹配
| 效果 | 说明 |
|---|---|
| ✅ node1 | 有 hostname 标签 → 可以使用 |
| ✅ node2 | 有 hostname 标签 → 可以使用 |
| ✅ node3 | 有 hostname 标签 → 可以使用 |
问题:Pod 可能被调度到任意节点,每个节点的数据是独立的,导致数据不一致!
配置2:operator: In(指定固定节点)
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In # 标签值必须在列表中
values:
- node1 # 只允许 node1
| 效果 | 说明 |
|---|---|
| ✅ node1 | hostname = node1 → 可以使用 |
| ❌ node2 | hostname ≠ node1 → 不可以使用 |
| ❌ node3 | hostname ≠ node1 → 不可以使用 |
好处:Pod 只能调度到 node1,数据始终一致!
工作流程图解
步骤1: Pod 申请使用 PVC
│
▼
步骤2: PVC 查找匹配的 PV
│
▼
步骤3: PV 检查 nodeAffinity
│
├─ nodeAffinity: operator=In, values=[node1]
│
▼
步骤4: Kubernetes 调度 Pod
│
├─ Pod 必须调度到 node1(否则无法绑定 PV)
│
▼
步骤5: Pod 在 node1 上运行,挂载 PV
│
▼
数据存储在 node1 的 /data/data-collection/mysql
实际案例
场景:MySQL 数据库部署
# PV 配置:数据存储在 node1
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
local:
path: /data/data-collection/mysql # node1 上的目录
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1 # 限定只能用 node1 的数据
效果:
- MySQL Pod 必须调度到 node1
- 数据读写都在 node1 的磁盘上
- 即使有 node2、node3,MySQL 也只在 node1 运行
- 数据始终一致,不会出现"每个节点数据不同"的问题
小白总结口诀
nodeAffinity 是住址要求,
告诉 Pod 哪里能找到数据。
operator=Exists → 任意节点都能用(数据可能不一致)
operator=In → 只有指定节点能用(数据始终一致)
本地存储必须配 nodeAffinity,
否则 Pod 调到别的节点,数据就找不到啦!
nodeAffinity 的 key 从哪里来?
答案:nodeAffinity 中的 key 就是节点 LABELS 里面的 key!
查看节点标签命令:
# 查看所有节点及其标签
kubectl get nodes --show-labels
# 查看特定节点的标签
kubectl describe node node1 | grep -A 30 "Labels"
# 格式化输出标签
kubectl get node node1 -o jsonpath='{.metadata.labels}' | jq .
实际输出示例:
NAME STATUS ROLES AGE VERSION LABELS
master1 Ready control-plane,etcd,master 7d3h v1.0.0 kubernetes.io/arch=amd64,kubernetes.io/hostname=master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=true,node-role.kubernetes.io/etcd=true,node-role.kubernetes.io/master=true
node1 Ready node 34h v1.0.0 kubernetes.io/arch=arm64,kubernetes.io/hostname=node1,kubernetes.io/os=linux,node-role.kubernetes.io/node=true
标签解析:
| 节点 | 标签 Key | 标签值 | 说明 |
|---|---|---|---|
| master1 | kubernetes.io/hostname |
master1 |
主机名 |
| master1 | kubernetes.io/arch |
amd64 |
CPU 架构 |
| master1 | kubernetes.io/os |
linux |
操作系统 |
| master1 | node-role.kubernetes.io/control-plane |
true |
控制面角色 |
| master1 | node-role.kubernetes.io/etcd |
true |
etcd 角色 |
| master1 | node-role.kubernetes.io/master |
true |
master 角色 |
| node1 | kubernetes.io/hostname |
node1 |
主机名 |
| node1 | kubernetes.io/arch |
arm64 |
CPU 架构 |
| node1 | kubernetes.io/os |
linux |
操作系统 |
| node1 | node-role.kubernetes.io/node |
true |
工作节点角色 |
Kubernetes 内置标签 Key 汇总
| 标签 Key | 含义 | 示例值 |
|---|---|---|
kubernetes.io/hostname |
节点主机名 | node1, master1 |
kubernetes.io/arch |
CPU 架构 | amd64, arm64 |
kubernetes.io/os |
操作系统 | linux, windows |
beta.kubernetes.io/arch |
CPU 架构(旧版) | amd64, arm64 |
beta.kubernetes.io/os |
操作系统(旧版) | linux |
node-role.kubernetes.io/control-plane |
控制面角色 | true |
node-role.kubernetes.io/master |
主节点角色 | true |
node-role.kubernetes.io/node |
工作节点角色 | true |
node-role.kubernetes.io/etcd |
etcd 角色 | true |
topology.kubernetes.io/zone |
可用区 | zone1, us-west-1a |
topology.kubernetes.io/region |
区域 | us-west-1 |
node.kubernetes.io/instance-type |
实例类型 | venus-lite |
常用 nodeAffinity 配置示例
1. 指定节点名
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
2. 指定 CPU 架构
matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- arm64
3. 指定节点角色(只在工作节点运行)
matchExpressions:
- key: node-role.kubernetes.io/node
operator: Exists
4. 指定操作系统
matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
5. 组合条件(AND 关系)
matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- arm64
- key: kubernetes.io/hostname
operator: In
values:
- node1
含义:必须同时满足 arch=arm64 且 hostname=node1
自定义节点标签
你也可以给节点打自定义标签:
# 添加自定义标签
kubectl label node node1 disk-type=ssd
kubectl label node node1 zone=zone1
# 查看标签
kubectl get node node1 --show-labels
# 删除标签
kubectl label node node1 disk-type-
在 nodeAffinity 中使用自定义标签:
matchExpressions:
- key: disk-type
operator: In
values:
- ssd
常见误解澄清:nodeAffinity 不是"创建 PV"
这是一个常见的误解!
误解:operator: Exists 会在所有匹配的节点上创建 PV。
正确理解:PV 只有一个,nodeAffinity 决定 Pod 能调度到哪个节点来使用这个 PV。
PV 创建时机
kubectl apply -f mysql-pv.yaml
执行这个命令时:
- PV 立即创建(在 Kubernetes 集群中,不是在节点上)
- PV 是一个集群级别的资源,不属于任何节点
nodeAffinity只是记录了"这个 PV 的数据在哪个节点"
nodeAffinity 的真正含义
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: Exists
含义:这个 PV 的数据存储在有 kubernetes.io/hostname 标签的节点上。
实际效果:
- master1 有这个标签 ✅ → PV 数据可能在 master1
- node1 有这个标签 ✅ → PV 数据可能在 node1
问题:operator: Exists 没有限定具体节点,Kubernetes 不知道数据到底在哪个节点!
生活类比
PV = 一个快递包裹
nodeAffinity = 包裹的收货地址
operator: Exists(错误配置)
─────────────────────────────
"包裹送到一个有门牌号的房子"
→ 有很多房子都有门牌号
→ 快递员不知道送到哪家!❌
operator: In, values: [node1](正确配置)
─────────────────────────────────────────
"包裹送到门牌号是 node1 的房子"
→ 只有 node1 符合条件
→ 快递员准确送到 node1 ✅
正确理解流程
PV 创建流程:
┌─────────────────────────────────────────────────────────────┐
│ PV 创建流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. kubectl apply -f mysql-pv.yaml │
│ ↓ │
│ 2. PV 在 Kubernetes 集群中创建(不在节点上) │
│ ↓ │
│ 3. PV 记录 nodeAffinity: kubernetes.io/hostname=Exists │
│ ↓ │
│ 4. PV 等待 PVC 来绑定 │
│ │
└─────────────────────────────────────────────────────────────┘
Pod 使用 PV 流程:
┌─────────────────────────────────────────────────────────────┐
│ Pod 使用 PV 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Pod 申请 PVC │
│ ↓ │
│ 2. PVC 绑定到 PV │
│ ↓ │
│ 3. Kubernetes 查看 PV 的 nodeAffinity │
│ ↓ │
│ 4. operator: Exists → 可以调度到任意有 hostname 的节点 │
│ operator: In [node1] → 只能调度到 node1 │
│ ↓ │
│ 5. Pod 在匹配的节点上运行,挂载本地存储 │
│ │
└─────────────────────────────────────────────────────────────┘
operator: Exists 的问题
operator: Exists # 问题:没有限定具体节点
后果:
- Pod 可能被调度到 master1
- 也可能被调度到 node1
- 每次调度到的节点可能不同
- 数据在不同节点上是独立的,导致数据不一致
正确配置示例
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1 # 明确指定节点
效果:
- Pod 只能调度到 node1
- 数据始终在 node1 的
/data/data-collection/mysql - 数据一致,不会混乱
误解 vs 正确理解
| 误解 | 正确理解 |
|---|---|
| nodeAffinity 决定在哪个节点创建 PV | PV 创建时就不属于任何节点,是集群资源 |
| operator: Exists 会在所有节点创建 PV | 只是指定 Pod 可以调度到哪些节点 |
| master1 和 node1 都会有这个 PV | PV 只有一个,数据只在一个节点的磁盘上 |
一句话总结:
nodeAffinity不是决定"PV 创建在哪里",而是决定"Pod 必须调度到哪个节点才能使用这个 PV"。
工作原理图
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ PVC │ ──────► │ PV │ │
│ │ mysql-pvc │ 绑定 │ mysql-pv │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ │ nodeAffinity │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Pod │ ──────► │ Node │ │
│ │ MySQL │ 调度 │ node1 │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ │ local.path │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 宿主机存储目录 │ │
│ │ /data/data- │ │
│ │ collection/mysql │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
使用流程
1. 在目标节点创建目录
mkdir -p /data/data-collection/mysql
2. 创建 PV
kubectl apply -f mysql-pv.yaml
3. 创建 PVC(由应用 YAML 定义)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: data-collection
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-storage
selector:
matchLabels:
component: mysql # 匹配 PV 的 label
4. Pod 挂载使用
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
template:
spec:
containers:
- name: mysql
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
注意事项
-
数据持久性
- 数据存储在宿主机指定目录
- Pod 删除后数据保留
- 节点故障会导致数据不可访问
-
节点绑定
- 本地存储 PV 只能被调度到指定节点
- 多节点集群需为每个节点创建独立 PV
-
容量管理
- PV 容量声明不限制实际写入
- 需监控宿主机磁盘空间
-
备份策略
- 本地存储无自动备份
- 需自行实现数据备份方案
常见问题:多节点数据不一致
问题描述
使用以下配置时:
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: Exists
operator: Exists 的含义:只要节点有 kubernetes.io/hostname 标签就匹配(几乎所有节点都有),所以:
- Pod 调度到 node1 → 数据存储在 node1 的
/data/data-collection/mysql - Pod 调度到 node2 → 数据存储在 node2 的
/data/data-collection/mysql
结果:每个节点上的 MySQL 数据是独立的、不共享的。如果 Pod 重新调度到不同节点,将读取到不同的数据!
解决方案
方案1: 指定固定节点(推荐单节点集群)
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1 # 固定调度到 node1
这样 MySQL 始终运行在 node1,数据始终一致。
方案2: 使用共享存储(推荐多节点集群)
| 存储类型 | 说明 |
|---|---|
| NFS | 网络文件系统,多节点共享 |
| Ceph | 分布式存储,支持 RWX |
| Longhorn | K8s 原生分布式存储 |
| GlusterFS | 分布式文件系统 |
NFS 示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany # 支持多节点读写
nfs:
server: 192.168.1.100
path: /data/mysql
方案3: 使用 Deployment + 副本数 1
通过 Deployment 设置 replicas: 1,同时配合 nodeAffinity 指定固定节点,确保只有一个 MySQL Pod 运行在指定节点。
方案选择建议
| 场景 | 推荐方案 |
|---|---|
| 单节点集群 | local-storage + operator: In 指定节点 |
| 多节点集群(数据共享) | NFS/Ceph/Longhorn 等共享存储 |
| 高可用 MySQL | 使用 StatefulSet + 主从复制 |
相关命令
# 查看 PV
kubectl get pv mysql-pv
# 查看 PV 详情
kubectl describe pv mysql-pv
# 查看 PVC 绑定状态
kubectl get pvc -n data-collection
# 查看节点标签
kubectl get nodes --show-labels
更多推荐




所有评论(0)