在Kubernetes上进行云原生分布式数据库的垂直规格变更流程
在Kubernetes上进行云原生分布式数据库的垂直规格变更,本质上是通过声明式API,让数据库工作负载(Pod)的资源配置(CPU/内存)在无需重启或最小化影响的情况下被动态更新。
下面我将以 StatefulSet 为核心,详细解释在K8s中对云原生分布式数据库进行垂直规格变更的正确流程和K8s的具体工作。
🤔 为什么是 StatefulSet 而不是 Deployment?
简单来说,StatefulSet 是为有状态应用“量身定制”的工作负载控制器,它提供了数据库集群必需的三个核心特性:
- 稳定的网络标识:每个Pod拥有固定的、可预测的主机名(
<statefulset-name>-<ordinal>)和DNS记录,这对于需要固定地址进行主从复制的数据库至关重要。 - 稳定的持久化存储:通过
volumeClaimTemplates为每个Pod动态创建独立的PersistentVolumeClaim。即使Pod被重新调度,其存储卷也会保持不变,确保了数据的持久性和唯一性。 - 有序的部署与伸缩:Pod的创建、更新、删除、伸缩都遵循严格的顺序(如索引0,1,2...),这对于需要按顺序启动、建立主从关系的数据库集群是基本要求。
Deployment 无法满足上述任何一点,因此数据库必须由 StatefulSet 管理。
🔄 基于 StatefulSet 的垂直规格变更流程详解
当您修改一个管理数据库的 StatefulSet 的资源配置时(例如,增加CPU/内存限制),K8s会触发一个有序的滚动更新。以下是其内部发生的具体事件序列,通过流程图可以直观地看到这个受控的过程:
上图展示了StatefulSet严谨的更新控制。下面,我们结合K8s组件,拆解流程中的几个关键阶段:
阶段一:检测与计划 (Detection & Planning)
- 您的操作:您修改了
StatefulSet中spec.template.spec.containers[].resources的配置并应用。 - K8s的工作:
StatefulSet Controller检测到Pod模板的“期望状态”发生改变。它不会一次性替换所有Pod,而是制定一个按Pod索引逆序(默认策略)逐个更新的计划,确保服务的高可用性。
阶段二:有序重建与调度 (Ordered Recreation & Scheduling)
- K8s的工作:
- 控制器指令:
StatefulSet Controller会指示API Server,将计划更新的Pod(例如mysql-2)标记为“需要重建”。 - 调度决策:
Scheduler会为这个“新”Pod(虽然名字还是mysql-2,但已是新的Pod对象)选择一个拥有足够CPU/内存资源的节点。这个节点很可能就是原节点。 - Pod生命周期:在目标节点上,
Kubelet会先终止旧的mysql-2Pod(发送TERM信号,执行可能的preStop Hook),等待其完全停止后,再创建并启动符合新资源规格的mysql-2Pod。 - 存储挂载:最关键的一步是,新Pod会自动挂载原Pod使用的那个PersistentVolumeClaim(PVC)和PersistentVolume(PV)。这意味着,Pod的规格变了,但数据盘完全不变,数据得以保留。
- 控制器指令:
阶段三:等待就绪与迭代 (Readiness & Iteration)
- K8s的工作:
Kubelet和StatefulSet Controller会等待新Pod通过其readinessProbe(就绪探针),确认其数据库服务已完全启动并可以接受连接。只有当前Pod(mysql-2)更新并就绪后,控制器才会开始处理下一个Pod(mysql-1),如此反复,直到所有Pod更新完毕。
💡 关键注意事项与示例
- 存储扩容的差异:垂直扩容CPU/内存需要重建Pod。而如果垂直扩容存储空间,则通常不需要重建Pod。您可以直接修改
StatefulSet中volumeClaimTemplates里定义的PVC请求大小,或者直接编辑已生成的PVC。支持动态扩容的存储类会自动扩展底层卷和文件系统,Pod可以在线应用新的存储空间。 - 一个实际的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在流程中的核心作用
-
接收与处理变更声明
当用户修改YAML文件中的resources.requests/limits并提交时,API Server是变更的入口。Controller Manager中的控制器(如Deployment Controller)会持续监听,发现期望状态与实际状态(Pod的资源配置)的差异,随即驱动系统向新状态收敛。 -
调度与驱逐Pod
Scheduler会基于新Pod的资源请求,为其选择一个有足够资源的节点。如果新旧Pod在同一节点且资源不足,或为平衡负载,K8s会触发驱逐,优雅终止旧Pod(发送SIGTERM信号,等待应用自行清理)。 -
存储卷的动态扩容
这是数据库垂直扩缩容中最关键且特殊的一环,主要依赖CSI机制实现:- Pod通过PersistentVolumeClaim声明所需存储。
- 当修改PVC的
storage请求后,PVC控制器会检测到这一变更。 - 控制器通过CSI接口,调用底层云存储服务(如AWS EBS、GCP Persistent Disk)的API,真正扩大磁盘容量。
- 节点上的kubelet与CSI Node Driver协作,完成文件系统的扩展(例如对
ext4或xfs文件系统执行resize2fs操作)。
💡 关键注意事项与实践建议
- 选择正确的更新策略:对于数据库这类有状态应用,务必使用
RollingUpdate(滚动更新) 策略而非Recreate。这可以确保在旧Pod完全终止前,新Pod已就绪,是实现“在线变更、服务不中断”的关键。 - 理解存储扩容的限制:动态扩容通常要求底层StorageClass的
allowVolumeExpansion字段为true,且不是所有存储类型都支持(例如某些本地卷)。此外,文件系统的扩容操作通常只在Pod所在的节点上发生一次。 - 监控与就绪检查:在数据库Pod的模板中,务必定义完善的
readinessProbe(就绪探针) 。这样K8s能准确判断新Pod何时真正准备好接收流量,避免在升级过程中将请求导向尚未完全初始化的Pod,导致请求失败。 - 资源限制的设定:
resources.limits设置不当可能导致Pod被OOM Killer杀死。建议数据库这类重要应用的requests和limits设为相同值,以保证服务质量。
🔄 进阶方案:自动化与混合扩缩容
垂直扩缩容虽直接,但存在单机资源上限。生产环境常采用更弹性的混合策略:
- HPA:基于CPU/内存等指标,自动增减Pod副本数,应对流量波动。适合无状态或计算层可水平扩展的数据库(如TiDB的TiDB-Server组件)。
- VPA:可自动分析Pod历史资源使用情况,建议或自动调整
requests/limits。但注意,VPA在调整时通常也会重建Pod。
你可以将HPA与VPA结合:HPA负责应对突发流量,进行快速水平伸缩;VPA负责根据长期趋势,周期性地、更平缓地优化单个Pod的资源规格,实现成本与性能的平衡。
浙公网安备 33010602011771号