Deployment是k8s中部署更新的关键实现,今天我们一起初探下其关键机制包括: 暂停、回滚、扩缩容、更新策略的核心实现

1. 基础概念

Deployment本质上其实只是一种部署策略,在了解其实现之前,先简单介绍一下部署系统里面常见的概念,Deployment里面的各种参数和设计其实也都是围绕着这些展开的

1.1 ReplicaSet

893edc72d246388a328fe34f9d03e497.png

Deployment本身并不直接操作Pod,每当其更新的时候通过构建ReplicaSet来进行版本更新,在更新的过程中通过scale up(新的RS)和scale down(旧的RS)来完成

1.2 部署状态

在k8s的官方文档中主要是介绍了Deployment的三种状态, 对应的Condition分别为Available、Progressing、ReplicaFailure三种状态, 并且每个状态下面又会很有导致对应状态切换的不同的Reson,Reson可能是运维过程中最需要关注的点

1.3 部署策略

部署策略是Deployment控制ReplicaSet更新的策略,通过对新旧ReplicaSet的扩缩容,再满足部署策略的情况下,将系统更新至最新的目标状态,Deployment本身并没有太多可选的策略,默认只有两种Recreate和RollingUpdate

d19b5805551353ef61c110054880099d.png

在一些大版本产品更新的时候,新旧版本的数据库模型都不一致的情况下,通常会选择停服操作,此时可以选择Recreate即将所有老的副本都干掉,然后重新创建一批。当然默认情况下大部分业务还是RollingUpdate即滚动更新即可

1.4 可用性与高低水位

部署过程中最常被提到的可能就是可用性问题了,即在更新的过程中(RollingUpdate策略下)需要保证系统中可用的Pod在一个指定的水位,保证对应服务的可用性

bf8ae6d6edbe5fc4cdb9d866ac80a010.png

高低水位(deployment并没有这个词)其实就是对应的上面的可用性来说的,Deployment通过一些参数让我们可以自由控制在滚动更新的过程中,我们可以创建的Pod的最多数量(高水位)和可以删除的最多的Pod(低水位), 从而达到可用性保护的目标

部署的概念就介绍到这里, 接下来就一起看看Deployment中这些关键机制的具体实现

2. 核心实现

Deployment的实现上相对复杂一点,但是从场景上又可以简单的分为:删除、暂停、回滚、扩缩容、更新几个大的场景

2.1 暂停部署

暂停部署是用于中断Deployment更新流程的一种方式,但由于k8s中是基于事件驱动的最终一致性的系统,这里的中断仅仅意味着Deployment层不会进行的进行后续的副本变更,而底层的replicaSet此时如果还没有达到目标的副本,则就需要继续更新, 同时在暂停的过程中如果发现并没有尝试进行回滚到指定版本的操作,这时候还会进行一些副本的清理工作,即只保留最近的指定数量的历史副本

2.2 回滚控制

回滚控制里面的信息跟其他参数有些不同,其主要是通过在Annotations中存储的DeprecatedRollbackTo来进行指定版本的回滚

7512ca3d46a5d763318135a4304181b5.png

回滚的实现本质上就是从指定的Revisions中获取对应的replicaset的Pod模板,去覆盖当前的Deployment的Pod模板,并且更新Deployment即可, 那如果对应的版本不存在怎么办,如果是这种情况,其实就需要你自己去寻找历史版本了,并且k8s会给新添加一个RollbackRevisionNotFound类型的事件提示你版本不存在

2.3 扩缩容机制

扩缩容机制主要是指的Deployment的scale操作,在进行Deployment更新之前,会首先检查对应Deployment的副本的期望是否得到满足,只有期望的副本数得到满足,才会进行更新操作,所以在k8s中如果之前进行了扩缩容操作,则在该操作完成之前,是不会进行模板更新的

2.4 Recreate策略

9c832a617c254c662e1042223f22231a.png

Recreate部署策略在实现上通过两种机制保证之前的Pod一定被删除:所有活跃副本都为0和所有Pod都处于(PodFailed和PodSucceeded)两种状态下,然后才会创建新的副本,如果对应的副本完全就绪,还会进行清理历史副本

2.5 RollingUpdate策略

RollingUpdate策略可能是最复杂的部分之一了,里面很有多的参数控制,都作用于该策略,来一起看下

2.5.1 缩容过多的新版本

