Kubernetes多集群管理之路

2022-11-28 17:33:59 浏览数 (1)

随着Kubernetes在企业中的应用愈发广泛、普及,越来越多的公司开始在生产环境中运维多个Kubernetes集群。本文主要讲述了一些对于Kubernetes多集群管理的思考,包括为什么需要多集群、多集群的优势以及现有的一些基于Kubernetes衍生出的多集群管理架构。

作者:王琦, 中国移动云能力中心软件开发工程师,专注于云原生、Istio、微服务、Spring Cloud 等领域。

01

Kubernetes集群联邦

对于Kubernetes单集群,官方声称可以支持5000个节点和15万个Pod。但在实际的应用中,很少有公司会选择部署如此庞大的一个单集群。相反,可能更多的还是会选择部署多个集群。对于多集群的使用场景而言,如何对这些集群进行统一的管理,则是集群联邦(Federation)架构产生的原因。

1.1 为什么需要多集群?

l单集群负载能力有限:Kubernetes从v1.8版本开始已经可以具备了上述5000节点、15万Pod的负载能力。而衍生至今,最新的v1.25版本在单集群承载能力方面却并没有太大的变化,可想而知,社区并没有将单集群负载能力的提升作为其发展的重心。原因我想也很简单,如果实际的业务场景需要5000 的节点,甚至更多,那么不管单集群的性能如何加强,在可扩展性方面终究还是存在固有的短板。

l多云(Multi-Clund)和混合云(Hybird Cloud)的实际业务场景:随着云原生技术的不断发展,以及为了避免独立供应商带来的锁定、成本等问题,企业基于多云和混合云架构搭建多集群使用场景的方式愈发普遍。其实这不难理解,谁也不想只把“鸡蛋”放在同一个“篮子”里,而生产环境需要具备高可用的能力似乎也已经成为了业界既定的标准。

1.2 集群联邦概念

Kubernetes在设计之初并非是为了多集群的场景,而面对多集群分布式的使用需求,社区从v1.3版本开始着手设计,集群联邦的概念也应运而生。通过集群联邦可以实现对多个Kubernetes集群进行管理,其架构主要围绕以下两个模块来进行构建:

l跨集群资源同步:处于集群联邦中的所有集群具备彼此间保持资源同步的能力,即多个集群之间可以分布负载。比如,可以实现同一个Deployment资源被分发到多个或者全部联邦集群并同时部署服务。

l跨集群服务发现:集群联邦提供动态的DNS配置服务,并具有对所有联邦集群进行负载均衡的能力。比如,可以通过以一个DNS记录或者全局虚拟IP的方式访问多个或者全部的联邦集群。

以上两个模块的架构给予了集群更大范围的高可用能力,最大限度的减少了集群故障带来的影响。同时,因具备了跨集群应用迁移的能力,可以使得集群的搭建不再局限于某一厂商,从根本上也杜绝了集群厂商锁定带来的影响。

1.3 集群联邦演进

集群联邦一直是Kubernetes社区非常重视的功能之一,Federatioin设计的目的也是希望可以实现一种单一集群统一管理多个Kubernetes集群的机制,这些集群可以是跨区域的,也可以是不同云厂商的,甚至是用户本地自建的。只要他们加入到联邦集群中,就可以利用Federation API资源来统一管理多个集群的Kubernetes API资源,这带来了很多好处,比如:

l简化了对多个Kubernetes集群资源的管理,比如Deployment、Service等。

l提升了应用服务的可靠性,应用的工作负载不再局限于某个单集群,而是可以均匀的分散在多个联邦集群中。并且具备了跨集群资源编排的能力,可以根据预先制定的编排策略将应用部署至联邦集群。

l实现了跨集群的服务发现,应用服务可以快速、便捷的在联邦集群中完成迁移。可以很好的适配多云、混合云的实际业务场景。

02

K8s Federation v1

我们必须知道的是,虽然Kubernetes社区很早就已经实现了Federation v1的机制,发布了相关组件和命令行工具(kubefed),并在v1.6版本时正式推动其进入Beta阶段。但在那之后,Federation v1发展便没有再进一步,关于原因,我会在接下来的部分进行说明。

