Koordinator Descheduler的MigrationController插件:确保驱逐后Pod成功调度

由于调度器和descheduler是两个独立工作的组件,两个组件之间并没有任何通讯和协商机制,在descheduler执行pod驱逐后,由驱逐生成pod的调度和非驱逐原因生成的pod的调度(后面简称正常的pod)会存在资源抢占的问题。比如正常的pod和驱逐后生成的新pod需要相同的资源,且集群的剩余可以调度的资源紧张(或者资源碎片),且正常的pod在驱逐后生成的新pod之前调度,会出现pod驱逐后新生成的pod重新调度到原先的节点或无法调度的问题。

MigrationController插件通过Reservation资源跟koordinator scheduler进行通信,让koordinator scheduler进行资源预留,然后再执行驱逐pod,来解决上面这个问题。

仲裁机制是一种干预pod的驱逐过程的机制,它在 v1.4.0 版本中引入。它解决了应用变更维护过程中临时暂停应用pod的驱逐,比如deployment滚动更新的过程中同时descheduler执行pod的驱逐,会对这个应用的稳定性产生影响,甚至没有一个ready的pod。仲裁机制提供了一个方式,在驱逐pod时候增加一道审批的流程,能够进行批准、拒绝等操作。

本文基于koordinator v1.4.0

koordinator descheduler里的MigrationController插件,它实现了filter和evict插件的扩展点。它包含了资源保留和仲裁机制的功能。

名词说明:

Reservation:koordinator自定义资源,启用资源预留之后,每个被驱逐的pod关联一个reservation。用于koordinator-scheduler跟踪资源预留的状态,同时koordinator-descheduler根据这个reservation更新PodMigrationJob状态。

PodMigrationJob:koordinator自定义资源,每次MigrationController执行驱逐pod都会为这个pod创建一个PodMigrationJob。它用于跟踪pod的驱逐状态。

arbitrator:MigrationController插件内部的一个模块,负责对pod驱逐进行过滤,决定这个pod是否执行驱逐。

在koordinator-descheduler的配置文件里,分别在profiles[*].plugins.evictprofiles[*].plugins.pluginConfig添加下面配置

yaml

  profiles:
    - name: koord-descheduler
      plugins:
       ........
        evict:
          disabled:
            - name: "*"
          enabled:
            - name: MigrationController
      pluginConfig:
      - name: MigrationController
        args:    

资源保留功能默认开启,即在MigrationController插件配置中设置defaultJobMode: ReservationFirst

yaml

    pluginConfig:
      - name: MigrationController
        args:
          defaultJobMode: ReservationFirst

对于手动创建PodMigrationJob,默认启用资源保留功能。

yaml

apiVersion: scheduling.koordinator.sh/v1alpha1
kind: PodMigrationJob
metadata:
  name: migrationjob-demo
spec:
  mode: ReservationFirst

当然你也可以关闭资源保留功能,在MigrationController插件中配置defaultJobMode: EvictDirectly

yaml

    pluginConfig:
      - name: MigrationController
        args:
          defaultJobMode: EvictDirectly

对于手动创建PodMigrationJob,关闭资源保留功能。

yaml

apiVersion: scheduling.koordinator.sh/v1alpha1
kind: PodMigrationJob
metadata:
  name: migrationjob-demo
spec:
  mode: EvictDirectly

koordinator-scheduler将reservation看作像pod一样的调度单位,它会根据里reservation的信息进行调度(可以认为reservation是用来占坑的虚拟pod),分配一个满足需要的节点,对这个节点进行预留资源。等待相关的reservation相关的新pod生成,则将预留的资源分配给这个新的pod。

我们以同时启用了LowNodeLoad和MigrationController两个插件为例,下面是koordinator-descheduler的配置,LowNodeLoad做为balance类型插件和MigrationController做为evict类型插件。

