数据库 Operator 与高可用故障转移

共 24 题
📑 题目列表 24 题
#
★★★

1. 数据库 Operator 的备份/恢复/故障转移自动化

解释数据库 Operator 如何实现备份、恢复与故障转移的自动化,以及它相比人工运维的核心价值?

  • Operator 模式的核心(CRD + Controller 调谐循环)
  • 备份/恢复/故障转移三类自动化能力的底层机制
  • 声明式运维与人工运维的差异

数据库 Operator 基于 Kubernetes 的 Operator 模式实现,由两个核心组件构成:自定义资源(CRD,如 PostgresClusterMysqlCluster)和控制器(Controller)。用户通过 YAML 声明集群的期望状态(规格、副本数、存储、备份计划等),Controller 的调谐循环(reconcile)持续对比"期望状态"与"实际状态",差异驱动创建/更新/删除相关资源并收敛到期望状态。备份自动化:Operator 按声明的备份策略(如每天 02:00 全量、每 5 分钟 WAL 归档)创建 CronJob 或后台任务,将备份写入对象存储并维护保留策略。恢复自动化:Operator 提供 Restore 能力,用户声明目标集群与时间点,Operator 自动拉起新集群、拉取备份并重放 WAL。故障转移自动化:Operator 监控主库健康,检测到故障时触发自动切换(failover),将某从库提升为新主,并更新 Service 端点。核心价值在于将 DBA 的重复性、易错操作(升级、故障切换、备份恢复)编码为可重复、可审计、声明式的自动化流程,降低 MTTR 与人为失误。

Operator 的本质是"把运维专家的领域知识写成代码",用 reconcile 循环实现"写期望状态、自动收敛"。故障转移的自动化特别依赖脑裂防护(如通过 Consensus 或仲裁)与端点更新,而备份恢复的自动化依赖状态机与幂等性。

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster
spec:
  instances: 3
  storage:
    size: 100Gi
  backup:
    barmanObjectStore:
      destinationPath: s3://backups/pg
      wal:
        compression: gzip
  affinity:
    topologyKey: topology.kubernetes.io/zone
#
★★★

2. Operator 管理集群拓扑(主从/分片)的声明式

数据库 Operator 如何以声明式方式管理主从复制与分片集群拓扑?

  • 声明式拓扑描述(spec 中声明实例数、角色、分片)
  • Operator 如何据此创建/调整拓扑并维护角色
  • 分片集群(如 sharding)的声明式管理

Operator 将"集群拓扑"作为期望状态的一部分暴露在 CRD 的 spec 中。对主从复制:用户声明 instances: 3,Operator 据此创建主库与合适的从库,并自动配置复制流、识别主库(如通过 leader 选举或标签)。对分片集群:用户声明分片数量(如 shards: 3replicas: 2),Operator 分别管理每个分片组的副本集,并处理分片元数据(如协调器/配置服务器)。拓扑变更(如扩缩容、增删分片)只需修改 spec 并应用,Operator 幂等地推进:创建新 Pod、等待就绪、纳入复制、再摘除旧节点。Operator 还维护角色标签(如 role=primary)与 Service 端点,确保客户端始终访问正确的主库。分片集群的复杂度被封装在 Operator 中,用户无需关心底层每个分片的 Pod 细节。

声明式拓扑的关键是"描述要什么,而非怎么做"。Operator 把拓扑与角色管理(主从选举、分片协调)逻辑固化,使扩缩容、重新分片、故障恢复都变成可重复的自动化操作。

apiVersion: shardingsphere.apache.org/v1alpha1
kind: ShardingSphereProxy
metadata:
  name: ssp-proxy
spec:
  replicas: 3
  shardingConfig:
    shardingRule:
      tables:
        t_order:
          actualDataNodes: ds$->{0..1}.t_order_$->{0..1}
          tableStrategy:
            standard:
              shardingColumn: order_id
              shardingAlgorithmName: hash_mod
#
★★★

3. 数据库 Operator 的自定义备份策略与保留

数据库 Operator 如何支持自定义备份策略与备份保留(retention)?

  • 自定义备份策略的声明字段(频率、全量/增量、WAL 归档)
  • 保留策略的机制(保留数量/时长、过期清理)
  • Operator 如何保证策略按声明执行

自定义备份策略通过 CRD spec 中的 backup 字段声明,通常包含:备份方式(全量基础备份、增量备份、WAL/binlog 日志归档)、时间安排(Cron 表达式或间隔)、目标存储(S3/对象存储)、压缩与加密选项。保留策略(retention)定义保留多少份备份或保留多长时间,Operator 依据策略自动清理过期备份,避免存储无限增长。实现上,Operator 将备份请求抽象为 Backup CR,每个 Backup CR 携带策略参数,Operator 控制器调度执行、记录状态,并依据保留规则触发删除(先删最旧、跳过仍被 WAL 依赖的备份)。对涉及 PITR 的保留,Operator 会确保"保留期内的 WAL 段"与"基础备份"一起保留,避免出现被依赖的备份先被删除导致某时间点无法恢复。

