想象你是一家 AI 公司的“云原生管家”。算法团队要把模型训练、数据库都搬进 Kubernetes,办公室里最常响起的求助是:“我的数据放哪?Pod 一重启数据不就丢了吗?”这个问题的答案,就藏在存储三件套里。把存储世界想象成一栋写字楼:PV(PersistentVolume)是楼里真实存在的办公室,有门牌、有面积、有门禁;PVC(PersistentVolumeClaim)是员工填写的《办公位申请单》,只写需求不关心具体房源;StorageClass 则是物业的“快速装修队”,平时待命,收到申请单就按需施工交付。房源归物业管,申请单归开发者写,装修队只认单子干活——三者的联动,让“申请存储”像在线下单租房一样简单。搞懂这三件套,你就能在 K8s 上放心地跑数据库、跑训练任务,再也不用半夜爬起来救数据。

岗位关联

对 AI 从业者而言,存储三件套是让模型训练、数据库等有状态应用稳定落地的基石,掌握它意味着你真正具备了云原生生产环境的部署与排障能力。

目录
  1. 可视化演示
  2. 三件套认知
  3. PV存储资源
  4. PVC用户申请
  5. SC动态供应
  6. 动态供应流程
  7. 存储类型选择
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示PV/PVC/StorageClass三者关系、动态卷供应流程和存储类型对比,理解K8s持久化存储核心机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

PV/PVC/StorageClass架构关系 PV:集群级存储资源,由管理员创建或动态供应 PVC:用户级存储申请,声明容量和访问模式 PVC → PV (1:1 binding) 存储绑定关系

三件套认知

刚接手云原生管家这份工作时,最让我头疼的不是容器调度,而是同事们隔三差五来问:“我的数据放哪?”这个问题的答案,就藏在存储三件套里。想象一栋写字楼:PV 是一间间已装修好的办公室,有门牌、有面积、有门禁,真实可触碰;PVC 是员工填的《办公位申请单》,上面只写“我需要 20 平、朝南、可刷卡进入”;StorageClass 则是物业的快速装修队,平时待命,收到申请单就按需施工。房源归物业管,申请单归员工写,装修队只认单子干活——这就是 Kubernetes 存储世界的基本戏码。

把这套比喻映射到 Kubernetes,事情立刻清晰起来。PV(PersistentVolume)是集群层面的存储资源,像楼里真实存在的办公室,独立于任何 Pod 存在,Pod 崩了它还在;PVC(PersistentVolumeClaim)是命名空间里的申请单,声明需求,不关心存储来自哪块云盘;StorageClass 则定义了“装修队”的能力——由谁施工(provisioner)、用什么材料(parameters)、退租后房间怎么处置(reclaimPolicy)。三者各司其职,把“存储的提供”与“存储的申请”彻底解耦。

解耦带来的好处非常直接:业务开发者写 PVC 时根本不需要知道底层是 AWS EBS 还是 NFS,只需要写清楚容量和访问模式;平台管理员通过 PV 清单和 StorageClass 策略统一管控底层存储的规格、性能与成本。Kubernetes 会按照容量、访问模式、标签等条件,为每个 PVC 物色最合适的 PV。这个“物色”过程可以用一个匹配条件集合表示:

记住“房源、申请单、施工队”这个三角色,整个 K8s 存储体系就有了骨架。后面几节,我们会逐一细看每间办公室的房产证(PV 属性)、申请单的写法(PVC 规格)、施工队的施工手册(SC 参数),最后串起一条完整的动态供应流水线。

