在Kubernetes上进行云原生分布式数据库的垂直规格变更流程

在Kubernetes上进行云原生分布式数据库的垂直规格变更,本质上是通过声明式API,让数据库工作负载(Pod)的资源配置(CPU/内存)在无需重启或最小化影响的情况下被动态更新

下面我将以 StatefulSet 为核心,详细解释在K8s中对云原生分布式数据库进行垂直规格变更的正确流程和K8s的具体工作。

🤔 为什么是 StatefulSet 而不是 Deployment?

简单来说,StatefulSet 是为有状态应用“量身定制”的工作负载控制器,它提供了数据库集群必需的三个核心特性:

  1. 稳定的网络标识:每个Pod拥有固定的、可预测的主机名(<statefulset-name>-<ordinal>)和DNS记录,这对于需要固定地址进行主从复制的数据库至关重要。
  2. 稳定的持久化存储:通过 volumeClaimTemplates 为每个Pod动态创建独立的PersistentVolumeClaim。即使Pod被重新调度,其存储卷也会保持不变,确保了数据的持久性和唯一性
  3. 有序的部署与伸缩:Pod的创建、更新、删除、伸缩都遵循严格的顺序(如索引0,1,2...),这对于需要按顺序启动、建立主从关系的数据库集群是基本要求。

Deployment 无法满足上述任何一点,因此数据库必须由 StatefulSet 管理

🔄 基于 StatefulSet 的垂直规格变更流程详解

当您修改一个管理数据库的 StatefulSet 的资源配置时(例如,增加CPU/内存限制),K8s会触发一个有序的滚动更新。以下是其内部发生的具体事件序列,通过流程图可以直观地看到这个受控的过程:

sequenceDiagram participant U as 用户/Operator participant A as API Server participant C as StatefulSet Controller participant S as Scheduler participant K as Kubelet (目标节点) participant PV as 持久化存储 U->>A: 1. 提交StatefulSet YAML变更<br>(修改Pod模板resources) A->>C: 2. 通知期望状态变化 C->>C: 3. 计算Pod更新顺序<br>(默认逆序,从最高索引开始) loop 对每个待更新Pod C->>C: 4. 标记当前Pod为“更新中” C->>S: 5. 驱逐旧Pod并调度新Pod S->>K: 6. 在节点创建新Pod(含新规格) K->>PV: 7. 挂载原有PVC(数据不变) PV-->>K: 8. 确认存储已附加 K->>A: 9. 报告新Pod状态为Running C->>C: 10. 等待新Pod就绪(Readiness Probe) C->>C: 11. 确认新Pod就绪后,<br>再处理下一个Pod end C->>A: 12. 全部更新完成,状态同步

上图展示了StatefulSet严谨的更新控制。下面,我们结合K8s组件,拆解流程中的几个关键阶段:

阶段一:检测与计划 (Detection & Planning)

  • 您的操作:您修改了 StatefulSetspec.template.spec.containers[].resources 的配置并应用。
  • K8s的工作StatefulSet Controller 检测到Pod模板的“期望状态”发生改变。它不会一次性替换所有Pod,而是制定一个按Pod索引逆序(默认策略)逐个更新的计划,确保服务的高可用性。

阶段二:有序重建与调度 (Ordered Recreation & Scheduling)

  • K8s的工作
    1. 控制器指令StatefulSet Controller 会指示API Server,将计划更新的Pod(例如 mysql-2)标记为“需要重建”。
    2. 调度决策Scheduler 会为这个“新”Pod(虽然名字还是 mysql-2,但已是新的Pod对象)选择一个拥有足够CPU/内存资源的节点。这个节点很可能就是原节点
    3. Pod生命周期:在目标节点上,Kubelet先终止旧的 mysql-2 Pod(发送TERM信号,执行可能的preStop Hook),等待其完全停止后,再创建并启动符合新资源规格的 mysql-2 Pod
    4. 存储挂载:最关键的一步是,新Pod会自动挂载原Pod使用的那个PersistentVolumeClaim(PVC)和PersistentVolume(PV)。这意味着,Pod的规格变了,但数据盘完全不变,数据得以保留。

阶段三:等待就绪与迭代 (Readiness & Iteration)

  • K8s的工作KubeletStatefulSet Controller 会等待新Pod通过其 readinessProbe(就绪探针),确认其数据库服务已完全启动并可以接受连接。只有当前Pod(mysql-2)更新并就绪后,控制器才会开始处理下一个Pod(mysql-1),如此反复,直到所有Pod更新完毕。