保留策略的核心是"孤儿/依赖"问题:增量备份与 WAL 依赖对应的全量备份,删除时不能破坏恢复链。好的 Operator 会依据保留策略自动计算"必须保留的最小集合",并安全清理过期备份。

spec:
  backup:
    schedule: "0 2 * * *"          # 每天 02:00 全量备份
    retentionPolicy: "7d"          # 保留 7 天
    wal:
      retentionPolicy: "30d"       # WAL 保留 30 天以支持 PITR
    method: volumeSnapshot
    storage:
      s3:
        bucket: "backups"
        path: "/cluster"
#
★★★

4. Operator 的 CRD 与 Controller 调谐(reconcile)循环

解释数据库 Operator 中 CRD 与 Controller 的调谐(reconcile)循环的工作原理?

  • CRD 定义期望状态与 Spec/Status
  • reconcile 循环的触发、对比、收敛过程
  • 状态记录(Status)与最终一致性

CRD(Custom Resource Definition)定义了数据库集群的 Schema,包括 spec(用户声明的期望状态:实例数、存储、版本、备份策略)与 status(当前实际状态:副本就绪数、主库位置、备份状态)。Controller 是运行在 Pod 中的控制逻辑,它通过 informer/Watch 监听 CR 及关联资源(Pod、Service、StatefulSet)的变化。每当有变化,Controller 将对象放入工作队列,reconcile 函数被调用:读取期望 spec、对比集群实际状态,通过 "create/update/delete" 子资源操作逐步收敛差异,最后将最新状态写回 status。这一过程是幂等的、事件驱动的,并且有重试与限速(rate limiting)机制。数据库 Operator 还常采用"乐观并发"与"基于 status 的状态机"来避免重复动作,例如备份 CR 的状态机(Pending → Running → Completed/Failed)确保每个备份只执行一次。

reconcile 是 Operator 的"心脏",其核心是"不断让实际趋近期望"。数据库 Operator 的复杂之处在于:许多操作(如故障切换、备份、升级)是长时间、有状态、有副作用的,因此会用 status 状态机 + 重试 + 幂等来保证正确性,避免重复执行破坏数据。

func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    cluster := &dbv1.Cluster{}
    if err := r.Get(ctx, req.NamespacedName, cluster); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    // 1. 确保 StatefulSet 存在且副本数匹配 spec
    if err := r.ensureStatefulSet(ctx, cluster); err != nil {
        return ctrl.Result{}, err
    }
    // 2. 确保 Service 端点指向主库
    if err := r.ensureService(ctx, cluster); err != nil {
        return ctrl.Result{}, err
    }
    // 3. 更新 status
    return r.updateStatus(ctx, cluster)
}
#
★★

5. 云原生数据库的 StatefulSet 与稳定网络标识

为什么云原生数据库常用 StatefulSet,以及"稳定网络标识"(stable network identity)的意义?

  • StatefulSet 的特性(有序部署、稳定 Pod 名、稳定存储)
  • 数据库对稳定网络标识的需求(识别主从、数据归属)
  • Headless Service 与稳定 DNS

StatefulSet 为每个 Pod 提供稳定、唯一的身份:Pod 名称固定(如 mycluster-0mycluster-1)、存储卷与 Pod 绑定(PVC 不随 Pod 重建而丢失)、稳定的网络标识(通过 Headless Service 提供 mycluster-0.mycluster-svc 这样的稳定 DNS)。这些特性对数据库至关重要:数据库是有状态应用,数据归属于特定实例(如某分片、某实例的本地数据),主从角色需要稳定身份来配置复制(如 mycluster-1 复制自 mycluster-0);故障重启后 Pod 名不变,PV 重新挂载,数据不丢失。Controller 在部署时通过 Job 配置 Pod 网络标识,利用 Pod 的 DNS 主机名(hostname)和 Subdomain,让数据库集群节点间能通过稳定的 DNS 名互相发现与通信。相比之下,Deployment 的 Pod 名随机、无稳定身份,不适合有状态数据库。

稳定标识是"有状态"的根基。数据库需要知道"我是谁、谁是我的主、数据在哪",StatefulSet 的稳定 Pod 名 + Headless Service 的稳定 DNS + 绑定 PVC 三者共同解决了这个问题。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-svc     # Headless Service 提供稳定 DNS
  replicas: 3
  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"]
      resources:
        requests: { storage: 100Gi }
#
★★

6. 开源 Operator(Zalando Postgres/CNPG)能力对比

对比 Zalando Postgres Operator 与 CloudNativePG(CNPG)的能力与适用场景?

  • 两者的核心架构与存储方式
  • 备份/恢复/PITR/高可用能力差异
  • 适用场景与选型考虑