PVmatch={PVi∣cap(PVi)≥req(PVCj)∧mode(PVi)=mode(PVCj)}\displaystyle PV_{\text{match}} = \{ PV_i \mid \text{cap}(PV_i) \geq \text{req}(PVC_j) \wedge \text{mode}(PV_i) = \text{mode}(PVC_j) \}
候选 PV 集合 = 所有“容量够大、访问模式一致”的 PV;如果候选为空,则触发动态供应。
  1. 候选集合 PV_match把集群里所有 PV 拿出来逐个体检,能放进这个集合的,都是备选“办公室”。
    PVmatch={PVi∣… }PV_{\text{match}} = \{ PV_i \mid \dots \}
  2. 容量条件 cap 不小于 req办公室面积必须不小于申请单上写的面积,就像租 20 平的办公室,不能给个 10 平的仓库。
    cap(PVi)≥req(PVCj)\text{cap}(PV_i) \geq \text{req}(PVC_j)
  3. 模式匹配 mode 相等门禁规则必须一致,申请“可多人进入”的讨论室,就不能绑定“只允许一个人进”的独立工位。
    mode(PVi)=mode(PVCj)\text{mode}(PV_i) = \text{mode}(PVC_j)

PV存储资源

继续逛这栋写字楼。作为物业管家,我手里的每间 PV 办公室都有一张“房产证”,上面写满了关键信息:面积(capacity)、门禁规则(accessModes)、退租条款(reclaimPolicy)、以及这间房由哪家施工队承建(provisioner)。这些信息决定了一间办公室能被谁申请、用完之后怎么处理。

PV 的一生像房产的生命周期:建好待租(Available)→ 被租客签下(Bound)→ 租客退租(Released)→ 房主收回自管或拆除重建(Retain 或 Delete)。管理员手动登记一批房源,这种提前备好现房的方式叫静态供应;等申请单来了再让施工队盖新房,则属于动态供应。无论哪种方式,PV 的生命都独立于 Pod——某个容器崩溃了,办公室还好好立在楼里,数据安然无恙。

PV 的访问模式(accessModes)是门禁规则的核心,就像写字楼门口张贴的准入说明:

看懂了房产证,你就知道选 PV 不是随手一指:MySQL 主库要 RWO 高性能块存储,多副本共享配置要 RWX 文件存储。存储底层类型分三大家族——块存储(云盘)、文件存储(NFS)、对象存储(S3),最后一节会细讲。物业管家的工作,就是把这些房源登记清楚,等待 PVC 上门。

AccessMode(PV)∈{RWO,ROX,RWX}\displaystyle \text{AccessMode}(PV) \in \{ \text{RWO}, \text{ROX}, \text{RWX} \}
每个 PV 支持一种或多种访问模式,PVC 申请时必须声明一种,双方必须对得上号。
  1. RWO 单节点读写像一间只有一把钥匙的独立办公室,同一时间只有一人能进入办公。
    RWO:single node read/write\text{RWO}: \text{single node read/write}
  2. ROX 多节点只读像公共展厅,谁都能进去看,但没人能改动里面的展品。
    ROX:many nodes read only\text{ROX}: \text{many nodes read only}
  3. RWX 多节点读写像开放讨论区,整个团队可以同时进来头脑风暴、修改白板。
    RWX:many nodes read/write\text{RWX}: \text{many nodes read/write}

PVC用户申请

员工填申请单时,根本不需要关心 2 号楼 3 层还有没有空房,他只需要把需求写清楚。PVC 的 YAML 就是这张标准申请单:要多大面积(resources.requests.storage)、需要什么门禁(accessModes)、有没有指定楼盘(storageClassName)、有没有额外偏好(selector.matchLabels)。这张单子提交之后,Kubernetes 的“配对系统”就开始工作了。

配对系统会遍历所有 Available 状态的 PV,按三条硬性标准筛选:容量不小于申请、访问模式一致、如果 PVC 指定了 storageClassName,PV 也必须属于同一个类。若 PVC 还写了标签选择器,PV 的标签必须完全覆盖这些选择项。配对成功后,PVC 与 PV 建立一一对应的绑定关系,就像租客和房东正式签了合同:

谈到配额,申请单上不只是容量一个数字。除 storage 容量外,部分 CSI 驱动还支持声明 IOPS、吞吐量等性能配额;但最核心、最通用的仍然是容量。如果集群里既没有满足条件的 PV,PVC 也没指定可用的 StorageClass,申请单就会一直停在 Pending 状态,像排队等物业回复。这也是排查存储问题最常见的起点:先看 PVC 卡在什么状态,再顺藤摸瓜。

