3.14. 多集群编排:Karmada

一个集群能承载的规模有上限,容灾、地域合规和数据隔离又常常要求「不把鸡蛋放在一个篮子里」。Karmada 让你继续用 Deployment、Service 这些原生 API 描述应用,由一份策略决定副本落在哪些集群、每个集群长什么样、某个集群挂掉之后怎么办。

层级:

L3 · 平台

难度:

进阶

预计:

55 分钟

前置:

Deployment、Service、RBAC

3.14.1. 你将学到

  • 多集群要解决的四个问题:容灾、地域、隔离、规模,以及它们对调度策略的影响

  • 控制面组件分工,资源模板、PropagationPolicy、ResourceBinding、Work 的关系

  • Duplicated 与 Divided 两种副本模式,以及 OverridePolicy 的差异化改写

  • 集群故障时副本如何被判定失联、驱逐,并在其它集群重建

3.14.2. 为什么需要它

单集群的边界很实在:etcd 与 apiserver 的写入能力、单集群 5000 节点的推荐上限、一次误操作的影响半径,以及「同城双活 / 异地多活」这类合规要求。当业务跨可用区甚至跨云部署时,问题从「怎么调度 Pod」变成了「怎么调度集群」。主流做法有三种:单集群多命名空间(最简单,但故障域没变)、Cluster API + GitOps(每集群一份清单,成本随集群数线性增长)、多集群联邦控制面(一套 API 和策略管理所有集群,Karmada 属于这一类)。前两种是「分别管理」,第三种是「统一编排」,而且不修改应用:你写的仍是标准 Deployment,只是提交到 karmada-apiserver。

方案

统一 API

跨集群重调度

差异化配置

复杂度

单集群多命名空间

是

不涉及

Namespace 级

低

Cluster API + Argo CD

否,每集群一份

需人工改 Git

Kustomize overlay

中

Karmada

是

内置 failover

OverridePolicy

高

3.14.3. 核心概念

3.14.3.1. 控制面与成员集群

控制面是一套「迷你 apiserver + etcd + 控制器」,通常跑在某个 host cluster 上,自己不运行业务 Pod,只保存资源模板和策略。

架构图

图:模板与策略进 karmada-apiserver,scheduler 决定落到哪些集群,Work 才是真正下发到成员集群的对象。

controller-manager 把策略翻译成 ResourceBinding(命名空间级)或 ClusterResourceBinding(集群级),再拆成每个集群一份 Work;scheduler 决定放置;webhook 做准入校验。成员集群默认 Push 模式(控制面直接写成员集群),跨网络边界可用 karmada-agent 走 Pull 模式。

3.14.3.2. 资源模板与传播策略

提交到 karmada-apiserver 的 Deployment 只是模板,不会自动出现在任何成员集群。让它流动起来的是 PropagationPolicy:resourceSelectors 选中资源(支持 apiVersion、kind、namespace、name、labelSelector),placement 决定去哪,propagateDeps 决定是否连带分发 Service、ConfigMap 等依赖。

02-demoapp-propergation-policy.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata: {name: propagationpolicy-demo}
spec:
  resourceSelectors:
    - {apiVersion: apps/v1, kind: Deployment, name: demoapp}
  placement:
    clusterAffinity: {clusterNames: [kube02, kube03]}   # 目标成员集群
  propagateDeps: true

策略与资源是多对多匹配:一个策略可选中多个资源,一个资源也可能被多个策略选中——后者按 spec.priority 仲裁,同优先级下最早创建的生效。实验时「改了策略却没效果」,十有八九是旧策略还在。

3.14.3.3. Duplicated 与 Divided

replicaSchedulingType 决定副本怎么分:

  • Duplicated:每个目标集群拿到完整副本数。模板 replicas: 4 + 两个集群 = 8 个 Pod,适合「两地都全量服务」的容灾形态。

  • Divided:把 replicas 拆开分给各集群。4 个副本按权重 3:1 分成 3 和 1,适合「总量固定、按容量分摊」。

replicaDivisionPreference: Weighted 配合 weightPreference.staticWeightList 指定静态权重,不写则平均分配。spreadConstraints 进一步约束拓扑:spreadByField 可选 region、zone、provider、cluster,minGroups / maxGroups 控制铺开几组;cluster 维度是其它维度的前提,不按集群铺开就无从谈「每个 region 里选几个集群」。

3.14.3.4. OverridePolicy:同一份模板,不同集群不同样子

OverridePolicy 在分发前改写资源,常用于「测试集群用小 tag」「边缘集群加 nodeSelector」「某集群副本减半」。它支持 imageOverrider、plaintextOverrider(按 JSONPath 改任意字段)、commandOverrider、labelsOverrider 等,targetCluster 限定生效范围。注意 resourceSelectors 里同时写 name 和 labelSelector 时后者被忽略,仓库 YAML 的注释就是提醒这一点。