Zalando Postgres Operator(Zalando)是较早流行的 Postgres Operator,将 PostgreSQL 部署在 StatefulSet 上,使用 Spilo 作为镜像,依赖 etcd 或 Kubernetes 自带机制的分布式一致性来管理 leader 选举与故障转移,支持 WAL-E/WAL-G 的备份与 PITR。CloudNativePG(CNPG)由 EDB 维护,是较新的 Operator,同样基于 StatefulSet,但采用 declarative 的备份管理(Barman 集成、VolumeSnapshot 支持),提供更细粒度的拓扑、优雅的运维(如 cnpg plugin)、声明式备份与恢复、以及更完善的在线升级支持。二者的差异:Zalando 更成熟、生态历史长、依赖 etcd 做 leader 选举;CNPG 更贴合原生 Kubernetes 声明式模型、备份管理更现代、升级与磁盘扩容更平滑。选型时:若团队已有 Zalando 运维经验或需要高可用选举的强一致性,可选 Zalando;若追求更简洁的声明式、面向 PITR 与快照的备份、与云原生生态更紧密,选 CNPG。

对比 Operator 时,应关注:leader 选举机制(etcd vs 自选)、备份与恢复能力(PITR、快照)、升级与运维便利性、存储扩展方式、社区维护活跃度。这些决定了生产可维护性与故障转移的可靠性。

#
★★

7. Operator 的升级与版本兼容(k8s 版本)

数据库 Operator 的升级与版本兼容性如何管理?升级时要注意什么?

  • Operator 升级的方式(Helm/OLM/镜像替换)
  • 与 Kubernetes 版本/API 版本的兼容矩阵
  • 升级对运行中数据库集群的影响

Operator 升级通常涉及:替换 Operator 部署镜像(rolling update)、更新 CRD 定义(可能新增/修改字段)、升级数据库集群实例版本。升级前需核对兼容矩阵:Operator 版本支持的 Kubernetes 版本范围与 API 版本(如 apps/v1)、数据库版本范围。由于 CRD 是集群级资源,升级 Operator 时 CRD 可能被更新,需确保旧 CRD 实例仍能被新控制器处理(OpenAPI schema 兼容)。升级顺序上,通常先升级 Operator 控制器,再按需升级数据库集群;升级时 Operator 会协调既有集群(reconcile),一般不会中断运行中的数据库,但 Operator 自身重启期间集群的自动运维(如故障转移)会暂停,应在维护窗口内进行。还需注意:升级到新版本后,旧 Operator 的某些字段可能被弃用,需在文档与迁移脚本中处理。

升级兼容性的核心是"三份兼容矩阵":Operator 与 Kubernetes 版本、Operator 与数据库版本、CRD 新旧 schema 兼容。升级应遵循"先小版本、再大版本、先测试环境后生产"的原则,并在维护窗口内执行。

#
★★

8. Operator 与 Helm Chart 部署的差异

数据库 Operator 的部署与 Helm Chart 部署的差异是什么?

  • Operator 的声明式自愈能力
  • Helm 的模板化一次性部署特性
  • 两者在运维层面(升级、运行中变更)的区别

Helm Chart 是"模板化打包与一次性部署"工具:它把一组 Kubernetes 资源(清单)用模板 + 参数渲染成具体 YAML,执行 helm install/upgrade 时一次性应用,实现"部署"和"升级"的自动化,但 Helm 本身不具备"持续调谐/自愈"能力——资源被部署后,除非再次执行 helm 命令,否则它不会主动纠正状态偏移。Operator 则是"持续运行的控制循环":它在集群内运行时,持续 reconcile,自动修复漂移、自动处理故障、应对运行中事件(如 Pod 删除、节点故障)。实际中两者常结合:用 Helm 安装 Operator(Helm 管理 Operator 自身部署),Operator 负责数据库集群的持续运维。Helm 适合"部署与升级的模板化",Operator 适合"运行中的持续管理"。若只用 Helm 部署数据库本身,则升级、故障转移、备份恢复都需要人工执行 helm 命令,缺乏自愈能力。

核心区别:Helm 是"安装/升级工具"(烘焙一次),Operator 是"常驻控制器"(持续收敛)。理解这一点,就能明白为什么生产数据库往往用 Operator 而非裸 Helm 管理。

#
★★

9. 数据库对有状态存储(PV/PVC/StorageClass)的要求

数据库对 Kubernetes 中的有状态存储(PV/PVC/StorageClass)有什么要求?

  • StorageClass 的 provisioner 与存储类型(性能)
  • PV 的访问模式(ReadWriteOnce)与容量
  • 数据持久性与动态供给

