Xinzhao's Blog

Kubernetes 多集群项目介绍

KubernetesMulti-ClusterFederationKarmada

2021-11-03

为什么要使用集群联邦

Kubernetes 从 1.8 版本起就声称单集群最多可支持 5000 个节点和 15 万个 Pod,实际上应该很少有公司会部署如此庞大的一个单集群,很多情况下因为各种各样的原因我们可能会部署多个集群,但是又想将它们统一起来管理,这时候就需要用到集群联邦(Federation)。

集群联邦的一些典型应用场景:

Federation v1

最早的多集群项目,由 K8s 社区提出和维护。

Federation v1 在 K8s v1.3 左右就已经着手设计(Design Proposal),并在后面几个版本中发布了相关的组件与命令行工具(kubefed),用于帮助使用者快速建立联邦集群,并在 v1.6 时,进入了 Beta 阶段;但 Federation v1 在进入 Beta 后,就没有更进一步的发展,由于灵活性和 API 成熟度的问题,在 K8s v1.11 左右正式被弃用。

v1 的基本架构如上图,主要有三个组件:

在 v1 版本中我们要创建一个联邦资源的大致步骤如下:把联邦的所有配置信息都写到资源对象 annotations 里,整个创建流程与 K8s 类似,将资源创建到 Federation API Server,之后 Federation Controller Manager 会根据 annotations 里面的配置将该资源创建到各子集群;下面是一个 ReplicaSet 的示例:

这种架构带来的主要问题有两个:

Federation v2

有了 v1 版本的经验和教训之后,社区提出了新的集群联邦架构:Federation v2;Federation 项目的演进也可以参考 Kubernetes Federation Evolution 这篇文章。

v2 版本利用 CRD 实现了整体功能,通过定义多种自定义资源(CR),从而省掉了 v1 中的 API Server;v2 版本由两个组件构成:

在 v2 版本中要创建一个联邦资源的大致流程如下:

将 Federated Resource 创建到 Host 集群的 API Server 中,之后 controller-manager 会介入将相应资源分发到不同的集群,分发的规则等都写在了这个 Federated Resource 对象里面。

在逻辑上,Federation v2 分为两个大部分:configuration(配置)和 propagation(分发);configuration 主要包含两个配置:Cluster Configuration 和 Type Configuration。

Cluster Configuration

用来保存被联邦托管的集群的 API 认证信息,可通过 kubefedctl join/unjoin 来加入/删除集群,当成功加入时,会建立一个 KubeFedCluster CR 来存储集群相关信息,如 API Endpoint、CA Bundle 和 Token 等。后续 controller-manager 会使用这些信息来访问不同 Kubernetes 集群。

apiVersion: core.kubefed.io/v1beta1
kind: KubeFedCluster
metadata:
creationTimestamp: "2019-10-24T08:05:38Z"
generation: 1
name: cluster1
namespace: kube-federation-system
resourceVersion: "647452"
selfLink: /apis/core.kubefed.io/v1beta1/namespaces/kube-federation-system/kubefedclusters/cluster1
uid: 4c5eb57f-5ed4-4cec-89f3-cfc062492ae0
spec:
apiEndpoint: https://172.16.200.1:6443
caBundle: LS....Qo=
secretRef:
name: cluster1-shb2x
status:
conditions:
- lastProbeTime: "2019-10-28T06:25:58Z"
lastTransitionTime: "2019-10-28T05:13:47Z"
message: /healthz responded with ok
reason: ClusterReady
status: "True"
type: Ready
region: ""

Type Configuration

定义了哪些 Kubernetes API 资源要被用于联邦管理;比如说想将 ConfigMap 资源通过联邦机制建立在不同集群上时,就必须先在 Host 集群中,通过 CRD 建立新资源 FederatedConfigMap,接着再建立名称为 configmaps 的 Type configuration(FederatedTypeConfig)资源,然后描述 ConfigMap 要被 FederatedConfigMap 所管理,这样 Kubefed controller-manager 才能知道如何建立 Federated 资源,一个示例如下:

apiVersion: core.kubefed.k8s.io/v1beta1
kind: FederatedTypeConfig
metadata:
name: configmaps
namespace: kube-federation-system
spec:
federatedType:
group: types.kubefed.k8s.io
kind: FederatedConfigMap
pluralName: federatedconfigmaps
scope: Namespaced
version: v1beta1
propagation: Enabled
targetType:
kind: ConfigMap
pluralName: configmaps
scope: Namespaced
version: v1

Federated Resource CRD

其中还有一个关键的 CRD:Federated Resource,如果想新增一种要被联邦托管的资源的话,就需要建立一个新的 FederatedXX 的 CRD,用来描述对应资源的结构和分发策略(需要被分发到哪些集群上);Federated Resource CRD 主要包括三部分:

一个示例如下:

apiVersion: types.kubefed.k8s.io/v1beta1
kind: FederatedDeployment
metadata:
name: test-deployment
namespace: test-namespace
spec:
template: # 定义 Deployment 的所有內容,可理解成 Deployment 与 Pod 之间的关联。
metadata:
labels:
app: nginx
spec:
...
placement:
clusters:
- name: cluster2
- name: cluster1
overrides:
- clusterName: cluster2
clusterOverrides:
- path: spec.replicas
value: 5

这些 FederatedXX CRD 可以通过 kubefedctl enable <target kubernetes API type> 来创建,也可以自己生成/编写对应的 CRD 再创建。