yaml

 profiles:
    - name: koord-descheduler
      plugins:
        deschedule:
          disabled:
            - name: "*"
        disable LowNodeLoad by default
        balance:
          enabled:
          - name: LowNodeLoad
        evict:
          disabled:
            - name: "*"
          enabled:
            - name: MigrationController
      pluginConfig:
      - name: MigrationController
        args:
          apiVersion: descheduler/v1alpha2
          kind: MigrationControllerArgs
          evictionPolicy: Eviction
          namespaces:
            exclude:
              - kube-system
          evictQPS: "10"
          evictBurst: 1
      - name: LowNodeLoad
        args:
          apiVersion: descheduler/v1alpha2
          kind: LowNodeLoadArgs
          evictableNamespaces:
            exclude:
              - kube-system
          useDeviationThresholds: false
          lowThresholds:
            cpu: 45
            memory: 55
          highThresholds:
            cpu: 75
            memory: 80

在LowNodeLoadArgs插件执行节点上的pod驱逐阶段,会调用evicted插件(这里配置的是MigrationController)执行驱逐。

koordinator-descheduler-migrationController-process
koordinator-descheduler migrationController执行流程

MigrationController执行evict时候做的事情:

  1. 调用arbitrator,让arbitrator决定是否要执行后续的驱逐流程(仲裁机制)

  2. 创建PodMigrationJob(自定义cr)资源,用于记录pod迁移处理状态。

  3. 如果MigrationController配置里启用了资源保留功能(defaultJobMode为ReservationFirst,这个是默认启用),则创建Reservation(自定义cr)资源,用于koordinator-scheduler进跟踪资源保留的状态,让koordinator-scheduler对新生成的pod进行资源保留。同时等待koordinator-scheduler成功预留了资源,koordinator-descheduler会同步Reservation状态到PodMigrationJob。

  4. 执行pod驱逐。

  5. 如果MigrationController启用了资源保留功能,则等待新pod调度完成(即成功为新pod分配了预留的资源)。

在创建job之前和之后arbitrator都会进行仲裁(决定pod是否继续执行后续驱逐)。

它包括了一些内置驱逐的限制,同时提供了一个可以人工干预的方法,让pod不被驱逐(即pod的annotation里添加"scheduling.koordinator.sh/eviction-cost"的值等于int32最大值)

内置限制(符合任意一个条件的不进行驱逐)包括:

  1. annotation里"scheduling.koordinator.sh/eviction-cost"的值等于int32最大值

  2. defaultJobMode为ReservationFirst,且需要被驱逐的pod的Spec.SchedulerName不在MigrationController插件的SchedulerNames配置项里。即如果MigrationController配置里启用了资源保留功能,则当pod的调度器不在MigrationController插件配置调度器列表里不进行驱逐。

  3. 节点上不能被驱逐的pod(跟官方descheduler一样,具体规则请查看descheduler插件过滤规则

  4. pod的namespace不在MigrationController配置项Namespaces.Include里

  5. pod的namespace在MigrationController配置项Namespaces.Exclude里

  6. MaxMigratingPerWorkload和MaxMigratingPerWorkload超过pod对应的workload的副本数,或workload下的pod数量为1

  7. pod的annotation里没有"descheduler.alpha.kubernetes.io/evict",且满足下面任一个条件

    • 整个descheduler驱逐达到了MigrationController配置里ObjectLimiters里的速率限制

    • 这个节点上通过仲裁的pod数量大于MigrationController配置里MaxMigratingPerNode

    • pod的所在的namespace下已经通过仲裁的PodMigrationJob数量超过了MigrationController配置里MaxMigratingPerNamespace限制

    • pod的workload下迁移的pod超过MaxMigratingPerWorkload,或正在迁移的pod数量加workload下不可用的pod数量超过了MaxUnavailablePerWorkload

在创建PodMigrationJob之后,还会进行再次检查上面条件(这个是为了检查手动创建的podMigrationJob)

比如手动迁移指定的pod,会人为的为这个pod创建PodMigrationJob

MigrationController最终执行驱逐操作,支持三种驱逐动作。

  1. 调用Delete方法执行删除–MigrationController的配置参数EvictionPolicy配置为"Delete"
  2. 调用Evict方法执行删除–MigrationController的配置参数EvictionPolicy配置为"Eviction"
  3. 不进行pod移除操作,而是pod的annotation里添加key为"scheduling.koordinator.sh/soft-eviction"]",值为记录了驱逐的原因、谁触发的、时间和DeleteOptions的json字符串–MigrationController的配置参数EvictionPolicy配置为"SoftEviction"
