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 请求 → 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

注意事项

  1. 数据持久性

    • 数据存储在宿主机指定目录
    • Pod 删除后数据保留
    • 节点故障会导致数据不可访问
  2. 节点绑定

    • 本地存储 PV 只能被调度到指定节点
    • 多节点集群需为每个节点创建独立 PV
  3. 容量管理

    • PV 容量声明不限制实际写入
    • 需监控宿主机磁盘空间
  4. 备份策略

    • 本地存储无自动备份
    • 需自行实现数据备份方案

常见问题:多节点数据不一致

问题描述

使用以下配置时:

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
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