2.1 Federation v1架构

图1 Kubernetes Federation v1架构

从架构图可以看出,Federation v1版本与Kubernetes控制平面原生的架构相似,其主要包括四个组件:

lFederation API Server:提供统一的资源管理入口,功能类似Kubernetes API,但只允许使用 Adapter 拓展支持的Kubernetes API资源。

lFederation Controller Manager:提供多个联邦集群间的资源调度,协调不同集群之间的状态。

lKubefed:Federation CLI集群管理工具,用于将一个Kubernetes集群Join/UnJoin到联邦集群中。

lEtcd:存储Federation相关的资源对象,用于Control Plane同步状态。

2.2 Kubefed集群管理

通过调用Federation API Server创建和维护所有联邦集群的资源,数据被持久化存储在Federation的ETCD中。需要注意的是,宏观上的中央管控集群只负责维护和规划整个联邦环境,而实际提供应用服务的应是加入进来的所有联邦集群(KubeFedCluster)。

2.2.1 Kubefed集群资源定义

代码语言:javascript复制
apiVersion: core.kubefed.io/v1beta1
kind: KubeFedCluster
metadata:
  creationTimestamp: "2022-11-14T11:34:44Z"
  generation: 1
  labels:
    ... ...
  name: bbf93fd3-921b-488a-a06b-ceff037cb923
  namespace: kube-federation-system
  resourceVersion: "115683914"
  uid: 39950081-c5d1-4eeb-a69c-6892149142b2
spec:
  apiEndpoint: https://127.0.0.1:6443
  caBundle: xxx
  secretRef:
    name: bbf93fd3-921b-488a-a06b-ceff037cb923-secret
status:
  conditions:
  - lastProbeTime: "2022-11-14T21:21:38Z"
    lastTransitionTime: "2022-11-14T11:34:53Z"
    message: /healthz responded with ok
    reason: ClusterReady
    status: "True"
    type: Ready

KubeFedCluster相关的结构体被定义在/pkg/apis/core/kubefedcluster_types.go里,具体内容如下:

代码语言:javascript复制
type KubeFedClusterSpec struct {
    APIEndpoint string `json:"apiEndpoint"`
    CABundle []byte `json:"caBundle,omitempty"`
    SecretRef LocalSecretReference `json:"secretRef"`
    DisabledTLSValidations []TLSValidation `json:"disabledTLSValidations,omitempty"`
}
 
type KubeFedClusterStatus struct {
    Conditions []ClusterCondition `json:"conditions"`
    Zones []string `json:"zones,omitempty"`
    Region *string `json:"region,omitempty"`
}
 
type ClusterCondition struct {
    Type common.ClusterConditionType `json:"type"`
    Status apiv1.ConditionStatus `json:"status"`
    LastProbeTime metav1.Time `json:"lastProbeTime"`
    LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"`
    Reason *string `json:"reason,omitempty"`
Message *string `json:"message,omitempty"`
}

其中,common.ClusterConditionType表示联邦集群和中心管控集群的链接状态,并通过LastProbeTime记录最近一次的探针检查时间。

2.2.2 Kubefed集群Client

Kubefed通过集群Client监听不同集群的状态,并以此实现联邦集群的信息同步,相关结构体定义在/pkg/controller/kubefedcluser/clusterclient.go里,具体如下:

代码语言:javascript复制
type ClusterClient struct {
    kubeClient  *kubeclientset.Clientset
    clusterName string
}

简单来说,只是在一个用于集群访问的ClientSet基础上添加了一个集群名称用于标记具体的联邦集群,而在ClusterClient初始化时则会添加操作用于获取联邦集群更多的信息。