把 PVC 看成“需求声明而非资源本身”,很多困惑迎刃而解:创建 PVC 不等于拥有存储,只有等到 Bound 状态合同才生效,Pod 才能拿着合同去访问真正的办公室。

PVCj⇝PVi  ⟺  labels(PVi)⊇selector(PVCj)∧cap(PVi)≥req(PVCj)\displaystyle PVC_j \rightsquigarrow PV_i \iff \text{labels}(PV_i) \supseteq \text{selector}(PVC_j) \wedge \text{cap}(PV_i) \geq \text{req}(PVC_j)
绑定条件是“不等式”——标签与容量都要满足 PVC 的要求;合同一签,一个 PV 就只能属于一个 PVC。
  1. 绑定关系 PVC 到 PV一个 PVC 最终只对应一个 PV,签约之后这个 PV 不再接受其他申请。
    PVCj⇝PViPVC_j \rightsquigarrow PV_i
  2. 标签覆盖 labels 包含 selectorPV 的标签必须包含 PVC 指定的所有键值对,像 HR 说“要南向房间”,候选办公室标签里就必须有 south=true。
    labels(PVi)⊇selector(PVCj)\text{labels}(PV_i) \supseteq \text{selector}(PVC_j)
  3. 容量覆盖 cap 不小于 reqPV 可用容量指标必须不小于申请容量,防止“面积不够还硬塞”。
    cap(PVi)≥req(PVCj)\text{cap}(PV_i) \geq \text{req}(PVC_j)

SC动态供应

如果说 PV 是现房、PVC 是申请单,那 StorageClass 就是整个故事里最有“魔法”的角色——施工队。它不需要预先把房源备好,而是收到申请单后立刻开工:用云厂商的 API 创建一块云盘(provisioner 干活)、按参数选材料(SSD 还是 HDD)、贴好门牌(生成 PV 对象),最后把钥匙交给租客。这就是 K8s 口中的“动态卷供应”。

施工队的施工手册由三个关键字段组成。provisioner 是施工资质,声明谁来干活——AWS 的 ebs.csi.aws.com、NFS 的 nfs.csi.k8s.io、Ceph 的 rook-ceph 等;parameters 是材料清单,例如 type 指定磁盘类型、fsType 指定文件系统格式;reclaimPolicy 是退租条款,Delete 表示退房即拆房(同时删除底层存储),Retain 表示退房后保留房间等待人工处置。施工队收到申请后,按手册产出一个严格对齐的 PV:

除了这三个核心字段,还有两个常用开关。allowVolumeExpansion: true 允许日后给卷扩容,相当于租约里写了“可申请扩建”;mountOptions 可以附加挂载参数,比如 NFS 的 hard/soft 模式。生产环境通常按业务分级定义多套 SC:fast-ssd 给数据库、shared-nfs 给共享文件、archive-s3 给归档——开发者在申请单上写不同类名,就能自动获得不同性能与价格的存储。

施工队是 K8s 存储里“弹性”的来源。没有它,每次扩容都要人工创建云盘、手动登记 PV;有了它,“申请即交付”变成现实。下一节我们就把整条流水线完整走一遍。

PVCj[class=C]→Provisioner(C)PVi:cap(PVi)=req(PVCj), mode(PVi)=mode(PVCj)\displaystyle PVC_j[\text{class}=C] \xrightarrow{\text{Provisioner}(C)} PV_i : \text{cap}(PV_i) = \text{req}(PVC_j),\, \text{mode}(PV_i) = \text{mode}(PVC_j)
动态供应的 PV 不是“凑合匹配”,而是严格按 PVC 的申请量定制——申请 100Gi 就建 100Gi。
  1. 指定存储类 class=CPVC 声明“我要 C 施工队来盖房”,相当于申请单上填写指定承建商。
    PVCj[class=C]PVC_j[\text{class}=C]
  2. 施工队开工 Provisioner(C)StorageClass 的 provisioner 调用底层存储后端接口,真正创建存储资源。
    →Provisioner(C)\xrightarrow{\text{Provisioner}(C)}
  3. 产物对齐 cap 等于 req生成的 PV 容量与 PVC 申请容量严格相等,模式也保持一致,这就是“按需定制”。
    cap(PVi)=req(PVCj), mode(PVi)=mode(PVCj)\text{cap}(PV_i) = \text{req}(PVC_j),\, \text{mode}(PV_i) = \text{mode}(PVC_j)