💡 关键注意事项与示例

  1. 存储扩容的差异:垂直扩容CPU/内存需要重建Pod。而如果垂直扩容存储空间,则通常不需要重建Pod。您可以直接修改 StatefulSetvolumeClaimTemplates 里定义的PVC请求大小,或者直接编辑已生成的PVC。支持动态扩容的存储类会自动扩展底层卷和文件系统,Pod可以在线应用新的存储空间。
  2. 一个实际的YAML修改示例片段
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mysql-cluster
    spec:
      serviceName: "mysql"
      replicas: 3
      selector:
        matchLabels:
          app: mysql
      template:
        spec:
          containers:
          - name: mysql
            image: mysql:8.0
            resources: # 这是垂直规格变更的目标部分
              requests:
                memory: "4Gi" # 从2Gi修改为4Gi
                cpu: "1000m"  # 从500m修改为1000m
              limits:
                memory: "8Gi" # 从4Gi修改为8Gi
                cpu: "2000m"  # 从1000m修改为2000m
            # ... 其他配置(如probe、ports)很重要
      volumeClaimTemplates: # StatefulSet特有,定义每个Pod的存储
      - metadata:
          name: data
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: "fast-ssd"
          resources:
            requests:
              storage: 200Gi # 垂直扩容存储时修改这里,通常无需重启Pod
    

总结来说,在K8s中对云原生数据库进行垂直规格变更,核心是修改管理它的 StatefulSet 的资源定义。K8s的 StatefulSet Controller 会以有序、受控、保证存储稳定的方式,逐个重建Pod,最终将整个集群平滑地升级到新的资源规格,同时最大限度地保障服务的连续性和数据的完整性。

对于更复杂的操作(如备份、配置变更、版本升级),通常会结合使用专为特定数据库设计的 Operator,它封装了更多领域知识,能更安全、自动化地管理整个流程。

🔧 K8s在流程中的核心作用

  1. 接收与处理变更声明
    当用户修改YAML文件中的resources.requests/limits并提交时,API Server是变更的入口。Controller Manager中的控制器(如Deployment Controller)会持续监听,发现期望状态与实际状态(Pod的资源配置)的差异,随即驱动系统向新状态收敛。

  2. 调度与驱逐Pod
    Scheduler会基于新Pod的资源请求,为其选择一个有足够资源的节点。如果新旧Pod在同一节点且资源不足,或为平衡负载,K8s会触发驱逐,优雅终止旧Pod(发送SIGTERM信号,等待应用自行清理)。

  3. 存储卷的动态扩容
    这是数据库垂直扩缩容中最关键且特殊的一环,主要依赖CSI机制实现:

    • Pod通过PersistentVolumeClaim声明所需存储。
    • 当修改PVC的storage请求后,PVC控制器会检测到这一变更。
    • 控制器通过CSI接口,调用底层云存储服务(如AWS EBS、GCP Persistent Disk)的API,真正扩大磁盘容量。
    • 节点上的kubeletCSI Node Driver协作,完成文件系统的扩展(例如对ext4xfs文件系统执行resize2fs操作)。

💡 关键注意事项与实践建议

  1. 选择正确的更新策略:对于数据库这类有状态应用,务必使用 RollingUpdate(滚动更新) 策略而非Recreate。这可以确保在旧Pod完全终止前,新Pod已就绪,是实现“在线变更、服务不中断”的关键。
  2. 理解存储扩容的限制:动态扩容通常要求底层StorageClassallowVolumeExpansion字段为true,且不是所有存储类型都支持(例如某些本地卷)。此外,文件系统的扩容操作通常只在Pod所在的节点上发生一次
  3. 监控与就绪检查:在数据库Pod的模板中,务必定义完善的 readinessProbe(就绪探针) 。这样K8s能准确判断新Pod何时真正准备好接收流量,避免在升级过程中将请求导向尚未完全初始化的Pod,导致请求失败。
  4. 资源限制的设定resources.limits设置不当可能导致Pod被OOM Killer杀死。建议数据库这类重要应用的requestslimits设为相同值,以保证服务质量。

🔄 进阶方案:自动化与混合扩缩容

垂直扩缩容虽直接,但存在单机资源上限。生产环境常采用更弹性的混合策略:

  • HPA:基于CPU/内存等指标,自动增减Pod副本数,应对流量波动。适合无状态或计算层可水平扩展的数据库(如TiDB的TiDB-Server组件)。
  • VPA:可自动分析Pod历史资源使用情况,建议或自动调整requests/limits。但注意,VPA在调整时通常也会重建Pod

你可以将HPA与VPA结合:HPA负责应对突发流量,进行快速水平伸缩;VPA负责根据长期趋势,周期性地、更平缓地优化单个Pod的资源规格,实现成本与性能的平衡。

posted @ 2025-12-30 00:47  程煕  阅读(12)  评论(0)    收藏  举报