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-syncs
int32 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-period
duration 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 7
Events: 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 -w
watch 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