动态供应流程

现在,把“在线租房”全流程完整播放一遍。假设算法团队要跑一个需要 100Gi 数据的模型训练任务,他们写的申请单长这样:

这张申请单提交后,物业系统(Kubernetes 控制平面)开始自动处理,五个状态一气呵成:

这条流水线的价值在于“全自动”。训练任务结束后删除 Pod 和 PVC,如果 reclaimPolicy 是 Delete,云盘自动销毁,不再产生费用;下一次训练再来一张新申请单,新云盘即刻到位。算力与存储的配对完全按需进行,这正是云原生弹性的精髓。

整条链路像无人便利店:扫码下单(PVC)、后仓自动补货(SC 动态供应)、取货走人(Pod 挂载)、垃圾自动回收(Delete)。理解了这五个状态转换,你就能读懂集群里任何一条存储报错日志了。

Pending→SC matchProvisioning→PV createdBound→MountedInUse\displaystyle \text{Pending} \xrightarrow{\text{SC match}} \text{Provisioning} \xrightarrow{\text{PV created}} \text{Bound} \xrightarrow{\text{Mounted}} \text{InUse}
PVC 的状态机像订单物流:下单(Pending)→ 施工(Provisioning)→ 签收(Bound)→ 使用中(InUse)。
  1. 进入 PendingPVC 刚创建时等待匹配,K8s 扫描所有 SC,找到 storageClassName 指向的 fast-ssd。
    PVC:Pending\text{PVC}: \text{Pending}
  2. 触发 Provisioningfast-ssd 的 provisioner 被调用,通过云厂商 API 创建一块 100Gi 的 SSD 云盘。
    →SC matchProvisioning\xrightarrow{\text{SC match}} \text{Provisioning}
  3. 生成并绑定 PV系统根据云盘信息生成 PV 对象,容量恰好 100Gi,PVC 与其绑定,状态变为 Bound。
    PV:Bound\text{PV}: \text{Bound}
  4. Pod 挂载使用训练 Pod 的 volumes 引用 PVC,容器内出现挂载目录,状态变为 InUse。
    Pod→PVC→PV→Disk\text{Pod} \to \text{PVC} \to \text{PV} \to \text{Disk}
yaml10 行
apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: model-dataspec:  accessModes: [ReadWriteOnce]  storageClassName: fast-ssd  resources:    requests:      storage: 100Gi

存储类型选择

最后,作为云原生管家,你得知道给不同住户推荐哪种房型。存储世界有三大房型:块存储是独立办公室,性能天花板高;文件存储是开放讨论区,适合多人协作;对象存储是云端档案库,容量几乎无限、价格便宜。选错房型,轻则性能浪费,重则读写报错。

块存储(EBS、云盘)以 RWO 模式供给数据库、Redis 这类“单机强一致”服务,延迟低、IOPS 高;文件存储(NFS、CephFS)支持 RWX 多节点共享,适合多副本读写同一份配置、聚合日志;对象存储(S3、OSS)容量近无限、成本极低,适合模型 checkpoint 与训练数据的长期归档,通过 CSI 驱动也能以卷形式挂载。选型逻辑可以浓缩成一张决策映射表:

在有状态应用(StatefulSet)场景,K8s 还提供了锦上添花的组合拳:volumeClaimTemplates。它像一个“批量申请单模板”,StatefulSet 每创建一个副本,就自动生成一张 PVC,再通过 StorageClass 为每个副本动态分配独立卷。比如部署一个三节点的向量数据库,三个副本各自获得一块独立云盘,数据互不踩踏,扩容缩容都不用手工建卷。

