阅读本节前请了解 WorkloadSpread 的使用和功能
系列教程: Openkruise
openkruise doc :openkruise.io/zh/docs/use…
每一个WorkloadSpread定义多个区域(定义为subset), 每个subset对应一个maxReplicas数量。WorkloadSpread利用Webhook注入subset定义的域信息,同时控制Pod的扩缩容顺序。 与UnitedDeployment不同的是,UnitedDeployment是帮助用户创建并管理多个workload,WorkloadSpread仅作用在单个workload之上,用户提供workload即可。
Workloadspread 不
创建并调度
- 特性开关
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))
}
}
- 触发点
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
-
调用
acquireSuitableSubset找到 pod 所适合的 subset -
调用
injectWorkloadSpreadIntoPod(matchedWS, pod, suitableSubsetName, generatedUID)来使 pod 符合对应 subset 的调度策略- 具体来说会根据 subset 的 sub-fields 来修改 pod 的 affinity,具体逻辑参考特性 sub-fields
- 修改完后还会给 pod 打上 annotation "apps.kruise.io/matched-workloadspread" ,其 value 是对应的 subset name
Reconcile
准备阶段
-
获得所匹配的 pods 和 replicas:
pods, workloadReplicas, err := r.getPodsForWorkloadSpread(ws), 其中 replicase 的计算方式是: non-job-workload.Spec.replicas + job.Spec.Parallelism- 注意如果存在 pod 没有匹配到任意 subset, 那么它的 key 会是 "kruise.io/workloadspread-fake-subset-name"
-
按照 subsetName 进行 map:
podMap, err := r.groupPod(ws, pods)-
这里有个重要函数叫做
getSuitableSubsetNameForPod,它会给 subset 进行打分(matched, preferredScore, err := matchesSubset(pod, node, subset))返回最匹配的 subset- 该函数会调用
patchFavoriteSubsetMetadataToPod(pod, ws, favoriteSubset)对 pod 打一个 annotation "apps.kruise.io/matched-workloadspread" 以标注它所属的 subset
- 该函数会调用
-
-
计算删除代价并将其写到 pod 的 annotation(controller.kubernetes.io/pod-deletion-cost) 中: subset 的删除需要按照一定顺序: 总是按照删除代价最小的 pod 进行删除。删除代价被分为三部分部分: 一部分是当 subset > maxreplicas 时,额外 pod 的删除代价; 另一部分是当 subset <= maxreplicas 时普通的删除代价; 最后一部分是如果有 pods 属于 kruise.io/workloadspread-fake-subset-name, 即不匹配任何 subset,那么它始终会被计算出一个最小删除代价以满足最高优先级删除
- Deletion-cost: 100 * (subsets.length - subsetIndex) subset <= maxreplicas
- Deletion-cost: - 100 * (subsets.length -subsetIndex) subset > maxreplicas
- Deletion-cost: (0, -100 * (subsets.length + 1)) fake-subset-name
-
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
重调度(只更新状态)
-
对每个subset重新计算一遍 status (
calculateWorkloadSpreadSubsetStatus) ,比较值得关注的字段有如下几个- MissingReplicas: 它反映了未成功创建的 pod 数, 不包括正在创建( 创建时间距离现在小于 CreatPodTimeout(30s) ) 的
- subsetStatus.CreatingPods[podID] = createTime: 反映了各个 pod 的创建时间
-
并且对于 rescheduleCriticalSeconds > 0 (特性: 调度策略) 且不在最后一位的 subset 尝试重调度
rescheduleSubset()-
筛选出调度失败的 pods
-
如果有不可调度的 pod,那么把 subset 状态设置为 不可调度
-
如果当前没有不可调度的 pod 但是当前记录的 status 表明有错误,则会比较当前时间和上一次调度时间是否超过
MaxScheduledFailedDuration(300s)- 如果超过了
MaxScheduledFailedDuration,那么将 status 强制恢复成可调度状态 - 如果没超过
MaxScheduledFailedDuration,则保持不可调度状态直到MaxScheduledFailedDuration
- 如果超过了
-
最后该函数始终会返回一个不可调度 pods 列表
-
如果 rescheduleCriticalSeconds > 0 且为最后一个 subset,那么始终将它的状态设置为可调度
-