数据库对存储的核心要求是:持久性(数据不随 Pod 重建丢失)、性能(IOPS/吞吐/延迟满足数据库负载)、容量按需扩展、以及与 Pod 的绑定关系。具体实现:通过 StorageClass 定义存储类型(如云盘 SSD、本地 NVMe),PVC 声明申请容量与访问模式,数据库 Pod 通常要求 ReadWriteOnce(单节点读写)以配合数据一致性;StatefulSet 用 volumeClaimTemplates 为每个 Pod 动态创建独立 PVC。数据库还要求:存储支持动态供给(Provisioner 自动创建 PV)、稳定的性能(SSD 而非 HDD)、以及可扩展性(扩容时能加大 PVC 容量)。此外,主从节点的数据存储相互独立,避免共用一个 PV。对高可用场景,还需考虑存储的故障域(如跨可用区)与备份能力(快照支持)。

数据库的存储诉求是"性能 + 持久 + 规模"。选择 StorageClass 时应结合数据库类型(OLTP 热衷低延迟本地盘/SSD,OLAP 可能用大容量云盘)与性能测试数据,并留意访问模式(RWO)与容量扩展方式。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "250"
volumeBindingMode: WaitForFirstConsumer   # 延迟绑定,确保 Pod 与 PV 同节点
reclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pg-0
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: fast-ssd
  resources:
    requests: { storage: 200Gi }
#
★★

10. 本地盘(Local PV)与网络存储(云盘)的 IO 差异

对比本地盘(Local PV)与网络存储(云盘)在 IO 性能上的差异及适用场景?

  • 本地盘的低延迟与高吞吐、受节点限制
  • 云盘的可扩展性与网络延迟
  • 数据库选型权衡

本地盘(Local PV)直接挂载在计算节点上,数据访问不经过网络,因此延迟极低(微秒级)、吞吐高、IOPS 稳定,性能接近裸机,适合延迟敏感的 OLTP 数据库。但其缺点是:数据与节点绑定,节点故障意味着数据面临丢失风险(除非有副本/备份),且无法跨节点迁移,容量受节点本身限制,需配合 NodeAffinity 或 topology 确保 Pod 调度到持有该 PV 的节点。网络存储(云盘,如 EBS、云盘)通过存储网络(如 SAN/云存储服务)访问,有网络往返延迟(通常毫秒级),但具有:独立的生命周期与持久性(节点故障数据仍在)、可跨节点重新挂载、容量可弹性扩容、支持快照与跨可用区复制。适用的场景:本地盘适合追求极致性能、可容忍数据丢失风险(有副本)或作为热数据;云盘适合需要持久、高可用、易扩展的通用数据库生产环境。选型时要根据数据库的 RPO/RTO 目标、性能要求与成本综合决定。

差异本质是"数据靠近计算(低延迟)"还是"数据独立持久(高可用)"。数据库通常用云盘保证持久与高可用,用本地盘做高速缓存或对性能要求极高的场景,并配合副本与备份覆盖丢失风险。

#
★★

11. 数据库 Pod 的 IO 隔离与限速(IO Throttle)

如何对数据库 Pod 做 IO 隔离与限速(IO Throttle)?

  • 容器资源限制(CPU/内存)对 IO 的影响
  • IO 限速机制(cgroup blkio、卷级 QoS)
  • 数据库对 IO 稳定的要求

数据库对 IO 的稳定性要求高,IO 抖动会直接影响延迟与吞吐。Kubernetes 中对数据库 Pod 的 IO 隔离与限速可以从几方面实现:1)在容器层面设置 CPU/内存 requests 与 limits,避免因 CPU 超卖导致的 IO 排队;2)通过 cgroup 的 blkio 子系统对整块块设备做 IOPS/带宽限速(如 --device-write-iops--device-write-bps),但 k8s 原生不直接暴露,多依赖运行时参数或 CSI 存储驱动提供的 QoS 能力;3)利用 CSI 卷的 QoS 参数(如云盘类型 gp3 的 IOPS provisioned、云盘 burst 能力)在存储层面按卷隔离;4)通过 PriorityClass 与资源配额保证数据库 Pod 的优先级。限速的目的是防止"吵闹邻居"(noisy neighbor)影响数据库的 IO,同时为数据库设置合理的 IO 上限,避免突发流量打爆底层存储。对数据库而言,应保证其 IO 有确定性保障(QoS 预留),并监控 IO 延迟与排队深度。

IO 隔离的关键是"确定性":数据库需要可预期的 IO 延迟。比例 CPU 配额、IOPS 预留、blkio 限速、CSI 存储 QoS 是常用手段,配合监控(如 IO wait、队列深度)来验证。

resources:
  requests:
    cpu: "4"
    memory: 16Gi
  limits:
    cpu: "8"
    memory: 24Gi
# 用 cgroup blkio 对接口限速(示例)
docker run --device-read-bps /dev/sdb:50mb --device-write-bps /dev/sdb:50mb mysql
#
★★

12. K8s 网络策略(NetworkPolicy)对数据库访问控制

如何使用 K8s NetworkPolicy 对数据库访问做控制?

  • NetworkPolicy 的 Ingress/Egress 规则
  • 基于 Pod 标签/命名空间/IP 的隔离
  • 数据库最小化网络暴露