把三大房型记在心里,面对任何存储需求你都能脱口而出方案:要性能找块存储,要共享找文件存储,要省钱找对象存储。配合三件套的自动化机制,云原生存储这场“租房大戏”,你已经可以当导演了。

DB/Redis⇒Block,SharedFS⇒File,Archive/Backup⇒Object\displaystyle \text{DB}/\text{Redis} \Rightarrow \text{Block}, \quad \text{SharedFS} \Rightarrow \text{File}, \quad \text{Archive}/\text{Backup} \Rightarrow \text{Object}
工作负载类型与存储家族一一对应:要性能选块、要共享选文件、要容量选对象。
  1. 数据库映射到块存储MySQL、Redis 需要低延迟随机读写,块存储能在单节点独占时提供稳定性能。
    DB/Redis⇒Block\text{DB}/\text{Redis} \Rightarrow \text{Block}
  2. 共享文件映射到文件存储多个 Pod 要同时读写的配置与日志,用 NFS 这类文件存储实现 RWX。
    SharedFS⇒File\text{SharedFS} \Rightarrow \text{File}
  3. 归档映射到对象存储海量 checkpoint 与备份,对象存储在容量与成本上碾压前两者。
    Archive/Backup⇒Object\text{Archive}/\text{Backup} \Rightarrow \text{Object}

AI 应用场景

AI 模型训练数据持久化

阶段:数据准备与模型训练

解决的问题:传统做法是训练前人工去云控制台创建云盘、记录 volume ID、写死到 Pod 的 volumes 里;任务结束云盘要么闲置浪费钱,要么误删丢数据。用 StorageClass 动态供应后,训练任务只需用 PVC 声明“我要 500Gi”,后端自动建盘;任务结束删除 PVC,Delete 策略自动释放资源。

具体案例:某 CV 团队每天在 GPU 集群上跑 12 个训练任务,每个任务需要 300GB 数据集。接入 StorageClass 后,训练 YAML 从 40 行缩到 15 行,云盘成本下降约 60%——因为不再有“忘记删除的僵尸云盘”。

数据库等有状态应用的持久化

阶段:模型服务与在线推理

解决的问题:推荐系统需要 MySQL 保存用户画像,Pod 重启数据不能丢。传统方式是手工维护云盘与 Pod 的绑定关系,迁移环境要改一堆配置。用 PV/PVC/SC 解耦后,开发只需在配置里写 storageClassName,底层存储随时切换。

具体案例:某电商推荐平台用 StatefulSet 部署 MySQL 主库,volumeClaimTemplates 自动为每个实例创建 PVC,storageClassName 指向 SSD 块存储;从库通过 NFS 文件存储做只读副本共享。数据库升级、节点迁移都不再触碰存储细节。

大规模 checkpoint 与日志归档

阶段:训练监控与模型归档

解决的问题:LLM 训练每几小时产出 checkpoint,动辄上百 GB;采集的日志需要长期留存。块存储成本太高,对象存储更适合冷数据。通过对象存储 CSI 驱动定义 archive StorageClass,训练任务照常用 PVC 申请容量,底层数据实际落到 S3,容量无限、按量付费。

具体案例:某大模型团队将每轮训练产生的 2TB checkpoint 写入对象存储归档卷,结合回收策略保留最近 5 份、更早的自动清理,半年存储成本仅为块存储方案的八分之一。

Python 演示

