k8s的PV和PVC是什么东西?

Kubernetes PV 和 PVC 概念解析

在 Kubernetes(k8s)中,PV(PersistentVolume,持久化卷) 和 PVC(PersistentVolumeClaim,持久化卷声明) 是用于管理持久化存储的核心资源,目的是实现 存储资源与 Pod 解耦,让开发人员无需关心底层存储细节,专注于业务容器的部署。

一、核心定义

1. PersistentVolume(PV)—— 集群的 “存储资源池”

  • 本质:是 k8s 集群中预先创建的存储资源,由运维人员配置,属于集群级资源(不隶属于任何 Namespace)。
  • 作用:将底层存储(如本地磁盘、NFS、Ceph、云厂商的云盘等)抽象成 k8s 可管理的资源对象,提供标准化的存储接口。
  • 关键属性
    • 存储类型(storageClassName):用于和 PVC 绑定的核心标识,支持动态供应存储。
    • 容量(capacity):声明存储的大小,如 10Gi
    • 访问模式(accessModes):定义 PV 能被 Pod 以何种方式访问,核心模式有 3 种:
      • ReadWriteOnce(RWO):只能被单个节点以读写方式挂载。
      • ReadOnlyMany(ROX):能被多个节点以只读方式挂载。
      • ReadWriteMany(RWX):能被多个节点以读写方式挂载。
    • 回收策略(persistentVolumeReclaimPolicy):PVC 释放后 PV 的处理方式,包括 Retain(保留数据,手动清理)、Delete(删除底层存储)、Recycle(清除数据,复用 PV,已废弃)。

2. PersistentVolumeClaim(PVC)—— Pod 的 “存储申请单”

  • 本质:是 Pod 对存储资源的申请请求,由开发人员定义,属于命名空间级资源
  • 作用:Pod 通过 PVC 申请 PV 资源,PVC 会根据自身声明的条件(容量、访问模式、存储类)自动匹配集群中符合条件的 PV。
  • 关键属性
    • 资源请求(resources.requests.storage):申请的最小存储容量,如 5Gi
    • 访问模式(accessModes):必须与目标 PV 的访问模式匹配。
    • 存储类(storageClassName):指定要绑定的 PV 存储类,若为空则只能绑定无存储类的 PV。

二、核心关系与工作流程

PV 和 PVC 遵循 “供需匹配” 模型,工作流程分为 静态供应 和 动态供应 两种:

1. 静态供应(手动配置 PV)

  1. 运维人员提前创建 PV:将 NFS 目录、本地磁盘等封装成 PV 对象。
  2. 开发人员创建 PVC:声明需要的存储容量、访问模式、存储类。
  3. k8s 自动绑定:集群根据 PVC 的条件匹配符合要求的 PV,绑定成功后 PVC 状态变为 Bound
  4. Pod 挂载 PVC:在 Pod 的 volumes 字段中引用 PVC 名称,将存储挂载到容器内的指定路径。

2. 动态供应(自动创建 PV)

当集群中没有符合 PVC 条件的 PV 时,k8s 会通过 StorageClass 自动创建对应的 PV,无需运维人员手动配置,适合大规模集群场景。

三、核心价值

  1. 解耦存储与应用:运维负责底层存储管理,开发只需通过 PVC 申请存储,无需关注存储实现细节。
  2. 存储资源复用:PV 可以被多个 PVC 重复使用(取决于回收策略),提高存储利用率。
  3. 跨环境一致性:无论底层是物理存储还是云存储,通过 PV/PVC 抽象层,Pod 都能以相同的方式使用存储。

四、简单示例

1. 创建 NFS 类型的 PV

yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-storage  # 存储类名称
  nfs:
    path: /data/nfs  # NFS 服务器的共享目录
    server: 192.168.1.100  # NFS 服务器地址
  persistentVolumeReclaimPolicy: Retain

2. 创建 PVC 绑定该 PV

yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  namespace: default
spec:
  resources:
    requests:
      storage: 5Gi  # 申请容量小于 PV 的 10Gi
  accessModes:
    - ReadWriteMany
  storageClassName: nfs-storage  # 和 PV 的存储类一致