代码语言:javascript复制
func NewClusterClientSet (c *fedv1b1.KubeFedCluster, client generic.Client, fedNamespace string, timeout time.Duration) (*ClusterClient, error) {
    clusterConfig, err := util.BuildClusterConfig(c, client, fedNamespace)
    if err != nil {
        return nil, err
    }
    clusterConfig.Timeout = timeout
    var clusterClientSet = ClusterClient{clusterName: c.Name}
    if clusterConfig != nil {
        clusterClientSet.kubeClient = kubeclientset.NewForConfigOrDie((restclient.AddUserAgent(clusterConfig, UserAgentName)))
        if clusterClientSet.kubeClient == nil {
            return nil, nil
        }
    }
    return &clusterClientSet, nil
}

其中,BuildClusterConfig用于从上述KubeFedCluster资源中获取相关的配置参数,并最终生成一个可以访问联邦集群的REST Client。而对于ClusterClient如何获取集群的状态信息,Kubefed也给出了相关的定义和方法,具体如下。

代码语言:javascript复制
func (self *ClusterClient) GetClusterHealthStatus() (*fedv1b1.KubeFedClusterStatus, error) {
    body, err := self.kubeClient.DiscoveryClient.RESTClient().Get().AbsPath("/healthz").Do().Raw()
    if err != nil {
        runtime.HandleError(errors.Wrapf(err, "Failed to do cluster health check for cluster %q", self.clusterName))
        clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterOfflineCondition)
        metrics.RegisterKubefedClusterTotal(metrics.ClusterOffline, self.clusterName)
    } else {
        if !strings.EqualFold(string(body), "ok") {
            metrics.RegisterKubefedClusterTotal(metrics.ClusterNotReady, self.clusterName)
            clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterNotReadyCondition, newClusterNotOfflineCondition)
        } else {
            metrics.RegisterKubefedClusterTotal(metrics.ClusterReady, self.clusterName)
            clusterStatus.Conditions = append(clusterStatus.Conditions, newClusterReadyCondition)
        }
    }
    return &clusterStatus, err
}

可以看到,Kubefed先是通过上述的REST Client获取联邦集群的健康状态,再根据获取到的状态信息回写入集群Condition中。

2.2.3 Kubefed集群控制器

Kubefed在/pkg/controller/kubefedcluser/controller.go中定义了集群数据的结构体ClusterData,用于存放联邦集群客户端、集群状态等变量。而ClusterController则是最重要的控制器结构体,其内含了一个基于Cache Controller的集群控制器用于注册事件回调,并增加了clusterDataMap和fedNamespace用于存储多个节点的集群状态信息和记录Kubefed位于中心管控集群的具体命名空间,具体如下。

代码语言:javascript复制
type ClusterData struct {
    clusterKubeClient *ClusterClient
    clusterStatus *fedv1b1.KubeFedClusterStatus
    resultRun int64
cachedObj *fedv1b1.KubeFedCluster
}
 
type ClusterController struct {
    client genericclient.Client
    clusterHealthCheckConfig *util.ClusterHealthCheckConfig
    mu sync.RWMutex
    clusterDataMap map[string]*ClusterData
    clusterController cache.Controller
    fedNamespace string
    eventRecorder record.EventRecorder
}

其中,clusterHealthCheckConfig是一个健康检测的配置项,可以用于设置控制器的检测周期、连接数以及超时时间。接下来看一下具体创建控制器的函数逻辑,代码如下。