python92 行
class Cluster:    def __init__(self):        self.pvs = []        self.pvcs = []        self.scs = {}     def add_storage_class(self, sc):        self.scs[sc.name] = sc     def add_pv(self, pv):        self.pvs.append(pv)     def create_pvc(self, name, request_gb, mode, sc_name=None):        pvc = PersistentVolumeClaim(name, request_gb, mode, sc_name)        self.pvcs.append(pvc)        self.reconcile(pvc)        return pvc     def reconcile(self, pvc):        # 模拟 Kubernetes 控制平面的 PVC 调度逻辑        for pv in self.pvs:            if pv.can_bind(pvc):                self.bind(pv, pvc)                return        if pvc.sc_name in self.scs:            pv = self.scs[pvc.sc_name].provision(pvc.request_gb, pvc.mode)            self.pvs.append(pv)            self.bind(pv, pvc)        else:            print('PVC %s 保持 Pending, 没有匹配 PV 也没有 StorageClass' % pvc.name)     def bind(self, pv, pvc):        pv.bound_to = pvc.name        pvc.bound_pv = pv.name        pvc.state = 'Bound'        print('PVC %s 绑定 PV %s, 状态: %s' % (pvc.name, pv.name, pvc.state))  class PersistentVolume:    def __init__(self, name, capacity_gb, mode, sc_name=None, reclaim='Delete'):        self.name = name        self.capacity_gb = capacity_gb        self.mode = mode        self.sc_name = sc_name        self.reclaim = reclaim        self.bound_to = None     def can_bind(self, pvc):        if self.bound_to is not None:            return False        if pvc.sc_name is not None and self.sc_name != pvc.sc_name:            return False        return self.capacity_gb >= pvc.request_gb and self.mode == pvc.mode  class PersistentVolumeClaim:    def __init__(self, name, request_gb, mode, sc_name=None):        self.name = name        self.request_gb = request_gb        self.mode = mode        self.sc_name = sc_name        self.state = 'Pending'        self.bound_pv = None  class StorageClass:    def __init__(self, name, provisioner, reclaim='Delete'):        self.name = name        self.provisioner = provisioner        self.reclaim = reclaim     def provision(self, size_gb, mode):        pv = PersistentVolume(            name=self.name + '-' + str(size_gb) + 'g',            capacity_gb=size_gb,            mode=mode,            sc_name=self.name,            reclaim=self.reclaim,        )        print('StorageClass %s 通过 %s 动态创建 PV %s' % (self.name, self.provisioner, pv.name))        return pv  if __name__ == '__main__':    cluster = Cluster()    cluster.add_storage_class(StorageClass('fast-ssd', 'ebs.csi.aws.com'))    cluster.add_storage_class(StorageClass('shared-nfs', 'nfs.csi.k8s.io'))    cluster.add_pv(PersistentVolume('pv-static-1', 50, 'ReadWriteOnce', sc_name=None, reclaim='Retain'))     cluster.create_pvc('mysql-data', 20, 'ReadWriteOnce')    cluster.create_pvc('model-best', 100, 'ReadWriteOnce', sc_name='fast-ssd')    cluster.create_pvc('shared-logs', 200, 'ReadWriteMany', sc_name='shared-nfs')
目的:用纯 Python 模拟 Kubernetes 控制平面的存储调度逻辑,演示“静态绑定 + 动态供应”两条路径,跑一遍就能直观看到 PVC 如何从 Pending 走向 Bound。
  1. def can_bind(self, pvc):PV 的“体检函数”,检查自己是否还能被绑定、容量与模式是否满足。
  2. if pvc.sc_name in self.scs:没有现成房源时,看申请单是否指定了施工队,有就触发动态供应。
  3. pv = self.scs[pvc.sc_name].provision(pvc.request_gb, pvc.mode)StorageClass 按申请容量自动创建 PV,这就是“按需施工”。
  4. pv.bound_to = pvc.name在 PV 上标记“已被某租客签下”,此后不再接受其他 PVC。
  5. pvc.state = 'Bound'PVC 状态流转为已绑定,合同正式生效。
  6. cluster.create_pvc('model-best', 100, 'ReadWriteOnce', sc_name='fast-ssd')实战中就是这一行声明,触发整个动态供应流水线。
  7. cluster.add_pv(PersistentVolume('pv-static-1', 50, 'ReadWriteOnce', sc_name=None, reclaim='Retain'))管理员预置静态 PV,供不指定 SC 的申请使用。

常见误区

