阅读本节前请了解 clonset 的使用和功能
OpenKruise 系列专栏: juejin.cn/column/7262…
目录一览: juejin.cn/post/726298…
openkruise doc :openkruise.io/zh/docs/use…
code: github.com/openkruise/…
api: apis/apps/v1alpha1/cloneset_types.go
controller: pkg/controller/cloneset/
Reconcile
预处理
// 预处理
selector, err := metav1.LabelSelectorAsSelector(instance.Spec.Selector)
filteredPods, filteredPVCs, err := r.getOwnedResource(instance)
filteredPods, err = r.claimPods(instance, filteredPods)
revisions, err := r.controllerHistory.ListControllerRevisions(instance, selector)
// Refresh update expectations
for _, pod := range filteredPods {
clonesetutils.UpdateExpectations.ObserveUpdated(request.String(), updateRevision.Name, pod)
}
为什么我们需要 claim pod?
在OpenKruise的CloneSet中,
claimPods方法用于声明属于CloneSet实例的Pod对象。尽管Pod对象在创建时可能已经属于该CloneSet,但在某些情况下,可能需要通过引用管理器(refmanager)来确认并标记这些Pod对象的所有权。在给定的代码中,
claimPods方法被调用来处理筛选后的Pod对象。这个调用的目的是确保这些Pod对象确实属于该CloneSet实例,并且在引用管理器中进行声明。这样做的原因可能有以下几个方面:
确保一致性和完整性:通过声明Pod对象的所有权,可以确保它们与CloneSet实例之间的关系是一致的。这有助于维护整个系统的一致性和完整性。
处理外部干预:在某些情况下,Pod对象可能会被手动修改或转移到其他控制器的管理下,从而导致与CloneSet实例的关联丢失。通过声明Pod对象的所有权,可以检测并处理这种外部干预,以确保Pod对象仍然属于正确的CloneSet实例。
引用管理和资源清理:引用管理器可以跟踪CloneSet实例与其管理的Pod对象之间的引用关系。通过声明Pod对象的所有权,可以更好地管理和清理与CloneSet实例相关联的资源,例如在删除CloneSet实例时自动清理相关的Pod对象。
总之,通过调用
claimPods方法并在引用管理器中声明Pod对象的所有权,可以确保CloneSet实例与其管理的Pod对象之间的关系是一致的,并提供更好的资源管理和系统的完整性。
原地升级自动预热
if !isPreDownloadDisabled {
if currentRevision.Name != updateRevision.Name {
createImagePullJobsForInPlaceUpdate(instance, currentRevision, updateRevision)
}
update/scale pod
入口:delayDuration, syncErr := r.syncCloneSet(instance, &newStatus, currentRevision, updateRevision, revisions, filteredPods, filteredPVCs)
// 获取当前和更新后的版本信息.
currentSet, err := r.revisionControl.ApplyRevision(instance, currentRevision)
updateSet, err := r.revisionControl.ApplyRevision(instance, updateRevision)
// Scale
scaling, podsScaleErr = r.syncControl.Scale(currentSet, updateSet, currentRevision.Name, updateRevision.Name, filteredPods, filteredPVCs)
clonset_scale.go: Scale()
处理 toDelete 和 preDelete 状态的 pods
删除标记是一个 annotation: SpecifiedDeleteKey = "apps.kruise.io/specified-delete", 有这种标记的 pod 被称为 podsSpecifiedToDelete (toDelete)
另外还有一个 annotation 叫做 "lifecycle.apps.kruise.io/state" 来表示 pod 的生命周期,它有以下值
LifecycleStateNormal LifecycleStateType = "Normal"
LifecycleStatePreparingUpdate LifecycleStateType = "PreparingUpdate"
LifecycleStateUpdating LifecycleStateType = "Updating"
LifecycleStateUpdated LifecycleStateType = "Updated"
LifecycleStatePreparingDelete LifecycleStateType = "PreparingDelete"
我们通过podsSpecifiedToDelete, podsInPreDelete, numToDelete := getPlannedDeletedPods(updateCS, pods) 拿到处于 toDelete 和 preDelete 的 pods, 通过
r.managePreparingDelete(updateCS, pods, podsInPreDelete, numToDelete)
将所有不处于 toDelete 但处于 preDelete 状态的 pod(在 podsInPreDelete 数组里) 的生命期周期状态修正为 "Normal", 目的是在删除阶段能够使得 pod 被正确清理
Scale
-
调用
calculateDiffsWithExpectation(updateCS, pods, currentRevision, updateRevision)返回一个 expectationDiffs 对象(diffRes)- 该函数根据当前 CloneSet 的配置(比如 MaxSurge 最大弹性数量等)和 pods 的状态计算出扩缩容和更新所需的Pod数量并返回
-
type expectationDiffs struct { // 代表需要扩缩容 pod 的数量差值,是一个有符号数 // > 0 代表扩容, < 0 代表缩容 scaleNum int // scaleNum中属于旧版本Pod的数量 scaleNumOldRevision int // 在扩容时创建Pod的上限数量。受scaleStrategy.maxUnavailable的限制 scaleUpLimit int // 可以删除的就绪Pod的上限数量。受UpdateStrategy.maxUnavailable的限制 deleteReadyLimit int // 临时超过所需副本数的Pod数量。用于表示临时的扩容数量。 useSurge int // useSurge中属于旧版本Pod的数量。用于指示临时扩容的旧版本Pod数量 useSurgeOldRevision int // 需要进行更新的 Pod 数量 // 正数表示需要更新指定数量的 Pod 到 updateRevision; // 负数表示需要回滚指定数量的 Pod 到 currentRevision。 updateNum int // 可以进行更新的就绪Pod的最大数量。用于限制更新操作的可用Pod数量。 updateMaxUnavailable int }
Attachment:
这个算法用于计算当前CloneSet的扩缩容和更新所需的Pod数量。下面是算法的具体逻辑:
获取CloneSet的相关配置和参数。
初始化变量用于统计各种Pod数量。
遍历所有的Pod,根据其所属的版本进行分类统计。
对于属于新版本的Pod:
如果Pod处于预删除状态,增加预删除计数。
否则,如果Pod被指定为删除,则增加要删除的新版本Pod计数。
否则,如果Pod不可用(未准备好),增加不可用的新版本Pod计数。
否则,增加活跃的新版本Pod计数。
对于属于旧版本的Pod:
如果Pod处于预删除状态,增加预删除计数。
否则,如果Pod被指定为删除,则增加要删除的旧版本Pod计数。
否则,如果Pod不可用(未准备好),增加不可用的旧版本Pod计数。
否则,增加活跃的旧版本Pod计数。
根据新旧版本Pod的数量差异和相关配置,计算扩缩容和更新所需的Pod数量。
如果允许使用扩容(maxSurge大于0):
如果存在要删除的Pod,则计算可用于扩容的Pod数量,并确保不超过maxSurge。
否则,根据新旧版本Pod数量差异计算可用于扩容的Pod数量,并确保不超过maxSurge。
计算需要缩容的Pod数量,确保不超过当前Replicas数。
如果需要扩容:
计算可用于扩容的Pod数量上限,确保不超过scaleMaxUnavailable和总不可用Pod数量的差值。
计算可用于删除的Pod数量上限,确保不超过maxUnavailable和总不可用Pod数量的差值。
如果存在要删除的Pod或需要缩容:
计算可用于删除的Pod数量上限,确保不超过maxUnavailable和总不可用Pod数量的差值。
- 根据更新的新旧版本Pod数量差异,计算更新的Pod数量上限。
- 返回计算结果。
-
将 pod 分组
updatedPods, notUpdatedPods := clonesetutils.SplitPodsByRevision(pods, updateRevision) -
扩容 (diffRes.scaleNum > 0 && diffRes.scaleUpLimit > 0): 创建 pod 和 pvc
-
删除
podsInPreDelete数组里的 pods -
对于不在
podsInPreDelete但在podsSpecifiedToDelete中的pods (toDelete), 采用一下逻辑进行删除- 首先把要删除的 pod 根据 revision 分为两组 oldPodsToDelete 和 newPodsToDelete, 分别代表更新后和更新前的不同版本
- 优先从 newPodsToDelete 中选取diffRes.deleteReadyLimit个pod标记删除,如果数量不够,再从 oldPodsToDelete 里面按序拿,以此规则来对删除的顺序和并发度进行限制
-
缩容: 缩容的关键是选取哪些 pod 应当被删除,具体可以参考函数
choosePodsToDelete,规则简述如下:-
优先删除 notUpdatedPods, 如果不够再从 UpdatedPods 里面选。 其中从 notUpdatedPods 里面可选的 pod 数量受 diffRes.scaleNumOldRevision 限制。
-
从集合里选取 pod 的规则:如果配置了 Pod 拓扑分布约束(Topology Spread Constraints), 则按照该规则排序 pods, 没有则按照 NewSameNodeRanker 规则进行排序,该规则可以理解为 pod 的默认权重,对位于同一个 node 上的 pos 按照 ActivePodsWithRanks 排序。
-
生命周期钩子
先阅读官方文档,重复内容不赘述
生命周期钩子是代码切面的一种实现,在 pod 状态流转时通过 lifecycle 暴露切面,使得用户可以注入自定义逻辑。
每个 cloneset 所管理的 pod 会有明确的状态,在 pod label 中被 lifecycle.apps.kruise.io/state 所反应,总共五个状态
LifecycleStateNormal LifecycleStateType = "Normal"
LifecycleStatePreparingUpdate LifecycleStateType = "PreparingUpdate"
LifecycleStateUpdating LifecycleStateType = "Updating"
LifecycleStateUpdated LifecycleStateType = "Updated"
LifecycleStatePreparingDelete LifecycleStateType = "PreparingDelete"
Pod 会继承用户在 asts 中定义的 PreNormal / PreparingDelete / PreparingUpdate 生命周期钩子,那么在 cs pod 创建/删除/更新的时候会通过函数 IsPodHooked 判断并调整 pod 生命周期 state,如果没有相关的钩子则会直接进入 normal / updating / terminating。
如果有生命周期钩子, pod 上会看到 lable/finalizer 信息,需要用户的 controller 去处理这些消息,当所有 finalizer 处理完毕后,框架才会进行状态流转
使用
lifecycle:
preDelete:
markPodNotReady: true
finalizersHandler:
- example.io/unready-blocker
代码示例
生命周期钩子相关的代码仍然注册在 reconcile 流程中
例如删除函数在发现有生命周期钩子后就会把实例状态变更为 “PreparingDelete”, 只有用户的controller去掉了对应的 lable/finalizer 时, 状态才会流转到 Delete
func (r *realControl) deletePods(cs *appsv1alpha1.CloneSet, podsToDelete []*v1.Pod, pvcs []*v1.PersistentVolumeClaim) (bool, error) {
var modified bool
for _, pod := range podsToDelete {
if cs.Spec.Lifecycle != nil && lifecycle.IsPodHooked(cs.Spec.Lifecycle.PreDelete, pod) {
if updated, err := r.lifecycleControl.UpdatePodLifecycle(pod, appspub.LifecycleStatePreparingDelete); err != nil {
return false, err
} else if updated {
klog.V(3).Infof("CloneSet %s scaling update pod %s lifecycle to PreparingDelete",
clonesetutils.GetControllerKey(cs), pod.Name)
modified = true
clonesetutils.ResourceVersionExpectations.Expect(pod)
}
continue
}
......
}
return modified, nil
}
type Lifecycle struct {
// PreDelete is the hook before Pod to be deleted.
PreDelete *LifecycleHook `json:"preDelete,omitempty"`
// InPlaceUpdate is the hook before Pod to update and after Pod has been updated.
InPlaceUpdate *LifecycleHook `json:"inPlaceUpdate,omitempty"`
}
func IsPodHooked(hook *appspub.LifecycleHook, pod *v1.Pod) bool {
if hook == nil || pod == nil {
return false
}
for _, f := range hook.FinalizersHandler {
if controllerutil.ContainsFinalizer(pod, f) {
return true
}
}
for k, v := range hook.LabelsHandler {
if pod.Labels[k] == v {
return true
}
}
return false
}