结合上面介绍了的 Cluster Configuration、Type Configuration 和 Federated Resource CRD,再来看 v2 版本的整体架构和相关概念就清晰很多了:

Scheduling

Kubefed 目前只能做到一些简单的集群间调度,即手工指定,对于手工指定的调度方式主要分为两部分,一是直接在资源中制定目的集群,二是通过 ReplicaSchedulingPreference 进行比例分配。

直接在资源中指定可以通过 clusters 指定一个 cluster 列表,或者通过 clusterSelector 来根据集群标签选择集群,不过有两点要注意:

spec:
placement:
clusters:
- name: cluster2
- name: cluster1
clusterSelector:
matchLabels:
foo: bar

如果需要在多个集群间进行区别调度的话就需要引入 ReplicaSchedulingPreference 进行按比例的调度了:

apiVersion: scheduling.kubefed.io/v1alpha1
kind: ReplicaSchedulingPreference
metadata:
name: test-deployment
namespace: test-ns
spec:
targetKind: FederatedDeployment
totalReplicas: 9
clusters:
A:
minReplicas: 4
maxReplicas: 6
weight: 1
B:
minReplicas: 4
maxReplicas: 8
weight: 2

totalReplicas 定义了总副本数,clusters 描述不同集群的最大/最小副本以及权重。

目前 ReplicaSchedulingPreference 只支持 deployments 和 replicasets 两种资源

Karmada

Karmada 是由华为开源的多云容器编排项目,这个项目是 Kubernetes Federation v1 和 v2 的延续,一些基本概念继承自这两个版本。

Karmada 主要有三个组件:

和 Federation v1 类似,我们下发一个资源也是要写入到 Karmada 自己的 API Server 中,之前 controller-manager 根据一些 policy 把资源下发到各个集群中;不过这个 API Server 是 K8s 原生的,所以支持任何资源,不会出现之前 Federation v1 版本中的问题,然后联邦托管资源的分发策略也是由一个单独的 CRD 来控制的,也不需要配置 v2 中的 Federated Resource CRD 和 Type Configure。

Karmada 的一些基本概念:

Cluster

Cluster 资源记录的内容和 Federation v2 类似,就是访问被纳管集群的一些必要信息:API Endpoint、CA Bundle 和访问 Token。

spec:
apiEndpoint: https://172.31.165.66:55428
secretRef:
name: member1
namespace: karmada-cluster
syncMode: Push

但是有一个不一样的点是,Karmada 的 Cluster 资源有两种 sync 模式:PushPullPush 就是最普通、最常见的方式,host 集群的 Karmada 组件会负责同步并更新这类集群的状态;Pull 模式的 member 集群上会运行一个 karmada-agent 组件,这个组件会负责收集自己的状态并且更新 host 集群的相应的 Cluster 资源状态。

Propagation Policy

在 Karmada 中分发资源到 member 集群需要配置这个单独 PropagationPolicy CR;以下面的 nginx 应用为例,首先是 Resource Template,这个就是普通的 K8s Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx

之后配置一个 PropagationPolicy 来控制这个 nginx Deployment 资源的分发策略即可,在下面的示例中,会将 nginx 应用按 1:1 的权重比分发到 member1 和 member2 集群中:

apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2
replicaScheduling:
replicaDivisionPreference: Weighted
replicaSchedulingType: Divided
weightPreference:
staticWeightList:
- targetCluster:
clusterNames:
- member1
weight: 1
- targetCluster:
clusterNames:
- member2
weight: 1

在 Karmada API Server 中创建这两个资源后,可以通过 Karmada API Server 查询到该资源的状态:

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 2/2 2 2 51s

但是注意,这并不代表应用运行在 Karmada API Server 所在的集群上,实际上这个集群上没有任何工作负载,只是存储了这些 Resource Template,实际的工作负载都运行在上面 PropagationPolicy 配置的 member1 和 member2 集群中,切换到 member1/member2 集群中可以看到:

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 6m26s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6799fc88d8-7cgfz 1/1 Running 0 6m29s

分发策略除了上面最普通的指定集群名称外也支持 LabelSelectorFieldSelectorExcludeClusters,如果这几个筛选项都设置了,那只有全部条件都满足的集群才会被筛选出来;除了集群亲和性,还支持 SpreadConstraints:对集群动态分组,按 regionzoneprovider 等分组,可以将应用只分发到某类集群中。

针对有 replicas 的资源(比如原生的 DeploymentStatefulSet),支持在分发资源到不同集群的时候按要求更新这个副本数,比如 member1 集群上的 nginx 应用我希望有 2 个副本,member2 上的只希望有 1 个副本;策略有很多,首先是 ReplicaSchedulingType,有两个可选值:

而这个 ReplicaDivisionPreference 又有两个可选值:

完整和详细的结构可以参考 PlacementAPI 定义

Override Policy

Override Policy 就很简单了,通过增加 OverridePolicy 这个 CR 来配置不同集群的差异化配置,直接看一个例子:

apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
name: example-override
namespace: default
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
targetCluster:
clusterNames:
- member1
labelSelector:
matchLabels:
failuredomain.kubernetes.io/region: dc1
overriders:
plaintext:
- path: /spec/template/spec/containers/0/image
operator: replace
value: 'dc-1.registry.io/nginx:1.17.0-alpine'
- path: /metadata/annotations
operator: add
value:
foo: bar

Demo

最后看一个 Karmada 官方的例子:

参考链接


Powered by ☕️, 🍟 and 🍦