bigywater.tech
Kubernetes · 59

PVC 扩容:在线扩容的条件

“PVC 扩容”到底是谁在做?kubectl patch 一下 20Mi 就算扩好了吗?这篇用没有扩容能力的 local-path 存储把 API Server 管的那一半(让不让改、能改什么)和存储驱动管的那一半(真正把卷变大)拆开,看清中间停在哪一步。

Forbidden
存储类不允许扩容时,改 PVC 大小立刻被 API Server 拒绝
10Mi → 20Mi
spec 改了,但 40 秒后 status.capacity、PV、df 都没变
ExternalExpanding
受理之后 PVC 上的事件:在等外部控制器来扩

先过 API Server 这一关

实测 · 本机执行输出
  存储类 k53-noexp(allowVolumeExpansion: false)的 PVC a:spec.requests=10Mi;status.capacity=10Mi;conditions=['Unused']
  把 a 改成 20Mi:退出码 1;Error from server (Forbidden): persistentvolumeclaims "a" is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
  被拒绝之后 a:spec.requests=10Mi;status.capacity=10Mi;conditions=['Unused']
  把存储类的 allowVolumeExpansion 改成 true 之后再改 a 为 20Mi:退出码 0;persistentvolumeclaim/a patched(字段是随时可改的,而且对已有 PVC 立即生效)

存储类 allowVolumeExpansion: false 时,把已绑定 PVC 的 requests.storage 从 10Mi 改成 20Mi 直接返回 Forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize。这是 API Server 的准入检查,与存储驱动无关。

allowVolumeExpansion 是存储类上可以事后修改的字段,改成 true 后同一条 patch 立刻被受理——它对已经存在的 PVC 同样生效。

受理不等于扩好了

实测 · 本机执行输出
  扩容前 b:spec.requests=10Mi;status.capacity=10Mi;conditions=['Unused'];PV capacity=10Mi;Pod 里 df /d 总大小 474095688 KiB(local-path 的卷是节点磁盘上的目录,df 看到的是整块节点磁盘)
  把 b 改成 20Mi:退出码 0;persistentvolumeclaim/b patched
  40 秒之后 b:spec.requests=20Mi;status.capacity=10Mi;conditions=['Unused'];PV capacity=10Mi;df 总大小 474095688 KiB
    b 上多出来的事件:['ExternalExpanding: waiting for an external controller to expand this PVC']

允许扩容的类 k53-exp 上,PVC b 改成 20Mi 后 patch 成功,但 40 秒之后 status.capacity 还是 10Mi,PV 的 capacity 还是 10Mi,Pod 里 df 的总大小也没变(local-path 的卷本来就是节点磁盘上的目录,df 看到的是整块盘)。PVC 上多出一条事件 ExternalExpanding: waiting for an external controller to expand this PVC。

spec 写的是意图,status.capacity 是结果;从前者到后者要靠 CSI 的 external-resizer 这类控制器完成,local-path-provisioner 没有实现扩容,所以请求一直停在这里。真正的在线扩容(文件系统在容器运行时被撑大)需要支持它的 CSI 驱动,本文的集群验证不了。

只能往大改

实测 · 本机执行输出
  b 现在 spec 是 20Mi、容量 10Mi;改成 5Mi:退出码 1;The PersistentVolumeClaim "b" is invalid: spec.resources.requests.storage: Forbidden: field can not be less than status.capacity
  改成 15Mi(比 spec 的 20Mi 小,但比容量 10Mi 大):退出码 0;persistentvolumeclaim/b patched;现在 spec.requests=15Mi;status.capacity=10Mi;conditions=['Unused']
  改回 10Mi(等于容量):退出码 1;The PersistentVolumeClaim "b" is invalid: spec.resources.requests.storage: Forbidden: field can not be less than status.capacity;现在 spec.requests=15Mi;status.capacity=10Mi;conditions=['Unused']

b 的 spec 现在是 20Mi、容量 10Mi:改成 5Mi 被拒,field can not be less than status.capacity;改成 15Mi(比 spec 小但比容量大)被受理;改成 10Mi(正好等于容量)却仍然被拒,报错相同,spec 停在 15Mi。最后这一条是实测结果,但我没弄清为什么等于容量也算“小于”,所以不拿它下结论。

绑定前后能改什么

实测 · 本机执行输出
  PVC c(类 k53-exp 允许扩容,没有 Pod 使用,phase=Pending):spec.requests=10Mi;status.capacity=(无);conditions=(无)
  把还是 Pending 的 c 改大到 30Mi:退出码 1;The PersistentVolumeClaim "c" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims
  创建 Pod 使 c 绑定之后:spec.requests=10Mi;status.capacity=10Mi;conditions=['Unused'];PV capacity=10Mi
  绑定之后改 accessModes:退出码 1;The PersistentVolumeClaim "c" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims
  绑定之后改 requests.storage 为 30Mi:退出码 0;persistentvolumeclaim/c patched

还是 Pending 的 PVC c 想把大小改成 30Mi 被拒:spec is immutable after creation except resources.requests and volumeAttributesClassName for bound claims——没绑定时 spec 整个不可变,想换大小只能删了重建。绑定之后,改 accessModes 依然被拒,只改 requests.storage 才被受理。

另外,v1.37 里 PVC 的 status.conditions 会有一条 Unused,有 Pod 引用时它是 False(reason PodUsingPVC)。

分步演示

扩容
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。独立的 5 节点 kind 集群 kx-sched(Kubernetes v1.37.0),存储是 kind 自带的 local-path-provisioner,它没有扩容能力;自建的 StorageClass 与 PVC 实验后都已删除;40 秒是固定等待;“Pod 里 df”用 busybox 的 df -k 读 /d 挂载点;每种情形各一次
    • 没有验证:真正支持扩容的 CSI 驱动上的完整流程(external-resizer、ControllerExpandVolume 与 NodeExpandVolume 两个阶段、FileSystemResizePending 条件);在线扩容与需要重启 Pod 的差别;扩容失败后的恢复(RecoverVolumeExpansionFailure 特性);为什么把请求改成与容量相等会被拒;StatefulSet 的 volumeClaimTemplates 与扩容的关系
    京ICP备2026067502号-1京公网安备11010502063487号