3. Pod 挂载 PVC

yaml

apiVersion: v1
kind: Pod
metadata:
  name: storage-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: nfs-volume
      mountPath: /usr/share/nginx/html  # 容器内挂载路径
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc  # 引用上面创建的 PVC

是否需要我帮你整理 PV/PVC 常见问题排查步骤,比如绑定失败、存储无法读写的排查方法?

StorageClass怎么知道在哪块存储空间上创建PV呢?毕竟巧妇难为无米之炊,StorageClass不可能凭空创造出PV吧?

你的这个疑问非常关键,StorageClass 并不是 “凭空造存储”,而是依赖于集群中预先配置的存储插件(Provisioner),通过插件对接底层的实际存储资源来动态创建 PV。

简单来说,StorageClass 是 “规则定义者”,Provisioner 是 “实际执行者”,底层存储是 “资源池”,三者缺一不可。

一、核心逻辑:StorageClass + Provisioner + 底层存储

1. 关键组件:Provisioner(存储供应器)

Provisioner 是实现动态供应 PV 的核心,它是一个知道如何与特定存储系统交互的程序(可以是 k8s 内置的,也可以是第三方提供的)。

StorageClass 本身不做任何存储创建操作,它只是通过 provisioner 字段指定由哪个 Provisioner 来干活,同时传递一些创建 PV 所需的参数。

常见的 Provisioner 分类:

  • k8s 内置 Provisioner:以 kubernetes.io/ 开头,比如
    • kubernetes.io/nfs:对接 NFS 存储
    • kubernetes.io/host-path:对接节点本地磁盘(仅用于测试,不建议生产)
    • kubernetes.io/aws-ebs:对接 AWS 云盘
    • kubernetes.io/azure-disk:对接 Azure 云盘
  • 第三方 Provisioner:由存储厂商提供,比如
    • ceph.com/cephfs:对接 Ceph 存储
    • rook.io/ceph-block:通过 Rook 管理 Ceph 块存储
    • portworx.com/portworx-volume:对接 Portworx 存储

2. StorageClass 的 “规则配置”:告诉 Provisioner 怎么创建 PV

StorageClass 通过两个核心部分定义创建 PV 的规则:

  • provisioner 字段:指定具体的执行者(Provisioner)。
  • parameters 字段:传递给 Provisioner 的参数,这些参数是底层存储需要的关键信息(比如 NFS 服务器地址、Ceph 集群的 MON 节点地址、云盘的类型等)。

没有这些参数,Provisioner 也不知道该去哪个存储上创建 PV—— 这就是你说的 “巧妇难为无米之炊” 的关键。

3. 底层存储:必须提前存在的 “资源池”

无论动态供应还是静态供应,底层的物理 / 虚拟存储资源必须提前准备好

  • 用 NFS Provisioner → 必须先搭建好 NFS 服务器,共享好目录;
  • 用 Ceph Provisioner → 必须先部署好 Ceph 集群,创建好存储池;
  • 用云厂商 Provisioner → 必须确保 k8s 集群有权限调用云厂商的 API(比如 AWS IAM 权限),云平台有可用的磁盘资源。

二、实操示例:以 NFS 动态供应为例

我们用 nfs-subdir-external-provisioner(一个常用的第三方 NFS Provisioner)来演示,你就能清楚看到 “米” 在哪里。

步骤 1:提前准备 “米”—— 部署 NFS 服务器

在集群外或某个节点上搭建 NFS,共享目录 /data/nfs-storage,确保 k8s 节点能访问这个 NFS 地址(比如 192.168.1.100)。

步骤 2:部署 Provisioner —— 让 k8s 能对接 NFS

在 k8s 中部署 nfs-subdir-external-provisioner,它会以 Pod 形式运行,并且知道如何访问我们的 NFS 服务器(部署时需要配置 NFS 地址和共享目录)。

步骤 3:创建 StorageClass —— 定义创建规则

yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-dynamic-sc  # StorageClass 名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner  # 指定 Provisioner
parameters:
  archiveOnDelete: "false"  # PV 删除时是否归档数据
  # 这些参数会传递给 Provisioner,结合 Provisioner 已配置的 NFS 地址,确定 PV 存储位置