NetworkPolicy 通过声明式规则控制 Pod 之间的网络访问,是数据库网络隔离的重要手段。它通过 podSelector 选择目标 Pod,通过 ingress/egress 规则指明允许的来源/目的。对数据库而言,常见做法是:只允许应用 Pod(带特定标签)访问数据库 Pod 的 3306/5432 端口,拒绝其他来源;数据库 Pod 的 egress 限制为仅允许访问备份存储、监控端点等。默认情况下,若没有 NetworkPolicy,Pod 间流量全开放;创建 NetworkPolicy 后,未匹配的流量默认被拒绝(在支持该语义的 CNI 下)。实现上依赖 CNI(如 Calico、Cilium)来执行规则。数据库最小化暴露:只开应用所需的端口,限制来源到特定命名空间或标签,必要时对管理端口(如 6379 哨兵)单独限制。这能显著降低横向攻击面。

NetworkPolicy 是"零信任"落地的一部分:默认拒绝 + 显式允许。数据库是核心资产,应确保只有授权的应用与服务能访问,并配合 ServiceAccount、RBAC 一同构成纵深防御。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-app
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306
#
★★

13. 数据库 Service(Headless/ClusterIP)与发现

数据库的 Service 类型(Headless / ClusterIP)各自的作用与发现机制是什么?

  • ClusterIP Service 的负载均衡与 VIP 发现
  • Headless Service 的稳定 DNS 与直连 Pod
  • 数据库场景下两者的分工

ClusterIP Service 为后端 Pod 提供虚拟 IP(VIP)与负载均衡,客户端通过 Service 的 DNS 名访问,kube-proxy 转发到 Pod。它适合"无状态、随机访问任意后端"的场景。Headless Service(clusterIP: None)不分配 VIP,DNS 直接返回所有后端 Pod 的 IP 列表,客户端可自行选择与直连,同时每个 Pod 获得稳定的 DNS 名(pod-name.service-name)。数据库场景中:操作/读写入口通常用 ClusterIP Service(客户端固定访问一个稳定地址,由主库 Pod 承载);节点间复制与主从发现、以及 StatefulSet 的稳定身份则用 Headless Service,让数据库节点通过稳定的 DNS 名互相发现(如 pg-0.pg-svc)。读流量若需直连从库,也可用 Headless Service 让客户端按需选择。发现机制:Client-DNS 解析(ClusterIP)或 DNS 返回 Pod IP 列表(Headless),配合端点(Endpoints)自动更新。

关键区分:ClusterIP 给"客户端一个稳定入口 + 负载均衡",Headless 给"各 Pod 稳定身份 + 直连"。数据库既需要稳定入口(主库)也需要稳定身份(复制/发现),故两类常并用。

apiVersion: v1
kind: Service
metadata:
  name: mysql-primary
spec:
  selector: { app: mysql, role: primary }
  ports: [{ port: 3306 }]
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector: { app: mysql }
  ports: [{ port: 3306 }]
#
★★

14. 节点亲和(Affinity)与反亲和避免同 AZ 共置

如何使用节点亲和(Affinity)与反亲和避免数据库实例共置在同一故障域(AZ)?

  • nodeAffinity 与 nodeSelector 的定向调度
  • podAntiAffinity 的分散调度
  • 跨 AZ 高可用的拓扑

节点亲和(nodeAffinity)用于将 Pod 调度到具备特定标签的节点(如 zone=az-a),实现"定向部署";podAntiAffinity(反亲和)用于让同一组 Pod 不落在同一节点/同一故障域,实现"分散部署"。对数据库高可用,常组合使用:用 podAntiAffinity 配置 topologyKey: topology.kubernetes.io/zone,让主从副本分散到不同可用区,避免单 AZ 故障导致集群整体不可用;用 nodeAffinity 或 nodeSelector 把数据库 Pod 固定到具备特定硬件(如 SSD)或特定区域的节点。反亲和有"required"(硬性,强制不共置)与"preferred"(软性,尽量)两种,分别对应调度约束与优化。通过跨 AZ 分散主从副本,即使某个 AZ 整体故障,其他 AZ 的副本仍可接管,实现区域级高可用。

高可用的核心是"故障域隔离"。把数据库副本放在不同节点、不同 AZ,才能让节点故障、AZ 故障都不导致整体不可用。反亲和 + 拓扑键(topologyKey)就是把"分散"落到具体故障域维度的关键。

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels: { app: mysql }
        topologyKey: topology.kubernetes.io/zone
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - { key: node-type, operator: In, values: ["db"] }
#
★★

15. Pod 拓扑分布约束(Topology Spread)

什么是 Pod 拓扑分布约束(Topology Spread Constraints)?如何用于数据库?

  • topologySpreadConstraints 的字段与作用
  • 与反亲和的区别
  • 数据库均衡分布