代码语言:javascript复制
func newClusterController(config *util.ControllerConfig, clusterHealthCheckConfig *util.ClusterHealthCheckConfig) (*ClusterController, error) {
   ... ...
   kubeConfig := restclient.CopyConfig(config.KubeConfig)
   cc := &ClusterController{
      client:                   client,
      clusterHealthCheckConfig: clusterHealthCheckConfig,
      clusterDataMap:           make(map[string]*ClusterData),
      fedNamespace:             config.KubeFedNamespace,
   }
   ... ...
   kubeClient := kubeclient.NewForConfigOrDie(kubeConfig)
 
   var err error
   _, cc.clusterController, err = util.NewGenericInformerWithEventHandler(
      config.KubeConfig,
      config.KubeFedNamespace,
      &fedv1b1.KubeFedCluster{},
      util.NoResyncPeriod,
      &cache.ResourceEventHandlerFuncs{
         DeleteFunc: func(obj interface{}) {
            castObj, ok := obj.(*fedv1b1.KubeFedCluster)
            if !ok {
               tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
               if !ok {
                  klog.Errorf("Couldn't get object from tombstone %#v", obj)
                  return
               }
               castObj, ok = tombstone.Obj.(*fedv1b1.KubeFedCluster)
               if !ok {
                  klog.Errorf("Tombstone contained object that is not expected %#v", obj)
                  return
               }
            }
            cc.delFromClusterSet(castObj)
         },
         AddFunc: func(obj interface{}) {
            castObj := obj.(*fedv1b1.KubeFedCluster)
            cc.addToClusterSet(castObj)
         },
         UpdateFunc: func(oldObj, newObj interface{}) {
            var clusterChanged bool
            cluster := newObj.(*fedv1b1.KubeFedCluster)
            cc.mu.Lock()
            clusterData, ok := cc.clusterDataMap[cluster.Name]
            if !ok || !equality.Semantic.DeepEqual(clusterData.cachedObj.Spec, cluster.Spec) ||
               !equality.Semantic.DeepEqual(clusterData.cachedObj.ObjectMeta.Annotations, cluster.ObjectMeta.Annotations) ||
               !equality.Semantic.DeepEqual(clusterData.cachedObj.ObjectMeta.Labels, cluster.ObjectMeta.Labels) {
               clusterChanged = true
            }
            cc.mu.Unlock()
            if !clusterChanged {
               return
            }
            cc.delFromClusterSet(cluster)
            cc.addToClusterSet(cluster)
         },
      },
   )
   return cc, err
}

可以看出,这是一个近乎原生的Controller。首先创建了一个Informer用于监听KubeFedCluster资源的变化,并定义了监听资源变化的处理方法。通过go断言对资源对象进行处理,再执行相应的增删改操作。其中,更新操作需要比对缓存数据和更新数据是否一致。若一致,则新版本与缓存版本并无变化,无需更新,反之,则执行更新操作。操作的方式也很简单,直接删除旧的数据,再创建新的版本。

2.3 为什么v1被弃用?

通过上述,我们了解了Kubefed是如何实现多集群管理,并能监听联邦集群的事件变化的。而至于Federation v1版本为什么会在v1.11版本被正式弃用,根本的原因还是很多问题在v1架构设计的时候没有考虑的很全面。而且,随着Kubernetes自身不断的发展,v1对其的兼容性也变得越来越差,比如:

l控制平面的组件可能会出现问题,而这会直接影响整个联邦集群的工作效率。

l最初的设计里重用了Kubernetes API,且没有办法兼容新的Kubernetes API资源。

l不支持RBAC等权限校验,无法有效的对多个集群进行细粒度的权限管控。没有考虑到使用CRD功能,在定制化资源方面不具备可扩展性。

l联邦集群层级的设定和策略依赖Annotations的内容,可扩展性较差。

而从代码层面,Federation v1的API Server是基于k8s.io/apiserver的基础上进行开发的,采用了AA(API Aggregation)的方式对Kubernetes原生API进行扩充,最终的Federation API在前文已经提到,是以Adapter的形式管理Kubernetes API资源的,这些Adapter在底层写死了API的版本,比如说Deployment资源只支持extensions/v1beta1版本的API,此时想要创建一个apps/v1版本的Deployment,并在Annotations内设置联邦策略,就会使得Deployment资源无法正常运作。因此,如果想要支持其他资源或版本,就必须在Federation types中新增与之相匹配的Adapter,然后通过Code Generator产生API所需的Client-go组件给Controller Manager使用,并重新构建一个版本来更新API Server和Controller Manager以提供对其他资源和版本的支持,这无疑是繁琐的,也使得Federation v1在可扩展性方面变得非常局限。

正因为上述的这些原因,使得Federation v1在后续的发展中渐渐与社区脱节,并最终被弃用。但弃用并不代表着结束,多集群仍然需要可行的解决方案,因此,Kubernetes SIG Multi-Cluster团队在Federation v1之后又提出新的架构Federation v2。

