PVC 为 Pod 提供容器之外的持久存储
容器本性易失,所以要用 PersistentVolumeClaim 把真实存储钉到它身上,即使 Pod 被重建也不丢数据。这个拆分是刻意的:PersistentVolume 声明已经存在的容量,PersistentVolumeClaim 请求需要的容量,由控制器把两者配上。有了 StorageClass,声明还能通过 provisioner 自己创建卷,于是没人需要预先造好存储。 真正会咬人的是:声明与卷各自有独立的生命周期,由各自的规则管着。finalizer 决定声明什么时候才允许消失,回收策略决定卷之后会怎样。删除顺序搞错,就会得到一个卡在 Terminating 的声明;策略搞错,要么留下本想保留的数据,要么留下一份没人在用却仍在计费的存储。 声明、卷与绑定 绑定是一次「全部满足」而不是「最接近」。一块卷只有在容量不小于请求、访问模式包含所请求的模式、storageClassName 匹配,并且没有被别的声明占用时,才是候选。除了这几项,volumeMode(Filesystem 与 Block)也必须一致;而已经释放却还没清理的卷并不会变回可用的备件,它的 claimRef 仍指向上一个声明,所以新的声明根本看不到它。什么都不满足的声明会停在 Pending,并带一条指明究竟哪一项没满足的事件,这比一句笼统的失败有用得多;常见原因是要的访问模式该类交付不了,以及类名根本不存在。 accessModes 没有默认值,必须选一个,而多数单节点块存储 provisioner 实际能交付的就是 ReadWriteOnce。它同时是单节点的,所以副本分布在不同节点上的 StatefulSet 无法共用一份声明。类支持动态供应时,volumeBindingMode 决定时机:Immediate 在声明一出现就创建卷,可能把受拓扑限制的卷放到没有节点能用的可用区;WaitForFirstConsumer 则让声明一直 Pending,直到有 Pod 被调度,再在那个 Pod 所在的可用区创建。 删除顺序与 finalizer 顺序是 Pod、声明、卷,保护机制由 finalizer 实现。在 Pod 仍挂载着声明时删除声明,什么都不会被删掉:声明拿到删除时间戳、翻成 Terminating,然后停在那里,因为 kubernetes.io/pvc-protection 仍在它的 finalizer 列表里。只有再没有任何 Pod 引用这份声明时,控制器才会摘掉那个 finalizer,所以那个「永远 Terminating」的 PVC 并不是控制器卡住,而是一份仍被使用着的声明。 ...