Pod 拓扑分布约束(topologySpreadConstraints)用于让一组 Pod 在指定拓扑域(如节点、可用区)上尽量均衡分布,避免"扎堆"在少数节点或区域。它通过 topologyKey(如 kubernetes.io/hostnametopology.kubernetes.io/zone)定义拓扑域,maxSkew 控制允许的最大不均衡程度,whenUnsatisfiable 决定不满足时是拒绝(DoNotSchedule)还是尽量调度(ScheduleAnyway)。与反亲和相比:反亲和是"避免相同 Pod 落在同一拓扑域"(针对单个 Pod 的分散),而拓扑分布约束是"让整组 Pod 在拓扑域上均匀分布"(考虑全局均衡)。对数据库,可用拓扑分布约束让副本跨节点、跨 AZ 均衡,同时配合反亲和保证主从不共置,从而实现既均衡又高可用的部署。

拓扑分布约束着眼于"整体均衡",反亲和着眼于"互斥分散"。数据库常两者结合:拓扑分布约束保证集群利用均衡,反亲和保证关键副本不共置。

spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels: { app: mysql }
#
★★

16. 数据库 Pod 故障的探测(Liveness/Readiness)

如何为数据库 Pod 配置 Liveness 与 Readiness 探针?

  • LivenessProbe 与 ReadinessProbe 的区别
  • 数据库探测命令/端口 SQL 探测
  • 探针参数与误判风险

LivenessProbe 判断 Pod 是否存活,失败则 kubelet 重启容器;ReadinessProbe 判断 Pod 是否就绪、能否接收流量,失败则从 Service 端点摘除,但不重启容器。对数据库:Liveness 探针应避免过于激进(如一条简单的 SELECT 1 或检查进程存活),否则瞬时负载或锁等待会导致误判重启,破坏数据一致性;Readiness 探针应真实反映"能否接受客户端请求",常用 SELECT 1mysqladmin ping,也可检查主从状态、复制延迟等。探针通过 TCP 端口或执行容器内命令(如 mysqladmin ping)实现。关键参数:initialDelaySeconds(启动后等待)、periodSeconds(间隔)、timeoutSeconds(超时)、failureThreshold(连续失败次数)。对数据库,多用 Readiness 而非频繁重启的 Liveness,且 Liveness 的失败阈值要放宽,避免主库短暂繁忙被误杀。

探测的本质是"区分进程死与暂时不可用"。数据库尤其要谨慎:用 Readiness 摘流、用宽松的 Liveness 兜底,探测命令要轻量(如 SELECT 1),避免高负载下探针超时误判导致主库被重启。

readinessProbe:
  exec:
    command: ["mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p$$MYSQL_ROOT_PASSWORD"]
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3
livenessProbe:
  exec:
    command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
  initialDelaySeconds: 60
  periodSeconds: 30
  timeoutSeconds: 5
  failureThreshold: 5
#
★★

17. Pod 驱逐(Eviction)与数据库优雅关闭

数据库 Pod 被驱逐(Eviction)时如何优雅关闭,避免数据损坏?

  • 驱逐触发原因(节点压力、抢占、节点维护)
  • 优雅关闭机制(preStop 钩子、SIGTERM、terminationGracePeriod)
  • 数据库数据一致性

Pod 被驱逐(如节点资源不足、节点维护、抢占)时,kubelet 会先发送 SIGTERM 再在宽限期后 SIGKILL。数据库需要优雅关闭:在 SIGTERM 时执行 flush、checkpoint、干净关闭(如 MySQL 的 shutdown、PostgreSQL 的 pg_ctl stop -m fast),确保数据落盘一致。实现方式:1)preStop 钩子:在容器终止前执行清理脚本(如 mysqladmin shutdown 或等待从库追平);2)terminationGracePeriodSeconds:给足优雅关闭时间;3)容器主进程正确处理 SIGTERM 并执行优雅退出。此外,数据库 Operator 常通过 preStop 实现"先摘流、再排空、再关闭",把该副本从 Service 端点移除、等待在途事务结束,再实施关闭。对主库被驱逐,Operator 会先触发 failover 让从库接管,再接走主库,避免数据丢失与脑裂。

优雅关闭的核心是"先把流量摘掉、让数据一致、再关进程"。驱逐是 Kubernetes 主动终止,必须通过 preStop + 宽限期 + SIGTERM 处理实现干净退出,否则可能损坏日志或产生不一致。

containers:
- name: mysql
  image: mysql:8.0
  lifecycle:
    preStop:
      exec:
        command: ["sh", "-c", "mysqladmin shutdown 2>/dev/null || true"]
terminationGracePeriodSeconds: 120
#
★★

18. Operator 驱动的自动化 failover 与脑裂防护

Operator 如何驱动自动化 failover 并防止脑裂(split brain)?

  • failover 触发条件与新旧主切换
  • 脑裂的成因与防护(仲裁、主降级、fencing)
  • 存储 fencing 与防双写