03

K8s Federation v

Federation v2在Federation v1的基础上,简化了扩展Federated API的过程,并加强了跨集群服务发现与编排的能力。同时,Federation v2在设计之初就提出了两个最重要的核心理念,分别是Modularization(模块化)与Customizable(定制化),而提出这两个理念的原因大概也是希望Federation v2可以紧跟Kubernetes社区的发展,并且保持足够的可扩展性。

3.1 Federation v2架构

图2 Kubernetes Federation v2核心CRD及交互流程图

Federation v2在组件上最大的改变应该是移除了API Server,然后使用CRD机制来完成对Federated Resources的扩展。通过Kubefed Controller对这些CRD进行管理,并实现了跨集群的资源同步和编排等能力。简而言之,在功能可扩展方面,Federation v2的提升非常明显。Federation v2通过CRD的方式新增四种API群组来实现联邦机制的核心功能,分别是:

API Group

用途

core.kubefed.k8s.io

集群状态、联邦资源状态、KubeFed Controller相关设定等。

types.kubefed.k8s.io

被用于联邦的Kubernetes API资源定义。

scheduling.kubefed.k8s.io

副本调度编排策略。

multiclusterdns.kubefed.k8s.io

跨集群的服务发现。

而对于这些核心功能用途的理解,相信在了解后文的一些基础概念之后,便可以有一个比较清晰的认识。

3.2 Cluster Configuration

用来定义加入联邦的Kubernetes集群。可通过Kubefed工具执行kubefedctl join/unjoin来加入/删除集群。当集群成功加入联邦后,会创建一个KubeFedCluster的资源对象来储存集群相关信息,比如API Endpoint、CA Bundle等(详见3.2.1章节)。而这些信息也将会被Kubefed Controller获取并应用在不同的Kubernetes集群之上,以确保能够创建Kubernetes API资源用于访问,示意图如下所示。

图3 Kubefed Controller工作流程示意图

3.3 Type Configuration

用于管理和定义哪些Kubernetes API资源需要被用于联邦集群。比如说想将ConfigMap 资源通过联邦机制建立在不同的联邦集群上,就必须先在Host集群中,通过CRD创建与之相对应的FederatedConfigMap资源对象,然后再创建名为configmaps的Type Configuration(FederatedTypeConfig)资源,并在其中描述出ConfigMap要被FederatedConfigMap管理,这样KubeFed Controller才能知道如何创建Federated 资源。以下是对上述例子的一个示范:

代码语言:javascript复制
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

如果想要支持自定义资源(CR)的联邦,即通过CRD来扩展Federated API的话,则可以通过执行kubefedctl enable <target kubernetes API type>指令来进行创建,具体如下:

代码语言:javascript复制
// 启用CRD,执行后会会创建一个CRD:federatedcustomresourcedefinitions.types.kubefed.io
kubefedctl enable customresourcedefinitions
// 检索Kubefed控制平面所在命名空间是否生成与上述一样的CRD
kubectl -n kube-federation-system get federatedtypeconfigs.core.kubefed.io
// 执行创建名为envoyfilters.networking.istio.io的联邦资源
kubefedctl federate crd envoyfilters.networking.istio.io

而一个联邦资源通常具备三个主要功能,可以在Spec中由使用者自行定义,示例如下:

代码语言:javascript复制
apiVersion: types.kubefed.k8s.io/v1beta1
kind: FederatedDeployment
metadata:
  name: fed-deployment
  namespace: fed-namespace
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      ... ...
  placement:
    clusters:
    - name: cluster1
    - name: cluster2
  overrides:
  - clusterName: cluster2
    clusterOverrides:
    - path: spec.replicas
      value: 6

其中,placement用于定义联邦资源需要分发到哪些集群上。如果没有该配置,则资源不会分发至任何集群,示例中将会同步在集群1(Cluster1)和集群2两个集群创建相同的Deployment。当然,也可以使用spec.placement.clusterSelector的方式来选择需要分发的集群。