首先在更新的时候要做一致性检测,如果发现新版本的ReplicaSet比当前的deployment设定的副本数目多,则首先干掉这部分Pod, 同时会根据当前的Deployment的副本数来设定当前的期望副本数DesiredReplicasAnnotation, 并且根据maxSurge来计算当前最大的副本数量MaxReplicasAnnotation, 同时在这个同步ReplicaSet的minReadySeconds

2.5.2 扩容新副本

如果说新副本的数量不足,则就需要根据当前的maxSurege来设定,同时会再次计算当前的RS的所有Pod,如果发现Pod数量过多即超过Deployment的Replicas+maxSurge,则也不会进行操作

// Find the total number of pods        currentPodCount := GetReplicaCountForReplicaSets(allRSs)        // 最大pod数量        maxTotalPods := *(deployment.Spec.Replicas) + int32(maxSurge)        // 当前pod数量》总的运行的pod数量        if currentPodCount >= maxTotalPods {            // Cannot scale up.            return *(newRS.Spec.Replicas), nil        }

否则则就会进行计算允许scale up的数量

        scaleUpCount := maxTotalPods - currentPodCount        scaleUpCount = int32(integer.IntMin(int(scaleUpCount), int(*(deployment.Spec.Replicas)-*(newRS.Spec.Replicas))))

2.5.3 缩容旧的副本

缩容计数器的算法计算主要是根据Deployment的Replcas和maxUnavailable(通过surge和maxUnavailable)共同计算而来,最终的公式其实如下,有了缩容的数量,就可以更新旧ReplicaSet的数量了

    minAvailable := *(deployment.Spec.Replicas) - maxUnavailable    // 新副本不可用数量    newRSUnavailablePodCount := *(newRS.Spec.Replicas) - newRS.Status.AvailableReplicas    // 最大缩容大小=所有pod统计-最小不可用-新副本不可用副本    maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount

整理如下

        最小可用副本     = Deployment的副本-最大不可用副本        新副本不可用统计= 新副本数量-可用副本数量        最大缩容数量   = 全部副本Pod计数-最小可用副本-新副本不可用统计

至此我们知道了Deployment扩缩容的核心的副本计算实现,也知道了扩缩容的流程,那还缺什么呢?答案是状态

2.5.4 Available状态

Deployment的状态主要是由新旧副本以及当前集群中的Pod决定的,其计算公式如下, 则认为当前可用否则即为不可用

    前可用的副本数量计数>=Deployment的副本数量-最大不可用副本计数
if availableReplicas >= *(deployment.Spec.Replicas)-deploymentutil.MaxUnavailable(*deployment) {        minAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionTrue, deploymentutil.MinimumReplicasAvailable, "Deployment has minimum availability.")        deploymentutil.SetDeploymentCondition(&status, *minAvailability)    } else {         noMinAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionFalse, deploymentutil.MinimumReplicasUnavailable, "Deployment does not have minimum availability.")        deploymentutil.SetDeploymentCondition(&status, *noMinAvailability)    }

2.5.5 Processing状态

首先并不是所有的Deployment都有该状态,只有设置了progressDeadlineSeconds参数的才会有该状态,其主要实在Deployment未完成的时候,进行一些状态决策,从而避免一个Deployment无期限的运行,其关键状态有两个即运行中与超时决策, 其流程实现上分为两步1)首先如果判断是正在运行中,就更新LastTransitionTime

condition := util.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, util.ReplicaSetUpdatedReason, msg)condition.LastTransitionTime = currentCond.LastTransitionTime

2)超时检测则及时通过记录之前的转换时间,然后决策是否超时

    from := condition.LastUpdateTime    now := nowFn()    delta := time.Duration(*deployment.Spec.ProgressDeadlineSeconds) * time.Second    timedOut := from.Add(delta).Before(now)

2.5.6 ReplicaFailure状态

该状态相对简单,检测当前的所有的副本,如果发现有副本失败,就取最新的一条失败的信息来填充Condition

3. 小结

6d5ddc7000fae4576ce2f013681f78ca.png

更新机制的核心实现可能就这些,代码实现上还是相对复杂的,主要是集中在为了保证伸缩和更新时为了保证可用性而做了大量的计算,还有很多的边界条件的处理,先就关注到这里,里面的具体的更细的细节,等出问题的时候,再详细琢磨,今天就到这里了

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