Li Sheng | Backend / Distributed Storage Engineer Li Sheng | Backend / Distributed Storage Engineer
Home
Resume
Projects
Topics
Notes
GitHub (opens new window)
Home
Resume
Projects
Topics
Notes
GitHub (opens new window)
  • Go语言

  • C++

  • 算法题

  • 存储系统

  • CephFS

  • 分布式系统

  • 计算机网络

  • Redis与缓存

  • Kubernetes

    • 存储

      • 待完成专题池
      • 为什么 Kubernetes 要把存储抽象拆成 PV、PVC、StorageClass 与 CSI
      • 一个 PVC 从创建到 Pod 成功挂载,中间经过了哪些控制链路
        • 进一步讨论
      • 为什么 PVC 已经 Bound,Pod 仍然可能挂载失败
      • 本地盘、网络盘与拓扑约束,是如何共同影响 Kubernetes 调度的
      • Kubernetes 存储故障为什么常表现为应用启动超时
      • 为什么 Kubernetes CSI 插件架构要拆成 Controller、Node 与 Sidecar
  • 技术笔记
  • Kubernetes
  • 存储
lisheng
2026-04-28
目录

一个 PVC 从创建到 Pod 成功挂载,中间经过了哪些控制链路

# 一个 PVC 从创建到 Pod 成功挂载,中间经过了哪些控制链路

这篇文章的核心问题不是“PVC 是什么”,而是顺着真实链路回答:从用户提交一个 PVC 开始,到某个节点上的 Pod 最终拿到可用挂载点,中间究竟经过了哪些控制器、调度决策和节点侧动作。

如果只看 YAML,很多人会以为这条链路只有两步:先创建 PVC,再让 Pod 引用它。但在 Kubernetes 里,PVC 到可用挂载点之间并不是一次同步调用,而是一条跨越 API 对象、控制器循环、调度器决策和节点侧插件调用的异步控制链。只有把这些阶段按顺序拉开,很多常见误解才会自动消失,比如为什么 PVC 已经 Bound 了,Pod 还没起来;为什么控制平面对象看起来都正常,节点上却还在 ContainerCreating;又为什么同样是“挂载一块卷”,网络盘和本地盘对调度时机的影响会完全不同。

这条链路真正的起点不是 Pod,而是存储需求先被显式提交为 PVC。当用户创建 PVC 时,API Server 只是接受了一份“我要某种卷”的声明,系统此刻还没有承诺卷已经存在,更没有承诺某个节点已经能访问它。紧接着介入的通常是负责绑定和供给的控制器循环。它首先会判断这份声明能否匹配到已有 PV;如果不能,而 StorageClass 又允许动态供给,就会转而驱动外部供给器或 CSI 控制器去创建卷。也就是说,在链路最前面,系统先解决的是“有没有合适的卷可供使用”,而不是“卷已经挂到谁身上”。这也是为什么 PVC 早期最常见的状态是 Pending,因为它描述的是资源声明正在等待被满足,而不是挂载动作正在进行。

一旦系统找到了现成卷,或者后端已经动态创建出新卷,PVC 和 PV 就会建立绑定关系。很多人会把这一刻理解成“存储已经准备好了”,其实这只是链路中的第一个重要收束点。Bound 只说明需求侧和资源侧的对应关系已经确认,控制平面已经知道“这份声明最终对应哪块卷”。它并不说明这块卷已经完成节点侧附加,不说明它一定能被调度到合适节点,也不说明文件系统已经在目标节点上准备就绪。换句话说,绑定解决的是“卷归属谁”,不是“卷已经被谁用起来”。

接下来链路才会真正和 Pod 调度纠缠在一起。对于没有明显拓扑约束的场景,这一步看起来很平滑,因为调度器只要看到 Pod 引用了一个可用 PVC,就可以继续按 CPU、内存和亲和规则挑选节点。但只要卷与可用区、节点、机架或者本地设备位置绑定,这个顺序就会变得敏感。某些 StorageClass 会把卷绑定延后到真正有消费者时才做,目的就是避免过早创建出一块拓扑上根本无法被目标 Pod 使用的卷。这意味着调度器在某些场景下并不是在“卷准备好之后”才出现,而是在卷选择和节点选择之间承担协调角色。也正因为如此,存储链路的中段开始同时受到存储拓扑和工作负载调度的共同约束。