这里的 Provisioner 已经提前知道 NFS 服务器的地址和共享目录(部署时配置的),StorageClass 只需要告诉它 “创建 PV 时要不要归档数据” 等规则。

步骤 4:创建 PVC 触发动态供应

当你创建一个引用 nfs-dynamic-sc 的 PVC 时:

  1. k8s 发现没有匹配的 PV,就去找 StorageClass 指定的 Provisioner;
  2. Provisioner 收到请求,根据自己已配置的 NFS 信息,在 NFS 共享目录 /data/nfs-storage 下创建一个子目录(比如 pvc-xxxxxx);
  3. Provisioner 用这个子目录创建一个 PV,然后将 PV 与 PVC 绑定;
  4. Pod 挂载 PVC 后,数据就会写入 NFS 服务器的这个子目录中。

三、核心结论

  1. StorageClass 不造存储,只定规则:它的作用是关联 Provisioner 和传递参数,本身不接触底层存储。
  2. Provisioner 是 “中介”:它必须提前知道底层存储的位置和访问方式(部署时配置),否则无法创建 PV。
  3. 底层存储是 “米”:必须提前存在且能被 Provisioner 访问,这是动态供应的前提 —— 没有底层存储,Provisioner 也无从下手。

是否需要我帮你整理一份 NFS 动态供应的完整部署清单,包括 Provisioner 部署、StorageClass 创建、PVC/Pod 验证的一步步操作?

那Provisioner是Pod的形式运行的话,声明Provisioner Pod和声明普通Pod有区别吗?如果没区别,给StorageClass指定Provisioner的时候,k8s又如何判断Provisioner Pod是不是真的Provisioner呢?比如说,我运行了一个nginx pod,然后我把这个nginx配置到storageClass的provisioner中,会怎样呢?

你提的这个问题直击核心,能看出你已经深入理解了动态供应的逻辑!简单来说:Provisioner Pod 和普通 Pod 本身的声明格式没有本质区别,但 k8s 识别 Provisioner 靠的是「身份注册 + 功能实现」,而非 Pod 本身;如果把 nginx Pod 填到 StorageClass 的 provisioner 字段里,动态供应会直接失败。

下面我分两部分把这个逻辑讲透:

一、Provisioner Pod 与普通 Pod 的异同

1. 声明格式:几乎无区别

从 YAML 语法上看,Provisioner Pod(比如 NFS Provisioner)和 nginx Pod 都遵循 k8s Pod 的核心规范:都有 apiVersion: v1kind: Pod,都包含 metadataspec.containers 等字段,甚至可以用相同的资源限制(cpu/memory)、镜像拉取策略等。

唯一的 “格式差异” 是 Provisioner Pod 通常会:

  • 挂载 k8s 的 API Server 证书(通过 ServiceAccount + Secret),用于向 k8s 注册自己的 Provisioner 身份,并监听 PVC 事件;
  • 配置与底层存储交互的环境变量(比如 NFS 服务器地址、Ceph 集群信息);
  • 运行具备 Provisioner 功能的镜像(而非普通业务镜像)。

举个对比例子:

普通 nginx Pod(简化版)

yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
NFS Provisioner Pod(简化版)

yaml

apiVersion: v1
kind: Pod
metadata:
  name: nfs-provisioner-pod
spec:
  serviceAccountName: nfs-provisioner-sa  # 有访问k8s API的权限
  containers:
  - name: nfs-provisioner
    image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
    env:
    - name: PROVISIONER_NAME  # 关键:声明自己的Provisioner名称
      value: "k8s-sigs.io/nfs-subdir-external-provisioner"
    - name: NFS_SERVER        # 底层存储信息
      value: "192.168.1.100"
    - name: NFS_PATH          # 底层存储路径
      value: "/data/nfs-storage"
    volumeMounts:
    - name: nfs-volume
      mountPath: /persistentvolumes
  volumes:
  - name: nfs-volume
    nfs:
      server: 192.168.1.100
      path: /data/nfs-storage

2. 核心区别:功能与身份(而非格式)

