K8S Internals 系列:第四期
在 Kubernetes 一统天下场面造成后,K8S 成为了云原生时代的新一代操作系统。K8S 让所有变得简略了,但本身逐步变得越来越简单。【K8S Internals 系列专栏】围绕 K8S 生态的诸多方面,将由博云容器云研发团队定期分享无关调度、平安、网络、性能、存储、利用场景等热点话题。心愿大家在享受 K8S 带来的高效便当的同时,又能够如庖丁解牛般领略其内核运行机制的魅力。
在上一期文章《K8S多集群治理很难?试试Karmada | K8S Internals系列第3期》中,咱们次要介绍了 karmada 架构及相干资源概念,理解了 karmada 的倒退历史。
家喻户晓,karmada 组件中有三个组件与调度密切相关,别离是 karmada-scheduler,karmada-descheduler 以及 karmada-scheduler-estimator,这三个组件的协同作用能够实现 karmada 的多种调度策略。本文咱们将次要介绍下这三个组件。
01karmada-scheduler
karmada-scheduler 的次要作用就是将 k8s 原生 API 资源对象(蕴含 CRD 资源)调度到成员集群上。咱们先看一下其与 k8s 集群的 kube-scheduler 的比照:
调度插件
karmada scheduler 在调度每个 k8s 原生 API 资源对象(蕴含 CRD 资源)时,会一一调用各扩大点上的插件:
- filter 扩大点上的调度算法插件将不满足 propagation policy 的成员集群过滤掉 karmada scheduler 对每个考查中的成员集群调用每个插件的Filter办法,该办法都能返回一个Result对象示意该插件的调度后果,其中的code代表待下发资源是否能调度到某个成员集群上,reason用来解释这个后果,err蕴含调度算法插件执行过程中遇到的谬误。
// Filter checks if the API(CRD) of the resource is installed in the target cluster.func (p *APIInstalled) Filter(ctx context.Context, placement *policyv1alpha1.Placement, resource *workv1alpha2.ObjectReference, cluster *clusterv1alpha1.Cluster) *framework.Result { if !helper.IsAPIEnabled(cluster.Status.APIEnablements, resource.APIVersion, resource.Kind) { klog.V(2).Infof("Cluster(%s) not fit as missing API(%s, kind=%s)", cluster.Name, resource.APIVersion, resource.Kind) return framework.NewResult(framework.Unschedulable, "no such API resource") } return framework.NewResult(framework.Success)}// NewResult makes a result out of the given arguments and returns its pointer.func NewResult(code Code, reasons ...string) *Result { s := &Result{ code: code, reasons: reasons, } if code == Error { s.err = errors.New(strings.Join(reasons, ",")) } return s}
score 扩大点上的调度算法插件为每个通过上一步过滤的集群计算评分karmada scheduler 对每个通过上一步过滤的成员集群调用每个插件的Score办法,该办法都能返回一个int64类型的评分后果。
const ( // MinClusterScore is the minimum score a Score plugin is expected to return. MinClusterScore int64 = 0 // MaxClusterScore is the maximum score a Score plugin is expected to return. MaxClusterScore int64 = 100)// Score calculates the score on the candidate cluster.// if cluster object is exist in resourceBinding.Spec.Clusters, Score is 100, otherwise it is 0.func (p *ClusterLocality) Score(ctx context.Context, placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec, cluster *clusterv1alpha1.Cluster) (int64, *framework.Result) { if len(spec.Clusters) == 0 { return framework.MinClusterScore, framework.NewResult(framework.Success) } replicas := util.GetSumOfReplicas(spec.Clusters) if replicas <= 0 { return framework.MinClusterScore, framework.NewResult(framework.Success) } // 再次触发调度时,已存在正本的集群得分更高 if spec.TargetContains(cluster.Name) { return framework.MaxClusterScore, framework.NewResult(framework.Success) } return framework.MinClusterScore, framework.NewResult(framework.Success)}
最终依照第二步的评分高下抉择成员集群作为调度后果。目前 karmada 的调度算法插件:
APIInstalled: 用于查看资源的API(CRD)是否装置在指标集群中。ClusterAffinity: 用于查看资源选择器是否与集群标签匹配。SpreadConstraint: 用于查看 Cluster.Spec 中的 spread 属性即Provider/Zone/Region字段。
TaintToleration: 用于查看流传策略是否容忍集群的污点。
ClusterLocality 是一个评分插件,为指标集群进行评分。
调度场景
karmada scheduler 的输出是 resource detector 的输入:resource binding 和cluster resource bingding。这里这里波及到 doScheduleBinding 和 doScheduleClusterBinding,两者流程相似咱们单看 doScheduleBinding:
func (s *Scheduler) doScheduleBinding(namespace, name string) (err error) { rb, err := s.bindingLister.ResourceBindings(namespace).Get(name) if err != nil { if apierrors.IsNotFound(err) { // the binding does not exist, do nothing return nil } return err } // Update "Scheduled" condition according to schedule result. defer func() { s.recordScheduleResultEventForResourceBinding(rb, err) var condition metav1.Condition if err == nil { condition = util.NewCondition(workv1alpha2.Scheduled, scheduleSuccessReason, scheduleSuccessMessage, metav1.ConditionTrue) } else { condition = util.NewCondition(workv1alpha2.Scheduled, scheduleFailedReason, err.Error(), metav1.ConditionFalse) } if updateErr := s.updateBindingScheduledConditionIfNeeded(rb, condition); updateErr != nil { klog.Errorf("Failed update condition(%s) for ResourceBinding(%s/%s)", workv1alpha2.Scheduled, rb.Namespace, rb.Name) if err == nil { // schedule succeed but update condition failed, return err in order to retry in next loop. err = updateErr } } }() start := time.Now() policyPlacement, policyPlacementStr, err := s.getPlacement(rb) if err != nil { return err } appliedPlacement := util.GetLabelValue(rb.Annotations, util.PolicyPlacementAnnotation) if policyPlacementStr != appliedPlacement { // policy placement changed, need schedule klog.Infof("Start to schedule ResourceBinding(%s/%s) as placement changed", namespace, name) err = s.scheduleResourceBinding(rb) metrics.BindingSchedule(string(ReconcileSchedule), utilmetrics.DurationInSeconds(start), err) return err } if policyPlacement.ReplicaScheduling != nil && util.IsBindingReplicasChanged(&rb.Spec, policyPlacement.ReplicaScheduling) { // binding replicas changed, need reschedule klog.Infof("Reschedule ResourceBinding(%s/%s) as replicas scaled down or scaled up", namespace, name) err = s.scheduleResourceBinding(rb) metrics.BindingSchedule(string(ScaleSchedule), utilmetrics.DurationInSeconds(start), err) return err } // TODO(dddddai): reschedule bindings on cluster change if s.allClustersInReadyState(rb.Spec.Clusters) { klog.Infof("Don't need to schedule ResourceBinding(%s/%s)", namespace, name) return nil } if features.FeatureGate.Enabled(features.Failover) { klog.Infof("Reschedule ResourceBinding(%s/%s) as cluster failure or deletion", namespace, name) err = s.scheduleResourceBinding(rb) metrics.BindingSchedule(string(FailoverSchedule), utilmetrics.DurationInSeconds(start), err) return err } return nil}
能够看到这里实现了三种场景的调度:
散发资源时抉择指标集群的规定变了
正本数变了,即扩缩容调度
故障复原调度,当被调度的成员集群状态不失常时会触发从新调度
调度策略在创立散发策略的时候,须要指定调度策略,举个例子:
apiVersion: policy.karmada.io/v1alpha1kind: PropagationPolicymetadata: name: nginx-propagationspec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinity: clusterNames: - member1 #散发到成员集群member1和member2 - member2 replicaScheduling: replicaDivisionPreference: Weighted #划分正本策略 replicaSchedulingType: Divided #调度正本策略 weightPreference: staticWeightList: #指标集群动态权重 - targetCluster: clusterNames: - member1 weight: 1 - targetCluster: clusterNames: - member2 weight: 1
相干字段设置关系如图所示:
ReplicaSchedulingType 可设置为 Duplicated 或者 Divided,如果设置为Divided则须要进一步设置ReplicaDivisionPreference。
ReplicaDivisionPreference 可设置为 Aggregated 或者 Weighted,如果设置为Weighted,则须要进一步设置weightPreference。
weightPreference 中可设置为动态权重(staticWeightList)或者动静权重(DynamicWeight),目前动静权重的动静因素只有 AvailableReplicas,即依据集群资源计算出的可调度正本数作为权重指标。
Duplicated策略
Duplicated 策略示意将要散发资源的 replicas 雷同数量的复制到所有的指标成员集群中,例如:散发 deployment 资源时,deployment 资源设定的正本数为10,则散发到所有指标集群的 deployment 的正本数都是10,如图所示:
Divided 策略
Divided 策略示意将要散发资源的 replicas 划分到多个指标成员集群中,例如:散发 deployment 资源时,deployment 资源设定的正本数为10,则散发到所有指标集群的 deployment 的正本总数是10。具体如何划分须要依据ReplicaDivisionPreference 的值来决定,而 ReplicaDivisionPreference 的值能够设定为 Aggregated 或者 Weighted。
Aggregated 策略Aggregated 策略示意将正本调度到尽可能少的指标集群上,例如指标集群有三个,然而第一个指标集群领有足够的资源调度到所有的正本,则正本会全副调度到第一个指标集群上,如图所示:
Weighted 策略
Weighted 策略具体设置通过weightPreference字段设置,如果weightPreference不设置,则默认给所有指标集群加雷同的动态权重1。当 weightPreference 设置为动态权重时,举个例子:有三个指标集群 A、 B、 C, 权重别离为 1、2、 3;须要散发正本数为12,则 A 集群散发正本数为:12 1/(1+2+3)= 2 , B 集群散发正本数为:12 2/(1+2+3)= 4 , A 集群散发正本数为:12 * 3/(1+2+3)= 6 ;如图所示:
当 weightPreference 设置为动静权重时,动态权重的设置会被疏忽,动静权重策略调度正本时须要依据 karmada-estimator 计算出的各个指标集群可调度最大正本数进行计算各个指标集群调度的具体正本数,举个例子:有三个指标集群 A、 B、 C, 最大可调度正本数别离为 6、12、 18;须要散发正本数为12,则 A 集群散发正本数为:12 6/(6+12+18)= 2 , B 集群散发正本数为:12 12/(6+12+18)= 4 , A 集群散发正本数为:12 * 18/(6+12+18)= 6 ;如图所示:
02karmada-descheduler
karmada-descheduler 在调度策略为动静划分时(dynamic division)时才会失效;karmada-descheduler 将每隔一段时间检测一次所有部署,默认状况下每 2 分钟检测一次。在每个周期中,它会通过调用 karmada-scheduler-estimator 找出部署在指标调度集群中有多少不可调度的正本,而后更新 ResourceBinding 资源的Clusters[i].Replicas 字段,并依据当前情况触发 karmada-scheduler 执行“Scale Schedule”。
03karmada scheduler-estimator
当调度策略是动静权重调度或者Aggregated策略时,karmada-scheduler通过调用 karmada-scheduler-estimator不会将过多的正本调配到资源有余的集群中。karmada-scheduler-estimator用来计算集群中CPU,Memory,EphemeralStorage 或者申请创立工作负载中的其余资源是否满足调度需要,对集群中每个节点可调度正本数进行计算,最终计算出该集群可调度的最大正本数。
评估服务评估哪些指标?
karmada scheduler-estimator评估了以下资源:
cpu
memory
ephemeral-storage
其余标量资源:(1)扩大资源,例如:requests.nvidia.com/gpu: 4(2)kubernetes.io/下原生资源(3)hugepages- 资源(4)attachable-volumes-
资源评估服务如何和调度器协调工作的?
karmada scheduler-estimator 是一个 gRPC 服务,当调度策略是动静权重调度或者Aggregated策略时,karmada-scheduler 会遍历所有启动的 karmada scheduler-estimator,调用其客户端办法 MaxAvailableReplicas 向 karmada scheduler-estimator 发送申请获取评估后果,即该集群可调度的最大正本数。
调度场景演示
karmada-descheduler再调度演示创立一个 deployment,正本数设置为3,并 divide 它们到三个成员集群:
apiVersion: policy.karmada.io/v1alpha1kind: PropagationPolicymetadata: name: nginx-propagationspec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinity: clusterNames: - member1 - member2 - member3 replicaScheduling: replicaDivisionPreference: Weighted replicaSchedulingType: Divided weightPreference: dynamicWeight: AvailableReplicas---apiVersion: apps/v1kind: Deploymentmetadata: name: nginx labels: app: nginxspec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx resources: requests: cpu: "1"export KUBECONFIG="$HOME/.kube/karmada.config"kubectl config use-context karmada-apiserverkubectl apply -f test-deploy-0629.yaml
查看创立的后果:kubectl karmada get pods
咱们设置 member 集群不可调度
export KUBECONFIG="$HOME/.kube/members.config"kubectl config use-context member1kubectl cordon member1-control-planekubectl delete pod nginx-68b895fcbd-jgwz6# member1集群上pod变得不可调度kubectl get pod
大概 5 到 7 分钟后,查看 pod,能够看到正本曾经被调度到 member2 集群上
export KUBECONFIG="$HOME/.kube/karmada.config"kubectl config use-context karmada-apiserverkubectl karmada get podsNAME CLUSTER READY STATUS RESTARTS AGEnginx-6cd649d446-k72rf member3 1/1 Running 0 8mnginx-6cd649d446-hb4jk member2 1/1 Running 0 8mnginx-6cd649d446-qmckr member2 1/1 Running 0 1m
综上所述,咱们能够看到 karmada 领有较丰盛的调度策略,能够来满足工作负载在多集群间的调度,从而满足多种场景下的应用,例如双活,近程容灾,故障转移等。
下一篇咱们将为大家分享,基于 Karmada 和博云自研网络组件 Fabric,实现多集群业务跨集群服务发现、无损通信等高阶能力,助力业务单元化部署能力。