ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

StorageClass与Provisioner:从原理到PVC Pending排查实战

StorageClass与Provisioner:从原理到PVC Pending排查实战 前阵子帮一位朋友排查K8s集群里PVC一直Pending的问题绕了不少圈子最后发现是StorageClass里provisioner字段和实际安装的CSI驱动对不上。这类问题在社区里非常常见正好我也拿豆包这类AI问答工具把StorageClass和Provisioner的关系梳理过一遍发现工具能给出一个差不多对的概念解释但真正落到生产环境还是有不少细节需要自己踩过才知道。这篇文章我就把AI问答里问到的内容、结合我自己的生产实践整理成完整的认知链路从StorageClass和Provisioner的关系开始到PV/PVC/SC三者的配合流程再到主流Provisioner的选型对比、生产环境里容易踩的坑以及一次完整的PVC Pending排查过程。适合正在学K8s的开发者、准备面试的运维以及被存储问题折磨过的集群管理员。1. StorageClass和Provisioner的关系一张配方表和背后的供应商1.1 静态供给时代管理员又当水管工又当库管要理解StorageClass的价值得先看没有它的时候K8s存储是怎么玩的。在早期的K8s版本里管理员要先手动创建一批PVPersistentVolume每个PV代表一块真实的存储——可能是NFS目录、云盘、Ceph RBD卷。然后用户提交PVCPersistentVolumeClaim系统在已有PV里找一块满足容量、访问模式的PV绑定给用户。这个模型能跑但非常难受。管理员得提前预判用户会要多大容量要哪种存储类型然后一块一块手动建PV。用户要扩容管理员得临时再去搞一块新盘。云环境厂商支持动态创建云盘但这个能力没有被K8s接收管理员只能通过脚本或者云控制台手动分配。这个阶段就像小区物业统一修了几个固定大小的储物间住户来申请才能分配。储物间建多了浪费资源建少了住户投诉而且建储物间的动作只能靠物业人工完成。日常维护成本极高也很容易出现用户申请了50Gi的卷但PV池里最大的只有30Gi这种尴尬。1.2 StorageClass是配方表Provisioner才是供应商后来K8s引入了StorageClass核心目的就是把创建存储这个动作自动化。用户不需要知道底层到底是一块云盘还是一个NFS目录只需要声明我要一块标准硬盘或者我要一块高IOPS的卷就可以了。这里最关键的一点是StorageClass本身并不创建任何存储它只是一份配方表描述了这类存储应该怎么创建——用什么厂商的驱动、什么类型、什么回收策略。真正动手干活的是配置里provisioner字段指定的那个组件也就是Provisioner。我把这层关系类比成点外卖StorageClass就是菜单上面写着红烧肉多少钱、加辣不加辣Provisioner是后厨菜单上写的菜由它真正做出来PVC就是你下的订单最终端上桌的菜就是系统里生成的PV。你点了一份红烧肉系统把订单PVC拿去跟菜单StorageClass对了一下然后由对应后厨Provisioner把菜做好菜单本身不会自己蹦去厨房炒菜。很多刚接触K8s的人会误以为只要建了StorageClass存储就有了不是的StorageClass只是个静态定义不装对应的Provisioner/CSI驱动它永远都只是个漂亮的YAML文本。1.3 需要单独说明的反直觉点StorageClass不是存储池还有一个容易绕晕的点StorageClass不带容量信息也没有剩余空间之类属性。它只是定义了用什么方式、什么参数去创建存储。集群里可以同时存在几十个StorageClass但真正能动态供给卷的取决于有多少个可用的Provisioner实现。同时provisioner字段的名字不是随便取的。它一定要和集群里实际部署的Provisioner组件注册的名字一致。比如你部署了NFS CSI驱动它注册的名字通常是nfs.csi.k8s.io那你SC里的provisioner字段也必须是这个值。集群里有没有安装对应的驱动、注册名是否匹配是排查SC配置了但PVC出不来卷的第一道关口。2. 一次PVC动态供给的完整生命周期拆解2.1 从PVC提交到PV绑定中间发生了什么搞清楚关系之后我们把一次完整的动态供给流程按时间线拆开。假设我现在提交一份PVCapiVersion: v1 kind: PersistentVolumeClaim metadata: name:>apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: server: 192.168.1.10 share: /data/nfs reclaimPolicy: Retain volumeBindingMode: Immediate allowVolumeExpansion: true mountOptions: - nfsvers4.1 - hard每个字段背后都有它自己的行为和风险我挑几个重点展开reclaimPolicy回收策略取值是Delete或Retain。这里是事故高发区也是我反复跟AI问答确认过的一个点——如果设为Delete删掉PVC时对应PV和底层存储会被一并删除如果设为Retain删除PVC后PV会变成Released状态底层存储还在但需要管理员手动决定是清理还是恢复。AI问答通常只会给一句话解释Delete自动删Retain手动删。但实际生产里很多团队把默认的Delete直接套到有数据库快照的存储上结果回收的时候把生产数据卷了整个删掉找不回来的那种。volumeBindingMode绑定模式两种Immediate和WaitForFirstConsumer。Immediate表示PVC一提交就去创建PV立即绑定WaitForFirstConsumer则表示PVC先挂着等有Pod真正调度到某个节点后再基于该节点创建PV。后者对本地卷这类依赖节点位置的存储特别重要因为它能保证存储创建在Pod所在的节点上。但它的副作用是Pod被驱逐后PVC可能还挂在旧节点上重调度时要小心处理。allowVolumeExpansion是否允许扩容布尔值设为true后用户改了PVC的size字段允许扩容。实际扩容时要先扩展底层存储再由K8s CSI驱动完成文件系统的在线扩容。有一点容易忽略扩容是单向的只能增大不能缩小。这个是底层文件系统决定的不是K8s的限制扩容操作一旦提交想回滚基本不可能。mountOptions挂载选项这部分不同存储差异巨大。NFS场景常见的如nfsvers4.1、hard、noatime云盘场景可能涉及IOPS等。我在实际项目里见过最典型的问题是NFS服务器用的是NFSv3而客户端默认尝试v4挂载失败PVC状态的Pod一启动就报mount error。提前在SC里声明版本能避免大量脑细胞死亡。parameters驱动参数这个字段完全取决于Provisioner驱动不同驱动定义的参数完全不同。AWS EBS接受type、iopsPerGBNFS CSI常见的有server、shareCeph RBD是pool、csi.storage.k8s.io/node-stage-secret-name这类。所以参数怎么写必须看对应驱动的官方文档不要太相信网上某个通用模板。2.3 静态供给依然存在不用StorageClass也能跑动态供给好用但静态供给并没有消亡很多需要对接遗留存储的场景仍然在用。比如公司有一套老旧的SAN存储不支持CSI驱动管理员手动创建PV将路径和容量填好再把某块PVC绑定过去。这种情况下PVC不需要指定storageClassName或指定为空字符串直接匹配现成的PV。静态供给的痛点就像1.1说的那样所有PV都要人工建但它的好处是灵活——不支持CSI的存储也能用而且回收策略可控不会出现Provisioner误删底层数据的问题。所以并不存在StorageClass必须配置的硬性要求而是要让存储自动化供给就必须配置StorageClassProvisioner。3. 主流Provisioner选型别被AI问答的推荐配置带偏3.1 一张表看清常用Provisioner我问AI问答K8s有哪些Provisioner它能给出一大串名字但很少告诉你什么时候该选哪个。梳理了目前主流的选择我按使用场景做了个对照Provisioner适用场景底层存储典型备注csi-hostpath本地开发、测试节点本地目录不能用于生产多副本rancher.io/local-path本地轻量存储节点目录需要WaitForFirstConsumernfs.csi.k8s.io自建环境共享存储NFS服务器适合读写多节点、成本低ebs.csi.aws.comAWS云环境AWS EBS云盘单节点读写同AZ使用rbd.csi.ceph.com生产级可靠存储Ceph RBD支持快照、多副本运维重cephfs.csi.ceph.com生产级共享存储CephFS多读多写大规模并发local.csi.k8s.io高性能本地盘节点本地磁盘需要LVM/分区管理等前提如果是云上强烈建议优先使用云厂商官方CSI驱动不要用那些老的in-tree plugin名字比如kubernetes.io/aws-ebs。新版K8s已经把存储插件的in-tree代码迁出到CSI老插件名还在一定版本里兼容但新特性都不会补了迁移只是时间问题。3.2 自建环境怎么选NFS与本地盘的分界线自建K8s集群最常见的两个选择NFS CSI和本地卷方案我讲讲它们的取舍。NFS胜在共享和便宜。不需要SSD级别的介质普通服务器开个NFS服务就能用多个Pod跨节点挂载同一块卷没问题适合日志收集、文件上传这类共享读写的应用。缺点是性能上限一般IO密集的数据库跑在NFS上通常不会好看而且NFS服务器本身是单点万一挂了所有依赖它的Pod全部受影响。生产里建议至少做NFS服务器的HA或者直接用云NAS托管服务。如果应用是单副本且对性能敏感比如任务型中间件、本地缓存就可以考虑本地卷。本地卷的问题在于数据跟着节点走节点挂了数据不一定没看底层磁盘但Pod调度会受约束——必须调度到数据所在节点。所以本地卷方案几乎都要配合WaitForFirstConsumer和一定的节点亲和架构上要提前想明白。3.3 云上怎么选CSI驱动是主流方向云上的选型跟自建不太一样核心其实是别自己造轮子。每个云厂商都提供了自己的CSI插件功能和支持度最完整。我列几个选型时一定要考虑的点是否支持在线扩容。云盘在线扩容对业务影响小但不同驱动对扩容的实现路径不同是否支持快照。快照几乎是数据库类应用的强需求CSI的VolumeSnapshot接口是否齐全很重要存储类型和可用区。云盘通常绑定可用区如果PVC指定了可用区约束Pod只能调度到同区否则挂载会失败要不要做跨可用区高可用选型结果完全不同。另外提醒一下云厂商的CSI驱动一般要求集群用托管版本或者自己单独装插件装的时候RBAC和云厂商权限IAM / 账号密钥是两大坑点权限配少了Provisioner会静默失败这是最常见的云上Provisioner问题来源。4. 生产环境里最容易栽的五个坑4.1 Delete回收策略的误删事故这是我在多个团队里见过的事故类型里最狠的一个。线上数据库使用的PVC它的StorageClass配置了reclaimPolicy: Delete某天运维想测试一下PVC重建流程直接把PVC删了结果Provisioner立刻把底层存储卷连带所有数据库文件一起删了物理删除、没有回收站数据直接没了。可能很多人觉得删除PVC应该只是解绑底层数据不会丢吧但实际上Delete策略在动态供给里是删除PV 删除底层存储的双重动作。存储类里如果你明确没写reclaimPolicy默认就是Delete所以生产环境有数据资产的存储类强烈建议显式改成Retain底层数据保留删PVC之后还留着PV让你手动确认至少给了后悔药。提示不要用Delete策略承载有持久化价值的数据除非你能接受数据随PVC删除一起消失。4.2 WaitForFirstConsumer绑定带来的调度陷阱配置volumeBindingMode: WaitForFirstConsumer后PVC的绑定被推迟到有Pod真正用它的那一刻。它解决的是本地卷不知道该建在哪个节点的问题但随之而来的是调度约束Pod一旦被调度到某个节点PVC就会在那个节点上完成绑定。之后如果节点故障、维护、驱逐Pod被重新调度到别的地方这块本地卷还在旧节点Pod会一直卡在ContainerCreating报找不到卷。解决思路通常是预先设计好节点亲和性、使用节点组、或者配合Velero这类工具进行卷迁移。如果你要迁移Pod唯一干净的办法是删掉PVC重建并让数据同步回新卷这也是本地卷方案在生产里的最大成本。4.3 扩容只许增不许减以及mountOptions的小问题allowVolumeExpansion开启后用户可以改PVC请求容量来实现扩容。这个功能在AI问答里看着很美好但实际业务踩过几次之后值得注意扩容是单向的今天10Gi扩到20Gi容易但想缩回10Gi是不可能的事情——底层文件系统不支持缩减K8s也不提供这个能力扩容出错时PV容量和底层存储容量可能不一致需要手动清理对某些存储来说扩容只能在卷处于未挂载或特定状态下进行挂载中的卷扩容可能失败需要重新调度Pod触发重挂载。mountOptions的坑也很有意思。我遇到过一台NFS服务器因为防火墙配置只放行了TCP 2049但客户端的挂载请求尝试UDP端口结果NFS挂载一直timeout。这类问题在SC里加nfsvers4.1、tcp这些mountOptions就能规避。SC的mountOptions会被放入PV的spec里所以事后想改需要重建SC和PV不是改一行就生效的改之前先想清楚影响面。4.4 Provisioner权限不足导致的静默失败Provisioner作为集群组件它本身需要通过RBAC获得创建PV、读取SC、更新PVC等权限云服务商的Provisioner还需要云上的API授权。权限不足时PVC会一直PendingEvent里通常只会出现Failed to provision volume with StorageClass xxx细节则藏在Provisioner的Pod日志里。我在一次客户环境里排查了几个小时最后发现是CSI controller Pod的ServiceAccount没有persistentvolumes的create权限Event和PV查询日志全部没有存储端的错误信息。检查完毕之后做一个简单的权限审计再加好RBAC就恢复了。所以排错的时候不要一上来就怀疑存储后端先把Provisioner的权限列一下不亏。4.5 命名空间、访问模式这些看不见的约束还有一批看不见但会卡流程的约束SC是集群级别的对象PVC是命名空间级别的但PV的回收、绑定不受命名空间限制是集群级资源这意味着清理PV时要小心跨团队误操作访问模式匹配问题。PVC申请ReadWriteMany而底层Provisioner只能创建ReadWriteOnce的卷PVC也会匹配不到合适的卷云盘可用区。如果你在SC的parameters里指定了可用区而Pod调度的节点不在同区PVC即使绑定了PVPod启动时也会挂载失败。这类问题在最开始选型时就要考虑到。5. 排查一次PVC Pending的完整链路5.1 从describe pvc开始逐步定位网上和AI问答里都能搜到PVC Pending怎么排查但答案大多数是列了一堆命令没有告诉你排查的先后顺序。我用自己的习惯整理一遍第一步看PVC本身的状态和事件kubectl get pvc -n namespace kubectl describe pvc pvc-name -n namespacedescribe输出里的Events字段是最直观的判据。常见信息waiting for first consumer to be created before binding—— 说明SC用了WaitForFirstConsumer需要检查是否有Pod在用它storageclass.storage.k8s.io xxx not found—— SC名字错了或者SC没创建成功Failed to provision volume with StorageClass xxx—— Provisioner执行失败要看下一个环节的日志。第二步确认SC和PV是否存在kubectl get sc kubectl describe sc sc-name kubectl get pv别小看这一步很多人贴出来一个not found就去查Provisioner其实只是YAML里metadata.name打错了一个字母。第三步看controller-manager和Provisioner Pod日志自建集群kubectl logs -n kube-system csi-controller-pod-name -c container云托管集群看平台侧事件或者云存储控制台同样能定位到权限不足配额不足卷创建失败这类信息。第四步检查存储后端侧状态如果是NFS去NFS服务器确认共享路径是否存在、权限是否正确、容量是否够用如果是云盘去云控制台看卷是否创建出来了是否处于可用状态。Provisioner报告成功但Pod挂载失败的时候问题往往出在这一层。5.2 三个真实案例复盘案例一SC拼写错误现象kubectl describe pvc显示storageclass.storage.k8s.io fast-ssd not found。排查kubectl get sc发现实际SC叫fast-ssd-v2PVC里写的是fast-ssd。改掉PVC引用即可这种几分钟就能定位但很考验先看Events还是先看Pod日志的判断。案例二NFS驱动没装现象PVC一直在PendingEvent显示no volume plugin matched。排查集群里根本没有装任何注册名为nfs.csi.k8s.io的组件。去装了NFS CSI驱动DaemonSet controller之后PVC立刻绑上。这说明写SC和装驱动是两件事SC只是个配方表厨房驱动没开门单子永远没法完成。案例三NAS权限导致挂载失败现象PVC显示Bound状态正常但使用它的Pod一直ContainerCreatingdescribe pod报mount timeout。排查kubelet日志显示NFS mount被拒绝原因是NFS服务器的squash设置。PV/SC挂载是由root身份执行的NFS把root映射成了nobody权限不够挂载目录无法写入。调整NFS服务端的root_squash配置后恢复。这个案例的关键教训PVC绑定成功 ≠ 真正能用Pod运行时才是完整链路。5.3 面试和故障处理都能用的自检清单把这套流程沉淀成一份自检清单面试回答和真实排错都用得上PVC状态是否是Bound如果Pending先看EventsEvents里是否有not found查SC名称是否准确Events里是否有Failed to provision下一步看Provisioner Pod日志Provisioner是否注册成功kubectl get pods -n kube-system确认驱动组件存在Provisioner的RBAC权限是否完整ServiceAccount是否有PV的create权限底层存储是否创建成功检查NFS目录或云盘状态容量和配额是否足够云厂商和NAS都有配额限制超出会报错是否需要Pod调度到特定节点才能绑定检查volumeBindingMode和Pod调度结果Pod挂载失败时检查kubelet日志、节点上的CSI client日志还要注意PV的 recycling 状态如果PV卡在Released需要手动清理后才能继续被使用。这套流程走完90%的存储类问题能定位到具体环节。剩下的10%大多是存储硬件、网络底层或者极端参数组合的问题那就需要把日志提给存储厂商或K8s社区做进一步分析了。最后再分享一点个人经验像我一开始用AI问答工具了解StorageClass和Provisioner时得到的答案概念上是准的但真正帮我把逻辑打通的是那次PVC一直Pending、事件啥提示都没有的排障经历——理解了Provisioner才明白SC真的只是个菜单能不能端出菜来要看后厨在不在。建议你们在测试环境里亲手创建一个SC再故意把provisioner字段写错一次观察一下PVC的Event变化这个实验比背十遍概念都管用。另外接管的集群里第一件事先跑一遍kubectl get sc -o yaml看看每个SC的provisioner和reclaimPolicy大概率能帮你提前预判很多存储问题的走向。
返回列表