WorkloadSpread

263 阅读3分钟

阅读本节前请了解 WorkloadSpread 的使用和功能

系列教程: Openkruise

openkruise doc :openkruise.io/zh/docs/use…

每一个WorkloadSpread定义多个区域(定义为subset), 每个subset对应一个maxReplicas数量。WorkloadSpread利用Webhook注入subset定义的域信息,同时控制Pod的扩缩容顺序。 与UnitedDeployment不同的是,UnitedDeployment是帮助用户创建并管理多个workload,WorkloadSpread仅作用在单个workload之上,用户提供workload即可。

Workloadspread 不

创建并调度

  1. 特性开关

pkg/features/kruise_features.go

func SetDefaultFeatureGates() {
    if !utilfeature.DefaultFeatureGate.Enabled(PodWebhook) {
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", KruisePodReadinessGate))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", ResourcesDeletionProtection))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", PodUnavailableBudgetDeleteGate))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", PodUnavailableBudgetUpdateGate))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", WorkloadSpread))
    }
    if !utilfeature.DefaultFeatureGate.Enabled(KruiseDaemon) {
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=false", PreDownloadImageForInPlaceUpdate))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", DaemonWatchingPod))
        _ = utilfeature.DefaultMutableFeatureGate.Set(fmt.Sprintf("%s=true", InPlaceUpdateEnvFromMetadata))
    }
}
  1. 触发点

workloadSpread 是通过 webhook feature 来触发的,所以创建的触发不在 reconcile 阶段,而是在

pkg/webhook/pod/mutating/pod_create_update_handler.go

补充:如果有兴趣也可以去看 pkg/webhook/pod/validating/pod_create_update_handler.go,validating 表示是否允许操作

// Handle handles admission requests.
func (h *PodCreateHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
    ......
    if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadSpread) {
        err = h.workloadSpreadMutatingPod(ctx, req, obj)
        if err != nil {
            return admission.Errored(http.StatusInternalServerError, err)
        }
    }
}

workloadSpreadMutatingPod => workloadSpreadHandler.HandlePodCreation(pod)=>mutatingPod(matchedWS, pod, nil, CreateOperation)

下面详述函数 mutatingPod

  1. 调用 acquireSuitableSubset找到 pod 所适合的 subset

  2. 调用 injectWorkloadSpreadIntoPod(matchedWS, pod, suitableSubsetName, generatedUID) 来使 pod 符合对应 subset 的调度策略

    1. 具体来说会根据 subset 的 sub-fields 来修改 pod 的 affinity,具体逻辑参考特性 sub-fields
    2. 修改完后还会给 pod 打上 annotation "apps.kruise.io/matched-workloadspread" ,其 value 是对应的 subset name

Reconcile

准备阶段

  1. 获得所匹配的 pods 和 replicas: pods, workloadReplicas, err := r.getPodsForWorkloadSpread(ws) , 其中 replicase 的计算方式是: non-job-workload.Spec.replicas + job.Spec.Parallelism

    1. 注意如果存在 pod 没有匹配到任意 subset, 那么它的 key 会是 "kruise.io/workloadspread-fake-subset-name"
  2. 按照 subsetName 进行 map: podMap, err := r.groupPod(ws, pods)

    1. 这里有个重要函数叫做 getSuitableSubsetNameForPod,它会给 subset 进行打分(matched, preferredScore, err := matchesSubset(pod, node, subset))返回最匹配的 subset

      1. 该函数会调用 patchFavoriteSubsetMetadataToPod(pod, ws, favoriteSubset) 对 pod 打一个 annotation "apps.kruise.io/matched-workloadspread" 以标注它所属的 subset
  3. 计算删除代价并将其写到 pod 的 annotation(controller.kubernetes.io/pod-deletion-cost) 中: subset 的删除需要按照一定顺序: 总是按照删除代价最小的 pod 进行删除。删除代价被分为三部分部分: 一部分是当 subset > maxreplicas 时,额外 pod 的删除代价; 另一部分是当 subset <= maxreplicas 时普通的删除代价; 最后一部分是如果有 pods 属于 kruise.io/workloadspread-fake-subset-name, 即不匹配任何 subset,那么它始终会被计算出一个最小删除代价以满足最高优先级删除

    1. Deletion-cost: 100 * (subsets.length - subsetIndex) subset <= maxreplicas
    2. Deletion-cost: - 100 * (subsets.length -subsetIndex) subset > maxreplicas
    3. Deletion-cost: (0, -100 * (subsets.length + 1)) fake-subset-name
    4. e.g.
                       subset-a       subset-b     subset-c
          maxReplicas    10            10           nil
          pods number    20            20           20
          deletion-cost (300,-100)    (200,-200)    100
      

对于上述例子,删除的顺序应该是 subset-b 多余的 pods; subset-a 多余的 pods; subset-c; subset-b; subset-a

重调度(只更新状态)

  1. 对每个subset重新计算一遍 status (calculateWorkloadSpreadSubsetStatus) ,比较值得关注的字段有如下几个

    1. MissingReplicas: 它反映了未成功创建的 pod 数, 不包括正在创建( 创建时间距离现在小于 CreatPodTimeout(30s) ) 的
    2. subsetStatus.CreatingPods[podID] = createTime: 反映了各个 pod 的创建时间
  2. 并且对于 rescheduleCriticalSeconds > 0 (特性: 调度策略) 且不在最后一位的 subset 尝试重调度 rescheduleSubset()

    1. 筛选出调度失败的 pods

    2. 如果有不可调度的 pod,那么把 subset 状态设置为 不可调度

    3. 如果当前没有不可调度的 pod 但是当前记录的 status 表明有错误,则会比较当前时间和上一次调度时间是否超过 MaxScheduledFailedDuration(300s)

      1. 如果超过了 MaxScheduledFailedDuration,那么将 status 强制恢复成可调度状态
      2. 如果没超过 MaxScheduledFailedDuration,则保持不可调度状态直到MaxScheduledFailedDuration
    4. 最后该函数始终会返回一个不可调度 pods 列表

    5. 如果 rescheduleCriticalSeconds > 0 且为最后一个 subset,那么始终将它的状态设置为可调度