维度 普通 Pod(如 nginx) Provisioner Pod(如 NFS Provisioner)
核心功能 提供业务服务(如 HTTP 访问) 监听 k8s PVC 事件,创建 / 删除 PV
与 k8s API 交互 几乎无(仅被动被 k8s 管理) 主动调用 k8s API:注册 Provisioner、创建 PV、更新 PVC 状态
镜像能力 仅包含业务逻辑(如 nginx 服务) 内置 Provisioner 逻辑:识别 PVC 请求、对接底层存储、创建 PV 对象
身份标识 无特殊身份 通过 PROVISIONER_NAME 声明唯一标识,向 k8s 注册

二、k8s 如何识别 “真的 Provisioner”?

k8s 识别 Provisioner 靠的是两步验证,和 Pod 名称 / 类型无关:

1. 第一步:Provisioner 主动 “注册身份”

正常的 Provisioner Pod 启动后,会通过 k8s API 向集群的 CSI(容器存储接口) 或 内置存储控制器 注册自己的 “Provisioner 名称”(比如上面的 k8s-sigs.io/nfs-subdir-external-provisioner)。

这个过程相当于:Provisioner Pod 告诉 k8s:“我是处理 k8s-sigs.io/nfs-subdir-external-provisioner 这个标识的存储供应器,有 PVC 找这个标识的话,找我就行!”

2. 第二步:k8s 匹配 “标识 + 能力”

当你创建 StorageClass 并指定 provisioner: xxx 时,k8s 会做两件事:

  • 检查集群中是否有 Provisioner 注册了 xxx 这个名称;
  • 如果有,就将 PVC 动态供应的请求转发给这个 Provisioner;
  • 如果没有(比如填了 nginx Pod 相关的名称),则直接判定 “找不到 Provisioner”。

三、把 nginx Pod 填到 StorageClass 的 provisioner 字段里会怎样?

1. 直接结果:PVC 一直处于 Pending 状态

假设你创建这样的 StorageClass:

yaml

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fake-sc
provisioner: nginx-pod  # 填了nginx Pod的名称(或任意不存在的标识)

然后创建 PVC 引用这个 StorageClass:

yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: fake-pvc
spec:
  resources:
    requests:
      storage: 1Gi
  storageClassName: fake-sc

2. 查看 PVC 状态:明确提示 “找不到 Provisioner”

执行 kubectl describe pvc fake-pvc,会看到关键错误信息:

plaintext

Events:
  Type     Reason              Age               From                         Message
  ----     ------              ----              ----                         -------
  Warning  ProvisioningFailed  10s (x3 over 30s)  persistentvolume-controller  provisioner "nginx-pod" not found

3. 根本原因

  • nginx Pod 没有向 k8s 注册任何 Provisioner 标识,k8s 不知道 “nginx-pod” 这个标识对应的供应器;
  • 即使你强行把 Provisioner 名称改成 nginx Pod 里的某个环境变量,nginx 镜像也没有实现 Provisioner 的核心逻辑(监听 PVC、创建 PV、对接存储),无法处理供应请求。

简单说:k8s 找 Provisioner 是 “按标识找服务”,不是 “按 Pod 名称找容器”—— 就像你打电话找 “快递员”,得拨快递员的手机号(Provisioner 标识),而不是随便拨一个路人的手机号(nginx Pod),哪怕路人也有手机(也是 Pod),但他不会帮你送快递。

总结

  1. 格式无本质区别:Provisioner Pod 和普通 Pod 都是 k8s Pod 资源,声明语法一致;
  2. 识别靠 “身份 + 能力”:k8s 靠 Provisioner 注册的唯一标识(而非 Pod 本身)匹配 StorageClass,且只有实现了 Provisioner 逻辑的 Pod 才能处理存储供应请求;
  3. 错误配置的结果:把普通 Pod(如 nginx)的名称填到 StorageClass 的 provisioner 字段,会导致 PVC 一直 Pending,k8s 提示 “找不到该 Provisioner”。

如果需要,我可以给你一份可直接运行的 NFS Provisioner 完整部署清单,包含 ServiceAccount、Deployment、StorageClass、PVC/Pod 的全套 YAML,你可以亲手验证 “正确的 Provisioner 如何工作”。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