Operator 驱动的 failover 流程:通过探针或健康检查发现主库不可用,在确认后从候选从库中选出新主(通常选复制延迟最小、数据最新的从库),提升其为新主,并更新 Service 端点与客户端路由。脑裂防护是核心难点:当主库"假死"(网络分区但进程仍活着)时,若直接把从库提升为新主,可能两个主同时写同一数据卷,造成双写与数据损坏。防护手段包括:1)仲裁与共识:用 etcd/leader election 保证"同一时刻只有一个 leader";2)fencing(隔离):在提升新主前,先断电/隔离旧主(如重启、关闭其网络、或用存储 fencing 使旧主无法写入共享卷),确保旧主不能继续写;3)读原始数据确认:检查旧主是否已持久化最新数据,避免丢数据。对共享存储架构,存储 fencing(如 forcing 旧主挂载点失效)是关键;对非共享存储(每实例独立 PV),需通过复制链与 WAL 确保新主数据不落后于旧主。

脑裂防护的本质是"保证同一时刻只有一个可写主"。Operator 通过"选举 + 隔离(fencing)+ 数据确认"三层防护,牺牲一点可用性换取一致性与数据安全,这是高可用数据库的标配。

#
★★

19. 数据库在 k8s 的 RPO/RTO 实践

数据库在 Kubernetes 上的 RPO/RTO 实践如何设计与实现?

  • RPO(数据丢失容忍)/RTO(恢复时间目标)定义
  • 主从复制、备份、恢复对 RPO/RTO 的影响
  • 在 k8s 上的落地手段

RPO(Recovery Point Objective)指允许丢失的数据量/时间窗口,RTO(Recovery Time Objective)指从故障到恢复的最长时间。设计上:RPO 由复制与备份策略决定——主从同步复制(还是半同步/异步)决定故障时最多丢多少数据,备份频率决定"恢复点"的粒度;RTO 由恢复自动化程度决定——用备份恢复(分钟级)比用卷快照(秒/分钟级)慢,而用从库接替(秒级)最快。在 k8s 上的落地:1)高可用层用主从复制 + 自动 failover,缩短 RTO(秒级);2)定期备份 + WAL/binlog 归档实现 PITR,控制 RPO(分钟级甚至更小);3)用 VolumeSnapshot 做快照恢复,兼顾 RPO 与 RTO;4)通过 Operator 声明 RPO/RTO 目标,并配置备份调度、复制模式与恢复演练。实践上要"按业务分级":关键业务要求 RPO≈0 用同步复制,一般业务用异步复制 + 定期备份。最后用恢复演练实测 RTO,并持续优化。

RPO/RTO 是"目标",备份/复制/恢复是"手段"。在设计时先定目标,再选择组合:同步/半同步复制 + 高频备份 + 快照 + 自动恢复,从而在成本与可用性间权衡。

#
★★

20. 数据库与 etcd 的选址(避免同故障域)

为什么数据库与 etcd 要避免放在同一故障域,选址如何考虑?

  • etcd 作为 Kubernetes 控制面存储的角色
  • 故障域隔离的意义
  • 站点/区域选址原则

etcd 是 Kubernetes 控制面的核心存储(保存集群全部状态),数据库是业务数据核心。两者避免放在同一故障域(如同节点、同机房、同可用区)的原因:1)避免"单点共命"——若某个故障域(如某可用区)宕机,同时摧毁控制面与业务数据,导致既无法调度/恢复又丢失业务数据;2)隔离故障影响范围,让控制面与数据面互不拖累。选址原则:数据库与 etcd 应有独立的故障域(不同节点、不同机架、不同可用区),etcd 集群自身也跨可用区冗余(一般 3 或 5 节点),数据库副本也跨可用区。这样,即使某个 AZ 故障,控制面仍在(可继续调度与恢复),业务数据副本仍在其他 AZ,可快速恢复。此外,etcd 与数据库对 IO 敏感,也应避免共用同一物理存储造成 IO 争抢。

选址的核心是"故障域隔离 + 冗余"。把控制面与数据面放在不同故障域,是保证"故障时仍能恢复业务"的关键;否则一个故障域可能让恢复能力也同时失效。

#

21. 数据库在 k8s 的延迟敏感型调度

如何为延迟敏感型数据库在 k8s 上做调度优化?

  • 资源请求与节点选择
  • 干扰与噪声邻居控制
  • 专用节点与 QoS

延迟敏感型数据库的调度优化要点:1)设置充足的 CPU/内存 requests 与 limits,保证 QoS(Guaranteed 优先),避免被 OOM 或 CPU 抢占;2)用 nodeSelector/nodeAffinity 把数据库调度到高性能、低延迟的节点(如 SSD、专用数据库节点、实时节点);3)避免与高负载的"吵闹邻居"共置,用专用节点池或 podAntiAffinity 隔离;4)考虑 CPU 管理(如 CPU Manager 的 exclusive CPUs、静态策略)避免 CPU 抖动;5)对 NUMA 敏感场景绑定 NUMA 拓扑;6)用拓扑分布约束/反亲和控制故障域。调度时要考虑 Pod 启动时间、存储拓扑(本地盘需调度到含 PV 的节点)、以及网络延迟(同可用区就近)。延迟敏感型数据库尽量避免"尽力而为"的共享节点,优先保证资源确定性。