而overrides则用于定义修改指定集群,联邦资源中spec.template内的相关配置。比如部署FederatedDeployment至不同的厂商集群时,便可以通过spec.overrides参数来调整目标集群的Volume大小或副本数,示例中将集群2的副本数调整为6。

3.4 Scheduling

Kubefed 目前只能做到一些简单的集群间调度,即手动指定。而对于手动指定的调度方式主要分为两种方式,一种是直接在资源中指定目标集群,参考上述placement的配置方式或使用clusterSelector等亦可。还有一种是通过ReplicaSchedulingPreference参数项进行比例分配,可以实现在多个集群间按比例区别调度,具体如下。

代码语言:javascript复制
apiVersion: scheduling.kubefed.k8s.io/v1alpha1
kind: ReplicaSchedulingPreference
metadata:
  name: fed-deployment
  namespace: fed-namespace
spec:
  targetKind: FederatedDeployment
  totalReplicas: 10
  clusters:
    cluster1:
      minReplicas: 3
      maxReplicas: 6
      weight: 1
    cluster2:
      minReplicas: 5
      maxReplicas: 12
      weight: 2

其中,totalReplicas 定义了联邦集群的总副本数,clusters描述了不同联邦集群的最大/最小副本数的范围以及权重。目前,ReplicaSchedulingPreference参数项只支持Deployment和Replicaset两种资源配置。

04

Karmada

Karmada是华为开源的多云容器编排项目,可以看作是Kubernetes Federation v1和v2版本的延续,或者也可以将其理解为是 Federation v3。Karmada吸取了Federation项目的经验,在保持原有Kubernetes API不变的情况下,通过添加与多云应用资源编排相关的一套新的API和控制面组件,方便用户将应用部署到多云环境中,实现多集群管理可扩容、高可用等目标。

4.1 Karmada架构

图4 karmada架构

Karmada管理的多云和混合云多集群环境包含两类集群:

lHost集群:由karmada控制面构成的集群,接受用户提交的应用部署需求,将之同步到member集群,并从member集群同步应用后续的运行状况。

lMember集群:由一个或多个k8s集群构成,负责运行用户提交的应用。

4.1.1 Member集群的注册和注销

Karmada的Member集群支持Push和Pull两种模式注册到Host集群进行管理。Push模式下由Karmada控制面将应用推送到成员集群,而Pull模式下由运行在成员集群侧的Karmada Agent将应用下拉至本地。本文选取Push模式进行示例,在Push模式下,用户可以通过karmdactl命令工具的join/unjoin命令很方便的在Karmada集群联邦中注册/注销一个Kubernetes集群,示例如下。

代码语言:javascript复制
karmadactl join member1 --cluster-kubeconfig=$HOME/.kube/karmada.config
karmadactl unjoin member1 --cluster-kubeconfig=$HOME/.kube/karmada.config

参数member1是$HOME/.kube/karmada.config文件中集群的Context名称,也是该集群成功加入联邦后在Karmada控制面中的名称。在集群注册到联邦后,Karmada便会在控制面创建Cluster对象,将该Cluster对象以Yaml的格式输出,可以得到类似如下的内容。

代码语言:javascript复制
kind: Cluster
metadata:
  finalizers:
  - karmada.io/cluster-controller
  name: member1
spec:
  apiEndpoint: https://127.0.0.1:6443
  secretRef:
    name: member1
    namespace: karmada-cluster
  syncMode: Push

其中,.spec.syncMode为push,说明了Member集群加入Karmada集群联邦选用的模式,而.spec.apiendpoint则是Member集群的API Server的地址。.sepc.secretRef表示的是在集群Join的过程中,Karmada为其创建的Service Account以及对应的Secrets。通过Cluster对象spec中的apiEndpoint和secretRef等资源,Karmada控制面便可以在member1集群中操作各种资源对象,实现对Member集群的管理。

4.1.2 Member集群的状态跟踪