误区我创建了一个 PVC,是不是就等于申请到一块硬盘了?直接在 Pod 里挂载它就行。

正解PVC 只是一张“需求申请单”,不是存储本身。它要经过匹配静态 PV 或触发动态供应后,状态变成 Bound,Pod 才能真正读写数据。就像租房申请单不是房子,得等房东或施工队交付钥匙。

为何常见:使用 Pod 时只在 YAML 里写了 PVC 的名字,申请与绑定过程被 K8s 自动化遮蔽,新手容易把“申请”当成“拥有”。

误区StorageClass 就是给动态供应用的,只要集群里有 SC,所有 PVC 都走动态创建,手动创建的 PV 没用了。

正解两种供应方式并存:指定 storageClassName 的 PVC 会优先找同类静态 PV,找不到才动态创建;不指定 SC 的 PVC 只匹配静态 PV(除非集群配置了默认 SC)。像租房,有现房先住房,没现房才叫施工队。

为何常见:云厂商默认 SC 无处不在,大量教程只演示动态供应,导致静态供应这条路径被忽视。

误区PV 的回收策略选 Delete,删掉 PVC 后只是移除了 PV 这个配置对象,底层数据都还好好待着。

正解Delete 会连同底层存储实例(云盘、卷)一起删除,数据不可恢复;Retain 才保留底层数据供人工处置。选 Delete 前必须确认数据可再生成,否则就是事故。

为何常见:“删除 PV”和“删除底层存储”是两个动作,YAML 里只看到一个对象名,容易下意识以为删的只是配置。

误区ReadWriteOnce 就是这块卷同时只能被一个 Pod 挂载,多副本应用只能选 RWX。

正解RWO 的完整语义是“单个节点”(single node)读写,即同一时间只能被一个节点挂载;同一节点上的多个 Pod 往往可以共享该卷。严谨起见需查 CSI 驱动文档,但按“节点”而非"Pod"理解更接近本质。

为何常见:字面翻译“Once“让新手以为是”一次使用”,忽略了访问模式的真正粒度是节点。

误区StorageClass 创建的 PV 容量是写死的,申请多大就永远多大,数据多了只能重建。

正解如果 SC 配置了 allowVolumeExpansion: true 且底层驱动支持,修改 PVC 的 storage 字段即可在线扩容。就像租约里写了“可扩建”,申请扩建后施工队来加面积。

为何常见:默认创建的 SC 常常没开这个开关,而且很多教程不主动演示扩容,导致“不能改”的印象根深蒂固。

面试题

Q1. PV 与 PVC 的关系最像以下哪组生活角色?

单选题 · easy

  • A.实际房源和租房申请单← 正确答案
  • B.烹饪原料和厨师菜谱
  • C.汽车引擎和司机驾照
  • D.电影剧本和演员台词

答案:A。PV 是真实存在的存储资源(房源),PVC 是用户的需求声明(申请单);K8s 按容量与访问模式为 PVC 匹配 PV,就像房东与租客签约,而不是原料、工具或剧本的关系。

Q2. PVC 创建后长时间处于 Pending,最常见的原因是?

单选题 · easy

  • A.无匹配 PV 且无可用 StorageClass← 正确答案
  • B.Pod 尚未启动且没有创建容器
  • C.存储容量超过集群总内存上限
  • D.工作节点数量不足三个节点

答案:A。PVC 的 Pending 状态表示“申请单没人接”:要么没有满足容量/模式的现房,要么指定的施工队(SC)不存在。Pod 是否启动、节点数量与 PVC 绑定没有直接关系。

Q3. StorageClass 最核心的作用是?

单选题 · easy

  • A.按需动态创建持久化存储卷← 正确答案
  • B.定时备份所有 Pod 的数据
  • C.限制 Pod 使用 CPU 配额
  • D.自动扩缩容集群工作节点

答案:A。StorageClass 的核心是“动态卷供应”:PVC 指定类名后,其 provisioner 自动调用存储后端创建 PV。备份、CPU 配额、节点扩缩容都属于其他组件的能力。