当 Pod 最终被调度到某个节点后,控制平面还要继续把“卷应该在这个节点上可用”这件事推进下去。对于需要附加设备的块存储,下一步通常是控制器侧的 Attach 阶段。此时介入的是卷附加相关控制器以及 CSI 控制器插件,它们会把“这块卷要接到这台节点”翻译成后端存储系统可理解的动作。这里发生的仍然主要是控制平面到后端控制面的协调,而不是节点上已经完成挂载。很多云盘、网络盘问题就卡在这里:PVC 已经 Bound,Pod 也已经选到节点,但卷还没真正附加到那台机器,因此 kubelet 只能继续等待。

真正进入节点本地之后,链路才会来到最容易被忽略、但离业务可用性最近的部分。kubelet 在处理 Pod 的卷时,会调用节点侧的存储插件完成本地准备。对于 CSI 而言,通常会经历几类典型动作:先确认节点是否能看到该卷,必要时在全局路径完成设备准备和格式化,再把它挂接到 Pod 可见的卷目录。很多实现会把这条路径拆成 NodeStage 和 NodePublish 两层,前者更像“把卷在节点上准备成一个可复用的本地资源”,后者更像“把这个本地资源发布到具体 Pod 的命名空间里”。这也是为什么挂载问题经常不能只看控制平面对象,因为决定容器是否真的能看到文件系统的关键动作,此时已经发生在节点本地。

从控制面和节点面的职责分工来看,这条链路可以大致分成三段。第一段是声明到绑定,重点在 PVC、PV、StorageClass 和供给控制器,它回答“有没有合适卷”。第二段是绑定到调度与附加,重点在调度器、附加控制器和 CSI 控制器插件,它回答“这块卷能否与目标节点匹配,并被后端附加过去”。第三段是节点准备到 Pod 可见,重点在 kubelet 和 CSI Node 插件,它回答“目标节点是否真的把卷变成了容器可用的挂载点”。把这三段混在一起看,状态就会显得混乱;把它们分开,就会发现每一段其实都只对自己那部分结果负责。

这也是为什么“控制链路成功推进”和“业务真正可用”之间仍然存在最后一道缝。即便 PVC 已经 Bound,卷也已经附加到节点,节点侧仍然可能在格式化、挂载参数、权限准备或目标路径发布上失败。反过来说,就算 Kubernetes 对象层面已经看见卷相关状态都正常,容器里的应用也未必已经能够立刻用这块盘写数据。存储在 Kubernetes 中从来不是一个单点完成事件,而是一系列分阶段收敛的状态推进。

如果把这篇文章压缩成一句话,那么这条链路的核心不是“创建了一个 PVC,于是 Pod 拿到一块盘”,而是“先由控制平面把存储需求收敛成一个可分配卷,再由调度和附加链路把它送到目标节点,最后由节点本地把它变成容器真正可见的挂载点”。理解了这层分段,后面再看 Pending、Bound、Attach、Stage、Publish 这些状态,就不会再把它们误认为同一件事的不同名字,而会知道它们是在不同阶段分别回答不同问题。

# 进一步讨论

  • 【待展开:CSI Node 侧调用链为什么经常成为排障盲区|csi-node-call-path-debug-blind-spot】:这能单独展开节点挂载失败时日志和状态为何分散。
Edit (opens new window)
Last Updated: 2026/04/28, 14:21:08
为什么 Kubernetes 要把存储抽象拆成 PV、PVC、StorageClass 与 CSI
为什么 PVC 已经 Bound,Pod 仍然可能挂载失败

← 为什么 Kubernetes 要把存储抽象拆成 PV、PVC、StorageClass 与 CSI 为什么 PVC 已经 Bound,Pod 仍然可能挂载失败→

最近更新
01
待完成专题池
04-28
02
待完成专题池
04-28
03
为什么 Kubernetes CSI 插件架构要拆成 Controller、Node 与 Sidecar
04-28
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式