Karmada通过Cluster、Lease和Cluster Status三个Controller共同完成Member集群的状态跟踪,并将状态写入Cluster对象的status中。在Karmada集群联邦里,Cluster Status Controller(CSC)可能运行在两个地方,对于Push模式的Member集群,Karmada控制面中的Karmada Controller Manager会运行一个CSC。而对于Pull模式的集群,在Member集群侧运行的Karmada Agent会运行一个Karmada Controller Manager,再由其启动一个CSC,这一点与Push模式一致。了解完相关组件,接下来对Member集群状态跟踪的流程进行一下梳理,这里同样以Push模式为例。

CSC基于sigs.k8s.io/controller-runtime框架实现,负责监控Karmada控制面中Cluster对象的操作事件,这些增删改事件会在ClusterPredicateFunc中进行函数过滤,完成后可以实现CSC只处理Push模式的Cluster操作事件。CSC默认以10秒钟为间隔获取集群的相关信息,并写入Cluster对象的status中,具体如下:

lMember集群的在线状态(Online)和健康状态(Healthy)。CSC使用ClientSet访问Member集群的API Server的/readyz,如果/readyz无法访问则使用/healthz。如果/readyz或/healthz能够正常访问,则表示集群Online状态为true,如果能够得到200的HTTP响应,则表示集群Healthy状态为true。之后,CSC会根据获取到的信息设置集群状态,并写入Cluster对象对象的.status.conditions。

lMember集群的Node状态,并写入Cluster对象的.status.nodeSummary。

lMember集群的资源使用状态,并写入Cluster对象的.status.resourceSummary。

lMember集群的版本,并写入Cluster对象的.status.kubernetesVersion。

lMember集群的API支持情况,并写入Cluster对象的.status.apiEnablements。

下面示例一个Push模式下Member集群的status,即将Cluster对象以Yaml的格式输出。由于支持跟踪的API资源较多,示例只展示部分,具体如下。

代码语言:javascript复制
apiVersion: cluster.karmada.io/v1alpha1
kind: Cluster
metadata:
  finalizers:
  - karmada.io/cluster-controller
  name: member1
spec:
  apiEndpoint: https://127.0.0.1:6443
  secretRef:
    name: member1
    namespace: karmada-cluster
  syncMode: Push
status:
  apiEnablements:
  - groupVersion: v1
    resources:
    - kind: Endpoints
      name: endpoints
    - kind: Node
      name: nodes
    - kind: Pod
      name: pods
- kind: Service
      name: services
    ... ...
  conditions:
  - lastTransitionTime: "2022-11-13T10:18:36Z"
    message: cluster is reachable and health endpoint responded with ok
    reason: ClusterReady
    status: "True"
    type: Ready
  kubernetesVersion: v1.21.5
  nodeSummary:
    readyNum: 3
    totalNum: 3
  resourceSummary:
    allocatable:
      cpu: "32"
      pods: "256"
    ... ...

可以看到,Cluster资源对象中集群状态是Ready(/readyz返回HTTP 200),并且该状态的转变时间(lastTransitionTime)是2022-11-13T10:18:36Z,时间戳在集群状态发生变化时会同步更新。对于Push模式的Member集群耳炎,只要CSC正常运行,那么它就会一直尝试访问Member集群API Server的/readyz或/healthz,无论请求是否成功都将根据结果判断Member集群的状态并对Cluster对象进行更新。

4.2 Karmada核心概念

图5 Karmada API工作流程图

lResource Template:资源模板。Kubernetes原生API定义,包括CRD。无需修改即可创建多集群应用。

lPropagation Policy:分发策略。可重用的应用多集群调度策略。

lResource Binding:通用类型,驱动内部流程。

lOverride Policy:差异化调度策略。跨集群可重用的差异化配置策略。

lWork:Member集群最终资源在联邦层面的映射。

4.2.1 Propagation Policy示例

