2. 有状态工作负载的容器化与编排基础

无状态服务可以随意替换副本,但数据库、消息队列、搜索引擎这类有状态工作负载,每个实例都携带不可丢失的身份与数据。把它们放进 Kubernetes,核心矛盾不是“能不能跑起来”,而是“重启、迁移、扩缩容之后,数据还在不在、身份还认不认得、访问入口还稳不稳”。本节围绕 StatefulSet、Operator 模式、持久卷声明(PVC)与调度约束四条主线,说明有状态负载的编排基础,并给出数据库容器化的适用边界与风险判断。

StatefulSet:稳定身份与稳定存储的绑定

Deployment 管理的是“无差别副本”,Pod 名字随机、存储共享或干脆无状态。StatefulSet 则给每个副本一个稳定且有序的身份:Pod 名称固定为 <statefulset-name>-<序号>,例如 mysql-0、mysql-1、mysql-2;每个 Pod 通过 volumeClaimTemplates 自动生成专属 PVC,命名形如 data-mysql-0。这个“序号—网络标识—存储卷”三者一一对应的关系,是 StatefulSet 最本质的语义。

它带来两个可预期的行为。第一,有序性:默认按 0、1、2 顺序创建,缩容时从最大序号开始删除,滚动更新也逆序进行。第二,稳定网络标识:配合 Headless Service,每个 Pod 获得 mysql-0.mysql-headless.default.svc.cluster.local 这样的稳定 DNS 名,主从复制、集群成员发现都依赖它。需要注意,StatefulSet 删除 Pod 时默认保留 PVC,这是保护数据的默认行为,但也意味着缩容不会自动回收存储,需要人工确认后清理。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  podManagementPolicy: OrderedReady
  selector:
    matchLabels: { app: mysql }
  template:
    metadata:
      labels: { app: mysql }
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-replicated
      resources:
        requests:
          storage: 100Gi

Operator 模式:把运维知识编码进控制器

StatefulSet 只解决“稳定身份 + 稳定存储”,它不理解 MySQL 主从如何选主、副本如何重建、备份如何触发。Operator 模式用自定义资源(CRD)描述领域对象,例如 MySQLCluster、PostgresCluster,再用控制器持续对比“期望状态”与“实际状态”,自动完成成员加入、故障切换、版本升级、备份恢复等操作。它本质上是把 DBA 的运维手册变成了可循环执行的调谐逻辑。

使用 Operator 时要关注三点:一是它是否声明了明确的健康判据与切换条件,避免脑裂;二是它管理的 PVC、Service、Secret 是否可被审计和接管;三是升级 Operator 本身是否会影响存量集群。Operator 降低了日常操作门槛,但把复杂度转移到了“控制器行为是否符合预期”上,生产环境必须先在预发集群验证其故障切换路径。

持久卷声明与调度约束

PVC 是“我要一块多大、什么性能的存储”的声明,真正落盘由 StorageClass 与 CSI 驱动完成。有状态 Pod 的调度必须与存储位置协同:如果使用本地盘或区域级云盘,Pod 只能调度到卷所在节点或可用区。常见做法是用 volumeBindingMode: WaitForFirstConsumer 延迟绑定,让调度器先选节点、再创建卷,避免“卷建好了却没有节点可用”。

调度约束还包括:用 nodeAffinity 把数据库固定到高主频、大内存的节点池;用 podAntiAffinity 让同一集群的多个副本分散到不同节点甚至不同可用区;用 topologySpreadConstraints 控制副本在机架/可用区的均衡度。对延迟敏感的数据库,还应避免与高吞吐批处理任务混部,必要时通过 taints/tolerations 独占节点。

StatefulSet 有状态编排:身份 · 存储 · 调度 三者绑定 StatefulSet mysql · replicas=3 Headless Service mysql-headless mysql-0 mysql-0.mysql-headless PVC: data-mysql-0 mysql-1 mysql-1.mysql-headless PVC: data-mysql-1 mysql-2 mysql-2.mysql-headless PVC: data-mysql-2 创建 DNS 持久卷声明 PVC → PV data-mysql-0 → PV-A (ssd-replicated) 100Gi · RWO · zone-a data-mysql-1 → PV-B (ssd-replicated) 100Gi · RWO · zone-b data-mysql-2 → PV-C (ssd-replicated) 100Gi · RWO · zone-c WaitForFirstConsumer:先调度后建卷 删除 Pod 默认保留 PVC 调度约束:让 Pod 落到“卷所在位置”且互不干扰 nodeAffinity 绑定可用区 / 节点池 podAntiAffinity 副本分散到不同节点 topologySpread 机架 / 可用区均衡 taints/tolerations 独占节点避免混部

数据库容器化的适用边界与风险

并非所有数据库都适合容器化。判断标准可以归纳为:数据规模与 IO 模式是否可被容器存储满足、故障恢复是否依赖稳定网络标识、是否有成熟的 Operator 支撑、团队是否具备容器网络与存储的排障能力。适合的场景包括:中小规模 MySQL/PostgreSQL、开发测试环境、需要快速交付的多租户实例、以及已有成熟 Operator 的云原生数据库。需要谨慎的场景包括:超大单实例(数十 TB 级)、对本地 NVMe 极致延迟敏感、依赖特殊内核参数或裸设备、以及强合规要求下不允许共享存储的部署。

主要风险集中在四处。第一,存储性能与可靠性:容器网络存储的 IOPS、延迟、抖动通常不如本地盘,且 CSI 驱动的故障恢复行为需要验证。第二,数据持久性:PVC 误删、StorageClass 回收策略设为 Delete、跨可用区卷无法挂载,都可能造成数据不可用。第三,网络与时钟:有状态集群对网络分区、时钟漂移敏感,容器环境的网络抖动可能触发误切换。第四,升级与回滚:Operator 或数据库版本升级失败时,回滚路径是否清晰、备份是否可恢复,必须提前演练。

生产环境务必确认 StorageClass 的 reclaimPolicy。若为 Delete,删除 PVC 会连带删除底层卷;数据库场景建议使用 Retain,并配合独立备份策略。

落地检查清单

检查项判断依据示例值
身份稳定性Pod 名与 DNS 是否固定mysql-0.mysql-headless
存储绑定每个副本是否有独立 PVCdata-mysql-0 / 100Gi
卷绑定模式是否延迟绑定以配合调度WaitForFirstConsumer
回收策略删除 PVC 是否保留底层卷Retain
副本分布是否跨节点/可用区podAntiAffinity + topologySpread
故障切换Operator 是否声明切换条件主库失联 30s 触发选主
备份恢复是否有可验证的恢复演练每日全备 + binlog,RPO≤5min