Q4. 多个工作节点上的 Pod 需要同时读写同一份文件,可行的方案有?

多选题 · mid

  • A.选择 ReadWriteMany 访问模式的共享卷← 正确答案
  • B.使用 NFS 这类文件存储提供共享挂载← 正确答案
  • C.多个节点同时挂载 ReadWriteOnce 卷
  • D.让 PVC 同时绑定多个 PV 自动切换

答案:AB。RWX 与文件存储天然支持多节点共享读写;RWO 只允许单节点挂载;PVC 与 PV 是一一绑定关系,不可能通过“多绑”实现跨节点共享。

Q5. 管理员创建 PV 时设置 reclaimPolicy: Retain。PVC 被删除后,该 PV 会?

单选题 · mid

  • A.立即回到 Available 等待新租户
  • B.进入 Released 并保留底层数据← 正确答案
  • C.自动删除底层存储并释放空间
  • D.继续与已删除 PVC 保持绑定

答案:B。Retain 策略下,PVC 删除后 PV 进入 Released 状态,底层存储保留,等待管理员人工处置;它不会自动回到 Available,也不会删除数据,更不会与已删除的 PVC 保持绑定。

Q6. 动态卷供应中 provisioner 的角色可以理解为?

单选题 · mid

  • A.按需求施工并交付新房的施工队← 正确答案
  • B.审核租客资质与信用的中介
  • C.登记房源信息的物业前台
  • D.定期收取房租的财务专员

答案:A。provisioner 是 StorageClass 中负责“真正创建底层存储”的插件,对应租房故事里按申请单施工、交付新房的施工队;审核资质、登记信息、收费都不是它的职责。

Q7. PVC 指定 storageClassName: fast-ssd,但集群只有未关联该类的静态 PV。会怎样?

单选题 · hard

  • A.PVC 依然绑定普通 PV 正常使用
  • B.由 fast-ssd 动态创建 PV 完成绑定← 正确答案
  • C.PVC 绑定集群中任意可用 PV
  • D.PVC 直接失败且无法重新调度

答案:B。指定 SC 的 PVC 只与相同 SC 的 PV 匹配;若静态 PV 都不符合,系统会交给 fast-ssd 动态创建 PV 完成绑定。普通 PV 未关联 fast-ssd,不会参与匹配。

Q8. 关于三类存储的选择,下列说法正确的是?

单选题 · hard

  • A.数据库应首选对象存储以获得最大吞吐
  • B.MySQL 通常选择块存储以获得低延迟← 正确答案
  • C.文件存储只能被单个节点独占挂载
  • D.对象存储适合频繁修改的小文件场景

答案:B。数据库需要低延迟随机读写,块存储(云盘)是标准选择;文件存储支持多节点共享;对象存储适合海量归档与冷数据,频繁更新小文件并非其优势。

Q9. 关于 PV 与 PVC 的绑定规则,正确的有?

多选题 · mid

  • A.PV 容量必须不小于 PVC 申请容量← 正确答案
  • B.PV 访问模式必须与 PVC 声明一致← 正确答案
  • C.一个 PVC 可同时绑定多个 PV
  • D.指定 SC 的 PVC 只匹配同类 PV← 正确答案

答案:ABD。绑定是“一一对应”关系:一个 PVC 只能绑定一个 PV。容量、访问模式、存储类三者都匹配才会绑定成功;C 把映射关系完全颠倒了。

Q10. StorageClass 配置 reclaimPolicy: Delete 的影响包括?

多选题 · extreme

  • A.删除 PVC 后对应 PV 对象被删除← 正确答案
  • B.删除 PVC 后底层云盘也被删除← 正确答案
  • C.存在较高的数据丢失风险← 正确答案
  • D.删除 PVC 后 PV 回到 Available

答案:ABC。Delete 表示“退租即拆房”:PVC 删除后,PV 对象与底层存储(云盘)一并删除,数据不可恢复,数据丢失风险高;D 描述的是 Retain 之后管理员手动回收的行为,与 Delete 相悖。