3.14.3.5. 故障转移

Karmada 给不健康的成员集群打上 cluster.karmada.io/not-ready 与 cluster.karmada.io/unreachable 污点,工作负载通过 clusterTolerations 声明能忍多久:

机制

位置

作用

集群污点

成员集群对象

控制面按心跳自动打上

clusterTolerations

placement

容忍时长,决定多久后开始驱逐

failover.application

策略

触发条件、宽限期、purgeMode 驱逐方式

purgeMode: Graciously 是「等新副本就绪或宽限期超时再删旧的」,Directly 直接删。生产上几乎总该选前者,否则故障瞬间会出现容量缺口。

3.14.3.6. 与 Cluster API、Argo CD 的分工

三者不是竞品:Cluster API 创建、升级、伸缩集群本身;Karmada 决定应用放在哪些集群、每个集群长什么样、集群挂了怎么办;Argo CD / Flux 把 Git 里的期望状态同步到集群,负责交付而非放置。常见组合是 Argo CD 只对 karmada-apiserver 同步、Karmada 向成员集群扇出;ApplicationSet 也能扇出,但没有重调度与故障转移能力,集群失联时只能报 Unknown。

3.14.4. 动手实验

实验:把 demoapp 分发到两个成员集群并演练故障转移

目标:用 Karmada 控制面创建 Deployment,分发到 kube02 / kube03,做差异化,最后模拟集群故障观察副本迁移。

前置:一个 host 集群 + 两个成员集群;karmadactl 已安装;能访问仓库 karmada/ 下的 YAML。

3.14.4.1. 步骤 1 · 初始化控制面并接入成员集群

karmadactl init          # 在 host 集群部署 karmada-apiserver、etcd、控制器
karmadactl join kube02 --cluster-kubeconfig=$HOME/.kube/kube02.config
karmadactl join kube03 --cluster-kubeconfig=$HOME/.kube/kube03.config
kubectl --kubeconfig /etc/karmada/karmada-apiserver.config get clusters
NAME     VERSION   MODE   READY   AGE
kube02   v1.31.0   Push   True    3m
kube03   v1.31.0   Push   True    2m

后面所有命令都要带 --kubeconfig=/etc/karmada/karmada-apiserver.config(仓库 README 的写法,下文用变量 K 指代)。用成员集群的 kubeconfig 会报 no matches for kind "PropagationPolicy",因为那些 CRD 只存在于控制面。

3.14.4.2. 步骤 2 · 创建模板并分发

01-demoapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata: {name: demoapp, namespace: default}
spec:
  replicas: 4
  selector: {matchLabels: {app: demoapp}}
  template:
    metadata: {labels: {app: demoapp}}
    spec:
      containers:
        - {name: demoapp, image: "ikubernetes/demoapp:v1.0"}

只提交 Deployment 时,kubectl get deploy 会显示 READY 0/0——模板存在但没有被分发;成员集群里根本查不到这个 Deployment。加上策略后才真正下发。下面的 04 是带 spreadConstraints 的完整版,前面 02 只是最小示例:

K="--kubeconfig=/etc/karmada/karmada-apiserver.config"
kubectl apply -f 01-demoapp-deployment.yaml $K
kubectl apply -f 04-propagationpolicy-ha.yaml $K   # 完整版:Duplicated + spreadConstraints
karmadactl get deployment $K
NAME      CLUSTER   READY   UP-TO-DATE   AVAILABLE   AGE
demoapp   kube02    4/4     4            4           45s
demoapp   kube03    4/4     4            4           45s

karmadactl get 的 CLUSTER 列把同一份模板在各成员集群的副本摊开显示,是验证分发结果最快的方式。04 里的 spreadConstraints 做了两件事:spreadByField: region 要求铺到 2 个 region,spreadByField: cluster 限定每个 region 只选 1 个集群。

3.14.4.3. 步骤 3 · 用 OverridePolicy 做差异化

03-demoapp-override-policy.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata: {name: overridepolicy-demo}
spec:
  resourceSelectors:
    - {apiVersion: apps/v1, kind: Deployment, name: demoapp}   # 写了 name 就不看 labelSelector
  overrideRules:
    - targetCluster: {clusterNames: [kube03]}
      overriders:
        imageOverrider:
          - {component: Tag, operator: replace, value: "v1.1"}   # 只在 kube03 换 tag
kubectl apply -f 03-demoapp-override-policy.yaml $K
kubectl --kubeconfig ~/.kube/kube02.config -n default get deploy demoapp -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
kubectl --kubeconfig ~/.kube/kube03.config -n default get deploy demoapp -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