参数说明默认值
dryRun执行驱逐流程,但是不创建Reservation和不执行驱逐podfalse
evictFailedBarePods孤儿pod且为failed状态是否驱逐false
evictLocalStoragePods是否驱逐带有本地存储的podfalse
evictSystemCriticalPods是否驱逐优先级为SystemCritical的podfalse
IgnorePvcPods有pvc的pod是否驱逐false
labelSelectorpod匹配这个LabelSelector才会被驱逐nil
priorityThresholdpod的优先级小于这个级别才会被驱逐nil
maxConcurrentReconciles最大并发Reconciles数量1
namespaces.include代表忽略这些命名空间下的pod。
首先检查include,然后再检查exclude。
nil
namespaces.exclude代表只考虑这些命名空间下的pod。
首先检查include,然后再检查exclude。
nil
nodeFit是否有合适的节点调度新的pod,包括检查NodeAffinity和Taint容忍(目前这个参数在v1.4版本没有作用)false
nodeSelector用于过滤出目标节点,并判断能否从这些节点里找到能够容纳被驱逐pod的节点(目前这个参数在v1.4版本没有作用)false
maxMigratingPerNode每个节点上最大可以迁移的pod数量(0代表不限制)2
maxMigratingPerNamespace每个namespace下最多可以迁移的pod数量(0代表不限制)0
maxMigratingPerWorkload每个pod所属的workload下最多可以迁移的pod数量
其中0代表:副本数大于10,则为10%的workload副本数
副本数大于等于4小于等于10,则为2
其他情况(副本数小于4),为1
0
maxUnavailablePerWorkload在pod迁移时候,所属的workload最大不可用pod数量
其中0代表:副本数大于10,则为10%的workload副本数
副本数大于等于4小于等于10,则为2
其他情况(副本数小于4),为1
0
skipCheckExpectedReplicas如果为false,则检查maxMigratingPerWorkload和maxUnavailablePerWorkload配置应该小于当前workload的期望的副本数false
objectLimiters配置驱逐速率限制,目前只支持workload维度的驱逐速率限制。
每个维度下面可以设置的参数为maxMigrating和duration,代表在duration窗口内最大可以迁移pod数量为maxMigrating
这个功能和上面maxMigratingPerWorkload有重复,但是代码逻辑不一样,maxMigratingPerWorkload根据当时所有podMigrationJob进行统计,而objectLimiters使用rateLimiter来统计。
这里两个都存相同的问题,如果pod是deployment生成,workload是replicaSet,在deployment滚动更新的时候,deployment副本数跟replicas不一样。
nil
defaultJobMode代表PodMigrationJob执行pod驱逐的工作模式
可以设置:“ReservationFirst”–等待kooridinator-scheduler保留资源成功后,再执行pod驱逐,“EvictDirectly”–直接执行pod驱逐
“ReservationFirst”
defaultJobTTLkoordinator-descheduler创建的PodMigrationJob资源生存时间5m
schedulerNames这是一个调度器名字列表,这些调度器能够处理资源保留功能
当启用资源保留功能后,如果pod的调度器名字不在这个列表里,不会对这个pod执行驱逐。
[“koord-scheduler”]
evictQPS全局限制,每秒驱逐pod数量10
evictBurst全局限制,最大突发的驱逐pod数量1
evictionPolicy配置执行pod驱逐的方法
可以配置:“Eviction”–调用Evict方法执行删除。“Delete”–调用Delete方法执行删除。“SoftEviction”–pod的annotation里添加key为"scheduling.koordinator.sh/soft-eviction"]",值为记录了驱逐的原因、谁触发的、时间和DeleteOptions的json字符串
“Eviction”
defaultDeleteOptions执行驱逐pod时候传递的参数nil
arbitrationArgs.enabled是否开启仲裁机制true
arbitrationArgs.interval对PodMigrationJob执行仲裁的周期500ms

MigrationController插件是解决了descheduler驱逐pod之后,新的pod处于不可调度状态。同时能够手动驱逐某个pod(通过手动创建PodMigrationJob)和让特定的pod不被驱逐。

资源预留功能需要配合koordinator-scheduler才能完成,所以需要同时运行koordinator-descheduler与koordinator-scheduler。

相关内容