延迟敏感调度的核心是"确定性资源 + 隔离干扰"。用 QoS=Guaranteed、专用节点、CPU 亲和与拓扑感知,把数据库与不确定负载隔离,从而稳定延迟。

#

22. k8s 上数据库的主备切换与端点不变

在 k8s 上数据库主备切换时如何保证客户端访问端点不变?

  • Service 作为稳定端点
  • 主备切换时 Service 选择器更新
  • 代理/读写分离对端点的感知

在 k8s 上,客户端通过 Service 的稳定虚拟 IP/DNS 访问数据库,主备切换时只需更新 Service 的 selector(从指向旧主改为指向新主),Service 的 ClusterIP 与 DNS 名保持不变,客户端无需修改配置即可自动连接到新主。实现上:数据库 Operator 在主备切换后,更新 Service 的 selector(或维护独立的"主库 Service"),kube-proxy 更新 Endpoints,使流量无缝切到新主。对读写分离,通常有"主库 Service"与"从库 Service"(或一个 Service 按角色分流),切换时更新相应 Service。客户端连接池(如 HikariCP)会感知连接断开并重连到新端点。关键在于"端点不变"——客户端始终指向同一个 Service,由 Service 层透明地切换到容器的真实后端,从而对客户端实现零感知切换。

端点不变的本质是"用 Service 的稳定逻辑地址抽象后端真实 Pod"。切换时只改 selector/Endpoints,不改变客户端看到的服务地址,实现透明故障转移。

apiVersion: v1
kind: Service
metadata:
  name: mysql-primary
spec:
  selector: { app: mysql, role: primary }   # 切换后改成新主标签
  ports: [{ port: 3306 }]
#

23. 跨可用区部署与 Pod 反亲和的高可用

如何通过跨可用区部署与 Pod 反亲和实现数据库高可用?

  • 跨 AZ 部署副本
  • 反亲和保证副本分散
  • 单 AZ 故障恢复

跨可用区部署 + Pod 反亲和是实现数据库高可用的经典方案。具体:将数据库主从副本分别部署到多个可用区(AZ),用 podAntiAffinity 的 topologyKey: topology.kubernetes.io/zone 强制/尽让副本落在不同 AZ,确保主从不共置在同一故障域。这样当某个 AZ 整体故障(网络、电源、机房)时,其他 AZ 的副本仍存活,Operator 自动故障转移到可用区的副本,业务基本不中断。配合:StorageClass 使用跨 AZ 可用或可用区持久化的存储、Service 跨 AZ 路由、以及副本跨 AZ 的复制。同时要用拓扑分布约束让副本在整个集群均衡,避免多数副本挤在一个 AZ。跨 AZ 还带来复制延迟(网络 RTT),需权衡同步/半同步复制的 RPO 与性能。

跨 AZ 部署把"故障域"从节点提升到可用区,反亲和则把"分散"落到 AZ 维度。这样单 AZ 故障不会导致整体不可用,是生产高可用的标准做法。

#

24. k8s 升级对数据库可用性的影响

Kubernetes 升级对数据库可用性有什么影响,如何应对?

  • 节点升级(重启、驱逐)对数据库 Pod 的影响
  • 控制面升级的兼容性
  • 升级窗口与优雅处理

k8s 升级对数据库的影响主要在节点升级:节点升级通常需要重启、驱逐 Pod,会触发数据库 Pod 的驱逐与重建,若处理不当会造成数据库实例临时不可用、甚至数据风险。应对措施:1)用 PodDisruptionBudget(PDB)约束同一时刻最多可被驱逐的数据库副本数,避免主从同时被驱逐;2)节点升级前先 node drain(排空),通过 preStop 优雅关闭、PDB 允许、以及 Operator 的 failover 让从库接管后再升级主库节点;3)控制面(etcd、kube-apiserver)升级需在维护窗口进行,并核对 Operator 与 API 版本的兼容性;4)升级采用滚动策略,逐节点升级,每步验证。数据库 Operator 通常会感知节点状态或配合 PDB 规划,减少升级对数据库可用性的影响。此外,升级期间应避免缩容/扩缩等操作,并监控复制延迟与主从健康。

k8s 升级主要风险是"节点驱逐导致数据库实例不可用"。核心应对是 PDB 限制 + 有序排空 + 优雅关闭 + 故障转移,保证升级窗口内至少主从有可用副本。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: mysql-pdb
spec:
  minAvailable: 2          # 至少保留 2 个副本可被服务
  selector:
    matchLabels: { app: mysql }