kube02 上镜像 tag 仍是 v1.0,kube03 上已经变成 v1.1——同一份模板、两个镜像,这就是「不做多套 Git 分支也能做环境差异化」;想按集群改副本数,用 plaintextOverrider 指定 path: /spec/replicas。

3.14.4.4. 步骤 4 · Divided 拆分与故障转移

06-propagationpolicy-failover.yaml
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata: {name: propagationpolicy-failover-demo}
spec:
  failover:
    # Graciously 表示先建后删:等新副本就绪或宽限期超时才驱逐
    application: {decisionConditions: {tolerationSeconds: 60}, gracePeriodSeconds: 120, purgeMode: Graciously}
  resourceSelectors:
    - {apiVersion: apps/v1, kind: Deployment, name: demoapp}
  placement:
    clusterTolerations:
      - {key: cluster.karmada.io/not-ready, operator: Exists, effect: NoExecute, tolerationSeconds: 60}
      - {key: cluster.karmada.io/unreachable, operator: Exists, effect: NoExecute, tolerationSeconds: 60}
    replicaScheduling:
      replicaSchedulingType: Divided
      replicaDivisionPreference: Weighted
      weightPreference:
        staticWeightList:
          - {targetCluster: {clusterNames: [kube02]}, weight: 3}
          - {targetCluster: {clusterNames: [kube03]}, weight: 1}
  propagateDeps: true

这条策略把 replicaSchedulingType 换成 Divided 并给了 3:1 的静态权重,所以 4 个副本会拆成 3 和 1;同时用两条 clusterTolerations 声明对失联污点的容忍时长。

kubectl apply -f 06-propagationpolicy-failover.yaml $K
ssh kube03-node 'systemctl stop kubelet'   # 模拟 kube03 彻底失联
kubectl --kubeconfig /etc/karmada/karmada-apiserver.config get clusters
karmadactl get deployment $K
NAME      CLUSTER   READY   UP-TO-DATE   AVAILABLE   AGE
demoapp   kube02    4/4     4            4           6m

kube03 的 READY 变成 False 后并不会立刻驱逐:tolerationSeconds: 60 到期、decisionConditions 满足,副本才被重新调度到 kube02 补齐到 4。failover.application 是较新版本引入的字段,用 kubectl explain propagationpolicy.spec.failover 确认你的 CRD 支持哪一版。

3.14.4.5. 验证清单

  • 两个成员集群 READY=True,kubectl get clusters 显示 MODE=Push

  • Duplicated 下两个集群各 4 副本,总数是模板的 2 倍

  • Divided 3:1 下两个集群分别是 3 和 1,总数等于模板 replicas

  • OverridePolicy 生效后 kube03 是 v1.1,kube02 仍是 v1.0

  • kube03 失联后副本在 kube02 补齐到 4

3.14.5. 常见问题与排错

现象

可能原因

处理方式

资源创建成功但成员集群没有

没有匹配的 PropagationPolicy

kubectl get pp、kubectl get rb 检查绑定;resourceSelectors 的 apiVersion 必须是 apps/v1

报 no matches for kind PropagationPolicy

用了成员集群的 kubeconfig

所有策略操作加 --kubeconfig=/etc/karmada/karmada-apiserver.config

Work 一直 Pending

成员集群 NotReady 或接入凭据过期

kubectl -n karmada-es-kube02 get work -o yaml 看 conditions,必要时重跑 karmadactl join

改了策略但副本数没变

多条策略同时选中同一资源

kubectl get pp -o yaml 查 priority,删掉不用的策略

OverridePolicy 不生效

name 与 labelSelector 并存

二选一;按名字选资源时删掉 labelSelector

故障转移不触发

集群只是短暂抖动,未到 tolerationSeconds

加大故障注入强度(停 kubelet);核对两条 taint 拼写

3.14.6. 进阶挑战

  1. 用 clusterAffinity.labelSelector(按 region=beijing)替代 clusterNames,再接入一个同标签集群,观察它是否被自动纳入分发。

  2. 用 plaintextOverrider 给 kube03 注入 nodeSelector 与 tolerations,让它在只有污点的边缘节点上运行。

  3. 写一个 ClusterPropagationPolicy 分发 Namespace,比较它与命名空间级策略的差别。

3.14.7. 小结

  • Karmada 用一套 API 管理多集群:模板进 karmada-apiserver,策略决定放置,Work 负责下发。

  • Duplicated 是「每地全量」,Divided 是「总量拆分」;spreadConstraints 控制拓扑铺开的组数。

  • 故障转移靠集群污点、clusterTolerations、failover.application 三者配合,Graciously 才能先建后删。

3.14.8. 延伸阅读