代码语言:javascript复制
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: example-policy
spec:
  resourceSelectors:
  - apiVersion: apps/v1
    kind: Deployment
    name: deployment-1
    labelSelector: 
  propagateDependensies: false
  placement:
    clusterAffinity:
      clusterNames:
        - cluster1
        - cluster3
     clusterTolerations:
     spreadConstraints:
       - spreadByLabel: failuredomain.kubernetes.io/zone
         maxGroups: 3
         minGroups: 3
   schedulerName: default

其中,resourceSelector支持关联多种资源类型,并可以使用Name或labelSelector对资源对象进行筛选。clusterAffinity定义倾向调度的目标集群,相似的,它也可以支持通过Name或labelSelector的方式对目标集群进行筛选。clusterTolerations类似单集群中Pod Tolerations和Node Taints。spreadConstraints定义应用分发的HA策略,支持按Region、AZ、特性label分组对集群动态分组,实现不同层级的HA。

4.2.2 Override Policy示例

代码语言:javascript复制
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
  name: example-override
  namespace: default
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
  targetCluster:
    labelSelector:
      matchLabels:
      failuredomain.kubernetes.io/region: region1
  overriders:
    imageOverrider:
    - component: prefix
      operator: replace
      value: "region-1.registry.io

其中,resourceSelector支持使用Name或labelSelector对资源对象进行筛选。overriders支持多种override插件类型,而imageOverrider就是针对容器镜像的差异化配置插件。

4.3 Karmada案例演示

首先,定义资源模板(Resource Template)。为何称为模板?是因为在每个集群中实际部署Deployment对象时都可以以它为模板创建,但又允许通过Override Policy修改其配置。

代码语言:javascript复制
apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox
  namespace: default
  labels:
    app: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
    spec:
      containers:
      - image: busybox:latest
        name: busybox

然后,定义分发策略(Propagation Policy)。指定将上述nginx deployment部署到member1和member2两个由karmada管理的member集群中。在propagation policy对象的设置中我们应该注意以下几点:

l.spec.resourceSelectors指定了需要部署到member集群中的资源:deployment—>nginx。

l.spec.placement指定了nginx需要部署到member1和member2两个member集群中。

l.spec.dependentOverrides表示需要karmada控制面等待下面的差异化配置(override policy)创建之后再将应用部署到member1和member2集群。

代码语言:javascript复制
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: nginx-propagation
  namespace: default
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: busybox
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2
  dependentOverrides:
    - nginx-override

最后,定义差异化调度策略(override policy),指定将部署在member2集群中的nginx deployment的replica数量改为2,即需要在member2集群中运行2个nginx实例。

代码语言:javascript复制
apiVersion: policy.karmada.io/v1alpha1
kind: OverridePolicy
metadata:
  name: busybox-override
  namespace: default
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: busybox
  targetCluster:
    clusterNames:
      - member2
  overriders:
    plaintext:
    - path: "/spec/replicas"
      operator: replace
      value: 2

将上述三个yaml用kubectl apply提交给karmada控制面后,便可以登录到member1集群和member2集群查看应用服务分发的完成情况。比如,登录member2集群执行kubectl get deployment,可以看到busybox服务的实例数为2。或者直接在Host集群执行kubectl get deployment,可以发现busybox服务的Pod总数已经变成了3个。

05

总结

本文介绍了Kubernetes多集群管理发展的,主要包括以下几个部分内容:

l第一部分,介绍了Kubernetes集群联邦的基础知识,包括为什么需要多集群、集群联邦的概念和演进过程;

l第二部分,介绍了Kubernetes Federation v1和v2版本的发展历程,包括从代码层面剖析了Kubefed如何实现多集群管理,以及Federation v2版本的一些新特性;

l第三部分,介绍了Karmada多云容器编排项目,包括其架构、Member集群的管控、Karmada核心概念等,并提供了一个Karmada管理多集群实现服务应用分发的示例。

参考文献

[1]https://github.com/kubernetes-sigs/kubefed

[2]https://github.com/karmada-io/karmada

[3]https://zhuanlan.zhihu.com/p/407990257

[4]https://www.kubernetes.org.cn/5702.html

[5]https://zhuanlan.zhihu.com/p/411022709

0 人点赞