Kubernetes Deployment滚动更新原理解析
文章目录
分析基于kubernetes-1.15.5源码。
Deployment是新一代用于Pod管理的资源对象,除了继承了Replication的全部功能外,还在此基础上提供了更加完善的功能,特别是提供了滚动更新的功能,这对服务平滑升级简直太友好了。关于Rolling Update它有几个重要的参数用来控制滚动更新的动作:
.spec.minReadySeconds.spec.strategy.rollingUpdate.maxSurge.spec.strategy.rollingUpdate.maxUnavailable
为了更好的理解这几个参数的作用,有必要深入分析一下Deployment Controller的处理逻辑。
控制器相关的配置项
--concurrent-deployment-syncsint32 Default: 5 The number of deployment objects that are allowed to sync concurrently. Larger number = more responsive deployments, but more CPU (and network) load--deployment-controller-sync-periodduration Default: 30s Period for syncing the deployments.
Watch GVK
|
|
- Apps/V1/Deployments
- Apps.V1.ReplicaSets
- Core.V1.Pods。
Event Handler
|
|
Run函数
|
|
- 等待本地
Informer cache同步完成 - 开启workers(workers由
--concurrent-deployment-syncs参数指定,默认为5)个协程从任务队列中消费任务然后交给syncHandler处理,syncHandler的逻辑在syncDeployment函数,Deployment Controller的关键函数。
核心逻辑syncDeployment
Deployment Controller核心逻辑syncDeployment函数
|
|
- 从
Informer cache中获取deployment对象信息 - 获取
deployment管理的rs - 判断
deployment是否已经被删除,只有当删除策略为Foreground时才会出现 - 判断是否处于暂停状态,如果是则更新
Conditions,目的是在暂停的这段时间内不记时,防止触发spec.progressDeadlineSeconds - 通过检测
.spec.rollbackTo判断是否需要回退到指定Revision - 判断是否需要
scale,这里也能看出扩缩容的操作的优先级要高于更新操作,下边会详细 - 更新,根据
.spec.strategy.type指定的更新策略选择不同的处理逻辑,这里我们只分析RollingUpdate类型
scale 扩缩容
先来看下是如何判断是否需要scale的,逻辑在isScalingEvent函数
isScalingEvent
|
|
此函数就不再往下展开了,关键逻辑已经备注出来了。
下面来看下scale的逻辑,主要有sync函数完成
|
|
重点来看下scale函数
scale
|
|
根据FindActiveOrLatest函数返回结果分这么几种情况:
|
|
-
返回不为nil,这里又分两种情况
- 只有一个活跃rs,这种情况比较好理解,deployment.Spec.Template内容不变仅修改了deployment.spec.replicas,简单的扩缩容操作
- 没有活跃rs,我没想出来什么情况下为会现,有知道的朋友请告知下。
-
返回为nil,说明有两个及以上个活跃rs,这种情况出现在上次滚动更新还未完成又应用了一次修改(至少有扩容)时,这时需要根据rs的占比情况计算出待scale的数量。
RollingUpdate
Deployment滚动更新实际是依靠新旧rs交接棒完成的,更新过程分成两步:Scale up和Scale down。
-
Scale up负责将新rs的replicas朝着deployment.Spec.Replicas指定的数据递加。 -
Scale down负责将旧的replicas朝着0的目标递减。
一次完整的滚动更新需要经过很多轮Scale up和Scale down 的过程,这对理解pause和resume很重要。
PS:新旧rs,这里的新旧rs不是通过创建时间来区分的而是通过将rs.Spec.Template和deployment.Spec.Template对比,如果找到一致的就将此rs置为新rs,如果找不到就新建rs并置为新rs,不是简单的通过创建时间区分,这也解释了Deployment并不是每次更新都新建rs,关于此点后边会单独再详细分析。
|
|
-
获取deployment管理的全部RS,并通过比对Spec.Template信息找出新旧RS,新RS如果不存在则新创建
-
调用
dc.reconcileNewReplicaSet方法判断是否需要Scale up -
调用
dc.reconcileOldReplicaSets方法判断是否需要Scale down -
如果更新已经完成则清理历史
rs,最多只保留.spec.RevisionHistoryLimit个历史版本 -
更新
deployment状态,通过kubectl describe可以看到下面信息1 2 3 4 5 6 7Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 61s (x2 over 8h) deployment-controller Scaled up replica set my-nginx-79cb8c4647 to 1 Normal ScalingReplicaSet 49s (x2 over 8h) deployment-controller Scaled up replica set my-nginx-79cb8c4647 to 2 Normal ScalingReplicaSet 49s deployment-controller Scaled down replica set my-nginx-9f4d8c9d5 to 3 Normal ScalingReplicaSet 37s (x2 over 8h) deployment-controller Scaled up
Scale up的逻辑
|
|
- 判断是否需要
Scale up - 判断是否需要先
Scale down。 - 调用
deploymentutil.NewRSNewReplicas获取能Scale up的数量 - 调用api更新
rs的replicas,然后rs controller会负责pod的创建
继续来看下deploymentutil.NewRSNewReplicas方法,.spec.strategy.rollingUpdate.maxSurge参数的作用也就在于此。
|
|
函数逻辑比较简单,从中我们能看出的.spec.strategy.rollingUpdate.maxSurge的作用:
在scale up的时候所有pod不能超过
deployment.Spec.Replicas+.spec.strategy.rollingUpdate.maxSurge相加之和,.spec.strategy.rollingUpdate.maxSurge可以是整数或百分比,是百分比时需要向上取整(如0.1就限1)。
Scale down的逻辑
|
|
其中minAvailable := *(deployment.Spec.Replicas) - maxUnavailable 一句比较关键,
在scale down的过程中必须保证当前可用pod不能少于
deployment.Spec.Replicas-.spec.strategy.rollingUpdate.maxUnavailable。.spec.strategy.rollingUpdate.maxUnavailable可以是整数或百分比,如果百分比就向下取整(如1.7就是1)
PS:这里一开始我在看源码时对maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount这行代码有个疑问,为什么不直接用readyPodCount - minAvailable计算出能scale down的pod数量呢?后来根据这个Issue,发现在之前确实使用过totalScaleDownCount := readyPodCount - minAvailable,也因此引入了一个Bug,如果新起的pod因为某种原因一直不能ready,会卡住后续的更新,想回滚也不行,想了解细节的同学可以看下那个Issue。
Deployment 删除
从源码中我们可以看到Deployment Controller中没有deployment的删除逻辑,其实deployment的删除及关联的rs、Pod的删除是在GC Controller中处理的,以后有机会再分析下GC Controller的逻辑。
同一个Deployment先后触发滚动更新会如何处理?
如果上一次滚动更新还未完成马上接着又对此deployment执行了一次滚动更新,控制器又会如何处理呢?Scale up的流程参加上边分析的过程会创建New rs,但Scale down会如何处理呢,是Scale down上一次滚动更新刚创建的rs还是更老的rs的呢?
|
|
答案是先scale down最老的rs,然后再Scale down上次更新时创建的rs.
pause和resume
如果deployment还在滚动更新中我们执行了kubectl rollout pause 命令,控制器又会如何处理?
在Rolling Update章节我们已经提到过一次完成的滚动更新需要经过多轮Scale up和Scale down的过程,当执行暂停操作只会影响下一轮的Scale up或Scale down而不会影响本轮的操作。是不是也侧面说明了kubernetes操作都是声明式的而非命令式的。
minReadySeconds的作用
.spec.minReadySeconds的作用是在Scale up的过程中新创建的pod在本身ready的基础上会再等上minReadySeconds才会认为pod已经是可用状态,然后才会接着开始scale down,相当于一个观察期的作用,防止新起的pod发生crash,进而影响服务的可用性,保证集群在更新过程的稳定性。
在测试过程中可以适当增加这个值,人为减慢滚动更新的进度,方便我们使用kubectl get rs -w观察滚动更新的过程。
Scale down过程中被kill Pod的优先级
在滚动更新Scale down阶段需要杀掉老的pod,这些需要被杀掉的pod是如何被筛选出来的呢?
|
|
|
|
注释已经解释的比较清楚了,就不赘述了。
Deployment每次更新都会创建新Replicasets吗?
前边在分析rolloutRolling函数时已经提到过,答案是并不会每次都创建新的rs,详细逻辑我们来看下
|
|
可以看到Deployment每次在更新时会遍历RS通过对比Spec.Template内容判断是否需要新建RS,如果有相同的就在此RS的基础上更新scale up/down操作。细心的朋友日常使用中可能已经发现了此现象,原理就在此。
调试
kubectl get rs -wwatch rs 的变化kubectl describe deploy <name>查看deployment的更新状态
总结
Kubernetes Deployment滚动更新是靠新老RS交接棒完成的,新的RS scale up->老的RS scale down->新的RS scale up的…… 一直循环直到新的RS repliacs的数量达到期望值。在滚动更新的过程中会遵循:
- 总pod数不能超过
deployment.Spec.Replicas+.spec.strategy.rollingUpdate.maxSurge - 保证当前ready的pod不能少于
deployment.Spec.Replicas-.spec.strategy.rollingUpdate.maxUnavailable
在生产环境实际操作中默认25%对replicas基数很大的服务是不合适的,因为在滚动更新的一瞬间maxSurge可能突破你集群资源的上限,maxUnavailable也可能会击穿你服务性能水平的下限,因此一定要根据自己服务的情况做相应调整。
参考
https://github.com/kubernetes/kubernetes/issues/22065
https://github.com/kubernetes/kubernetes/pull/20368/commits/86aea1d59c42de15afbff5e2388e4b764bd134fc
文章作者 XniLe
上次更新 2023-10-10