首页 / 资讯中心 / 文章详情

Kubernetes存储入门:从PV、PVC到StorageClass动态供给实践

Kubernetes存储入门:从PV、PVC到StorageClass动态供给实践 ★ FEATURED ARTICLE
1. 从Pod重启丢数据说起存储抽象到底在解决什么问题1.1 Pod的生命周期与数据落盘的根本矛盾接触Kubernetes到了一定阶段PV和PVC是绕不开的一道坎。我最早学K8s的时候在Pod里直接写hostPath、emptyDir跑个demo好像没什么问题可一到多节点集群或者需要长期保留数据的业务立刻翻车。最典型的现象就是Pod被重新调度到另一台节点后之前写在宿主机的数据全没了或者说写入到了某个节点的磁盘里业务下一跳去了别的节点根本找不到。这时很多人会困惑——我的数据到底存在哪里Pod重建之后数据还在不在这个问题的根源在于Kubernetes里Pod的定位。Pod是有状态的还是无状态的不只是架构设计问题更是存储设计的起点。默认情况下Pod里的容器是“用完即弃”的容器重启、Pod重建、Node故障都可能导致数据丢失。emptyDir只是给一个Pod内部的临时目录生命周期和Pod完全绑定Pod被删除目录跟着消失hostPath则把宿主机目录直接挂进容器数据虽然在节点上留住了但会带来两个新问题第一Pod被调度到别的节点数据就“失联”了第二宿主机目录谁都能碰权限和隔离几乎没有。从实际项目来讲一个典型的业务Pod跑在几个副本里如果其中一个被驱逐Kubernetes会重建它。重建后的Pod如果还想读到原来的数据存储就必须做到三件事数据在哪、怎么访问、生命周期归谁管理。PV和PVC就是Kubernetes给这套问题提供的一套标准答案。1.2 环境差异与运维边界为什么在Pod里直接挂存储不是答案在还没有PV和PVC的年代部署一个带数据库的Pod几乎只能在YAML里写死云厂商的云盘ID或者NFS路径。这会导致两个直接问题。第一开发和运维的配置完全耦合。开发人员写一个Deployment里面得知道存储用的是哪块云硬盘、哪个NFS地址这些信息对应用来说根本不该关心第二换环境就得改一遍YAML。测试环境用的存储和线上环境用的存储不一样同一套部署文件没法直接迁移。PV和PVC的引入本质是做一个“存储的资源化抽象”。PV像一个库存记录描述集群里有一块多大、什么类型、什么性能的存储资源PVC像一份申请单写清楚应用需要多大的空间、以什么方式访问中间由Kubernetes负责撮合。应用不再关心底层用的是云盘、NFS还是本地磁盘只需要说“我要5GB读写一次即可”。存储工程师在集群层面准备好PV应用的维护者只写PVC两者通过资源和请求的匹配关系自动对接。这个设计思路和操作系统里进程申请内存的思路非常像。进程不关心物理内存在哪个bank里只向系统申请一个虚拟地址空间系统负责映射到物理内存。PV和PVC就是存储领域里的“虚拟内存机制”。理解了这个前提后面看PV、PVC的各种字段和绑定规则就非常好理解了。2. PV集群里静态预留存储资源的第一层抽象2.1 PV的经典YAML结构与关键字段解读直接看一个标准的PV定义把关键字段拆开来讲。假设我们要准备一块10GB的NFS存储给业务使用YAML大概是这个样子apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-001 labels: type: nfs usage: demo spec: capacity: storage: 10Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: manual-nfs nfs: path: /data/k8s-pv/pv-nfs-001 server: 192.168.1.100capacity.storage定义的是这块PV的总容量。accessModes是访问模式决定多个Pod能不能同时挂载它。persistentVolumeReclaimPolicy是释放策略表示PVC被删除后这块PV里的数据怎么处理。storageClassName用来关联StorageClass即使是静态PV也建议写清楚后面讲动态供给时会说明这里面的门道。最后的nfs段是卷类型的具体定义不同存储类型这一段内容会完全不同。这里面的accessModes一共就三种对应NFS、云硬盘这类存储的实际能力差异访问模式缩写支持情况典型场景ReadWriteOnceRWO大多数块存储单节点读写数据库、日志系统ReadOnlyManyROXNFS/部分文件存储多个Pod只读挂载配置文件共享ReadWriteManyRWXNFS/GlusterFS多个Pod同时读写文件服务、消息队列很多新手会以为这个字段随便填填了Kubernetes就会按这个模式来保证。实际上accessModes不是限制而是“声明”。它告诉调度器这块PV具备什么能力真正能不能被多个节点访问取决于底层存储本身。比如把一块云硬盘PV声明成RWX多个节点同时挂载时底层存储根本支持不了数据很快就损坏。2.2 PV的卷类型、回收策略与状态机PV支持的卷类型非常多常见的有hostPath、nfs、cephfs、rbd、iscsi以及各大云厂商的云盘类型如gcePersistentDisk、awsElasticBlockStore、azureDisk等。这些类型本质是Kubernetes把不同存储系统的接入协议做成了统一的抽象。选择哪种卷类型核心考虑三点存储系统自身能力、网络环境是否可达、运维团队的熟悉程度。persistentVolumeReclaimPolicy是另一个容易被忽略的字段。它有三种取值直接影响PVC释放后PV的去向Retain保留PVC删除后PV进入Released状态里面的数据保留。管理员需要手动处理数据然后删除PV重新创建。生产环境最安全的选择。Delete删除PVC删除时底层存储资源一并删除。适用于云盘等支持动态删除的存储。Recycle回收旧版本Kubernetes的清理机制会执行rm -rf清理数据后让PV重新可用。新版本已经弃用不推荐再碰。PV的状态机也有几个阶段需要记住Available可用待绑定、Bound已绑定到某个PVC、ReleasedPVC已删除但数据未清理、FailedPV发生错误。用kubectl get pv可以查看状态排查绑定问题时第一反应就应该是看一眼PV处于什么状态。写过一次静态PV之后很快会发现一个明显的问题如果业务需要一百块PV就要在YAML里定义一百份而且每块PV的容量、访问模式都必须提前和业务对好一旦业务侧需求变化静态PV就得手动调整。所以静态PV只适合存储资源少、需求稳定的场景。动态供给就是在这个痛点基础上出现的后面专门讲。3. PVC与PV的绑定机制一套标准化的存储供需关系3.1 PVC的核心字段与请求语义PVC的定义看起来更简单apiVersion: v1 kind: PersistentVolumeClaim metadata: name:>spec: selector: matchLabels: usage: demo这表示只绑定带有usage: demo标签的PV。合理使用标签是静态PV场景下控制绑定关系的核心手段。没有标签控制时谁容量够、访问模式匹配就可能绑到谁头上管理混乱的来源多半在这里。3.2 绑定细节容量、模式与状态机PVC和PV的绑定过程由控制器的PersistentVolumeController完成核心匹配逻辑有三条容量匹配PV的容量必须大于等于PVC请求的容量。访问模式匹配PVC需要的访问模式PV必须全部支持。StorageClassName匹配两者的storageClassName必须一致或者PVC不指定而PV的StorageClass为空才能绑定。注意第三点如果PVC没有设置storageClassName而集群里存在默认StorageClass那么PVC会被默认存储类接管进行动态供给不会去绑定静态PV。这是一个非常常见的坑后面讲动态供给时会展开。绑定完成后PVC和PV的状态都变成Bound同时PV上会记录绑定的PVC名字和命名空间PVC上会记录最终绑定的PV名字。这个双向引用关系可以通过kubectl get pvc -o yaml里的spec.volumeName字段看到。还有一个细节值得注意绑定是一对一的。一个PV只能绑一个PVC一个PVC也只能绑一个PV。即使PV还有剩余空间也不会再绑第二个PVC。这意味着如果静态PV规划得太碎片化会浪费不少容量。比如准备了三个10GB的PV三个PVC各申请5GB结果就是三个PV全被占用15GB的容量实际上“消失”了。动态供给出现后这种浪费的问题能大幅缓解。4. 真正落地的姿势StorageClass动态供给与默认存储类4.1 动态供给的完整链路拆解静态PV适合集群存储资源固定、使用方少的场景。一旦业务多、环境多比如每个项目都要独立的数据库存储静态PV的管理成本会直线上升。这时候就轮到StorageClass登场。StorageClass的典型定义apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: kubernetes.io/aws-ebs parameters: type: gp3 fsType: ext4 reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumerprovisioner是存储插件标识决定由谁来创建底层存储资源。parameters是传给存储插件的一组参数不同云厂商的StorageClass参数差别很大。reclaimPolicy确定了动态PV的默认回收策略。volumeBindingMode是一个容易被忽视但很重要的字段后面单独讲。动态供给的工作流程是这样的用户创建一个PVC。控制器发现PVC没有绑定任何PV而且指定了StorageClass。对应的存储插件被调用自动创建一块底层存储资源云盘、文件系统等。控制器基于该资源创建一个PV对象并与PVC完成绑定。应用Pod即可使用这个PVC。整个过程对用户完全透明。你只写了一份PVC剩下的存储创建、PV生成、绑定流程全由集群完成。4.2 WaitForFirstConsumer模式与实际选型建议volumeBindingMode有两种取值这个字段决定PV的创建时机。ImmediatePVC创建后立即触发动态供给立即创建PV并绑定。问题在于如果PVC的访问模式是RWO而PVC所在Pod还没被调度集群不知道Pod会跑到哪个节点可能把一个云盘创建在A节点结果Pod调度到了B节点导致挂载失败。WaitForFirstConsumer等到有Pod使用这个PVC并且Pod完成调度后才触发动态供给。这样存储会创建在Pod所在节点的可用区域内规避了跨可用区挂载的问题。生产环境建议用WaitForFirstConsumer。尤其是多可用区云环境RWO的云硬盘只能挂载到同一可用区的节点上如果PV创建早了Pod调度到别的可用区就得重建PV麻烦得很。默认StorageClass也很重要。New Kubernetes集群往往有一个默认存储类但是自建集群通常需要手动指定。如果集群没有默认StorageClassPVC不指定storageClassName时调度器不会动态供给PVC会一直卡在Pending。设置默认StorageClass的方式是在StorageClass上加一个注解metadata: annotations: storageclass.kubernetes.io/is-default-class: true设置好默认StorageClass之后不指定存储类名的PVC会自动走动态供给这对日常开发测试非常友好。不过生产环境我还是建议在PVC里显式写明storageClassName避免默认类被误改后一堆业务静静挂掉。4.3 本地存储的动态供给变体本地存储local storage是比较特殊的一类。本地PV不提供持久性节点宕机数据就丢但它有极低的延迟适合跑一些对IO要求极高、可以容忍节点级故障的应用。Kubernetes提供了local类型的StorageClass配合WaitForFirstConsumer可以把分布在各个节点上的本地磁盘统一管理起来。使用本地PV时有个关键步骤需要提前给节点打标签并创建本地PV资源kubectl label node node-1 disktypessdapiVersion: v1 kind: PersistentVolume metadata: name: local-pv-node1 spec: capacity: storage: 100Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: local-storage local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node-1这里的nodeAffinity是强制要求因为local类型的PV必须知道数据放在哪台节点没有节点亲和性约束调度器没法保证Pod被调度到正确的节点上。这一行丢了挂载就会出问题。5. 在Pod里使用PVC的完整示例与常见坑位5.1 从创建PV到Pod挂载的完整工作流示例把前面的内容串成一个完整的例子。假设业务需要一个5GB的存储要求单节点读写。我平时在测试环境最喜欢用NFS模拟真实场景流程如下第一步创建StorageClass方便管理后续也可以转动态apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: manual-nfs provisioner: example.com/nfs reclaimPolicy: Retain第二步创建PVapiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-001 spec: capacity: storage: 10Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: manual-nfs nfs: server: 192.168.1.100 path: /data/k8s-pv/pv-nfs-001第三步创建PVC请求5GBapiVersion: v1 kind: PersistentVolumeClaim metadata: name:>apiVersion: v1 kind: Pod metadata: name: app-with-pvc spec: containers: - name: app image: nginx volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName:>
阅读完成 · 觉得有帮助?
咨询建站