Deployment
Deployment
前面我们学习了 ReplicaSet 控制器,了解到该控制器是用来维护集群中运行的 Pod 数量的,但是往往在实际操作的时候,我们反而不会去直接使用 RS,而是会使用更上层的控制器,比如我们今天要学习的主角 Deployment。Deployment 一个非常重要的功能就是实现了 Pod 的滚动更新,比如我们应用要更新,我们只需要更新我们的容器镜像,然后修改 Deployment 里面的 Pod 模板镜像,那么 Deployment 就会用**滚动更新(Rolling Update)**的方式来升级现在的 Pod,这个能力是非常重要的。因为对于线上的服务我们需要做到不中断服务,所以滚动更新就成了必须的一个功能。而 Deployment 这个能力的实现,依赖的就是上节课我们学习的ReplicaSet 这个资源对象,实际上我们可以通俗的理解就是每个Deployment 就对应集群中的一次部署,这样就更好理解了。
Deployment是最常用的K8s工作负载控制器(Workload Controllers),是K8s的一个抽象概念,用于更高级层次对象,部署和管理Pod,其他控制器还有DaemonSet、StatefulSet等。
Deployment的主要功能:
管理Pod和ReplicaSet
具有上线部署、副本设定、滚动升级、回滚等功能
提供声明式更新,例如只更新一个新的Image
应用场景:网站、API、微服务
deployment应用生命周期管理流程:
控制器的协调工作原理
💘 实战:Deployment之水平伸缩和滚动更新测试-2022.12.15(成功测试)
- 实验环境
1、win10,vmwrokstation虚机;2、k8s集群:3台centos7.61810虚机,2个master节点,1个node节点k8sversion:v1.20CONTAINER-RUNTIME:containerd:v1.6.10
实验软件(无)
Deployment 资源对象的格式和 ReplicaSet 几乎一致,如下资源对象就是一个常见的 Deployment 资源类型:
# nginx-deploy.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deploynamespace:defaultlabels:#这个标签仅仅用于标记deployment本身这个资源对象。注意:deployment里metadata里的label标签定不定义都无所谓,没有什么实际意义,因为deployment在k8s已经是一个很高级的概念了,没有什么人可以管它了哈哈。role:deployspec:replicas:3# 期望的 Pod 副本数量,默认值为1selector:# Label Selector,必须匹配Pod模板中的标签。matchLabels:#这里也可以是matchExpressionsapp:nginxtemplate:# Pod 模板metadata:labels:app:nginx#一定要包含上面的 matchLabels里面的标签。spec:containers:- name:nginximage:nginx:latest# latest标签最好别用在线上ports:- containerPort:80
- 我们这里只是将类型替换成了 Deployment,我们可以先来创建下这个资源对象:
[root@master1 ~]#kubectl apply -f nginx-deploy.yaml deployment.apps/nginx-deploycreated[root@master1 ~]#kubectl get deployments.apps NAMEREADYUP-TO-DATEAVAILABLEAGEnginx-deploy3/33323s
- 创建完成后,查看 Pod 状态:
[root@master1 ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEnginx-deploy-cd55c47f5-gwrb51/1Running048snginx-deploy-cd55c47f5-h6spx1/1Running048snginx-deploy-cd55c47f5-x5mgl1/1Running048s
到这里我们发现和之前的 RS 对象是否没有什么两样,都是根据spec.replicas 来维持的副本数量。
- 我们随意查看一个Pod 的描述信息:
[root@master1 ~]#kubectl describe po nginx-deploy-cd55c47f5-gwrb5Name:nginx-deploy-cd55c47f5-gwrb5Namespace:defaultPriority:0ServiceAccount:defaultNode:node1/172.29.9.62StartTime:Thu,15Dec202207:24:20+0800Labels:app=nginxpod-template-hash=cd55c47f5Annotations:<none>Status:RunningIP:10.244.1.16IPs:IP:10.244.1.16ControlledBy:ReplicaSet/nginx-deploy-cd55c47f5……Events:TypeReasonAgeFromMessage-------------------------NormalScheduled103sdefault-schedulerSuccessfullyassigneddefault/nginx-deploy-cd55c47f5-gwrb5tonode1NormalPulling102skubeletPullingimage"nginx:latest"NormalPulled87skubeletSuccessfullypulledimage"nginx:latest"in15.23603838sNormalCreated87skubeletCreatedcontainernginxNormalStarted87skubeletStartedcontainernginx
我们仔细查看其中有这样一个信息 Controlled By:ReplicaSet/nginx-deploy-cd55c47f5,什么意思?是不是表示当前我们这个 Pod 的控制器是一个 ReplicaSet 对象啊,我们不是创建的一个 Deployment 吗?为什么Pod 会被 RS 所控制呢?
- 那我们再去看下这个对应的 RS 对象的详细信息如何呢:
[root@master1 ~]#kubectl describe rs nginx-deploy-cd55c47f5 Name:nginx-deploy-cd55c47f5Namespace:defaultSelector:app=nginx,pod-template-hash=cd55c47f5Labels:app=nginxpod-template-hash=cd55c47f5Annotations:deployment.kubernetes.io/desired-replicas:3deployment.kubernetes.io/max-replicas:4deployment.kubernetes.io/revision:1ControlledBy:Deployment/nginx-deployReplicas:3current/3desiredPodsStatus:3Running/0Waiting/0Succeeded/0FailedPodTemplate:Labels:app=nginxpod-template-hash=cd55c47f5Containers:nginx:Image:nginx:latestPort:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>Events:TypeReasonAgeFromMessage-------------------------NormalSuccessfulCreate3m41sreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-gwrb5NormalSuccessfulCreate3m41sreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-x5mglNormalSuccessfulCreate3m41sreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-h6spx
其中有这样的一个信息: Controlled By:Deployment/nginx-deploy,明白了吧?意思就是我们的 Pod 依赖的控制器 RS 实际上被我们的 Deployment 控制着呢。
- 我们可以用下图来说明 Pod、ReplicaSet、Deployment 三者之间的关系:
通过上图我们可以很清楚的看到,定义了 3 个副本的 Deployment 与 ReplicaSet 和 Pod 的关系,就是一层一层进行控制的。ReplicaSet 作用和之前一样还是来保证 Pod 的个数始终保存指定的数量,所以 Deployment 中的容器restartPolicy=Always 是唯一的就是这个原因,因为容器必须始终保证自己处于 Running 状态,ReplicaSet 才可以去明确调整 Pod 的个数。而 Deployment 是通过管理 ReplicaSet 的数量和属性来实现水平扩展/收缩以及滚动更新两个功能的。
目录
[toc]
1、水平伸缩
**水平扩展/收缩**
的功能比较简单,因为 ReplicaSet 就可以实现,所以 Deployment 控制器只需要去修改它所控制的 ReplicaSet 的 Pod 副本数量就可以了。比如现在我们把 Pod 的副本调整到 4 个,那么 Deployment 所对应的 ReplicaSet 就会自动创建一个新的 Pod 出来,这样就水平扩展了。
- 先来看下当前deployment、rs的数量:
[root@master1 ~]#kubectl get deployNAMEREADYUP-TO-DATEAVAILABLEAGEnginx-deploy3/3339m48s#这里表示有3个replica[root@master1 ~]#kubectl get rsNAMEDESIREDCURRENTREADYAGEnginx-deploy-cd55c47f53339m50s#这里表示有3个replica 即:deployment和rs这的replicas的数量是保持一致的
- 我们可以使用一个新的命令
kubectl scale
命令来完成这个操作:
开始扩容pod
[root@master1 ~]#kubectl scale deployment nginx-deploy --replicas=4deployment.apps/nginx-deployscaled
- 扩展完成后可以查看当前的 RS 对象
[root@master1 ~]#kubectl get rsNAMEDESIREDCURRENTREADYAGEnginx-deploy-cd55c47f54439m#可以看到期望的 Pod 数量已经变成 4 了,只是 Pod 还没准备完成,所以 READY 状态数量还是 3[root@master1 ~]#kubectl get rsNAMEDESIREDCURRENTREADYAGEnginx-deploy-cd55c47f544411m[root@master1 ~]#kubectl get deployNAMEREADYUP-TO-DATEAVAILABLEAGEnginx-deploy4/44411m[root@master1 ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEnginx-deploy-cd55c47f5-gwrb51/1Running011mnginx-deploy-cd55c47f5-h6spx1/1Running011mnginx-deploy-cd55c47f5-vt4271/1Running040snginx-deploy-cd55c47f5-x5mgl1/1Running011m
可以看到期望的 Pod 数量已经变成 4 了,只是 Pod 还没准备完成,所以 READY 状态数量还是 3。
- 同样查看 RS 的详细信息:
[root@master1 ~]#kubectl describe rs nginx-deploy-cd55c47f5Name:nginx-deploy-cd55c47f5Namespace:defaultSelector:app=nginx,pod-template-hash=cd55c47f5Labels:app=nginxpod-template-hash=cd55c47f5Annotations:deployment.kubernetes.io/desired-replicas:4deployment.kubernetes.io/max-replicas:5deployment.kubernetes.io/revision:1ControlledBy:Deployment/nginx-deployReplicas:4current/4desiredPodsStatus:4Running/0Waiting/0Succeeded/0FailedPodTemplate:Labels:app=nginxpod-template-hash=cd55c47f5Containers:nginx:Image:nginx:latestPort:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>Events:TypeReasonAgeFromMessage-------------------------NormalSuccessfulCreate13mreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-gwrb5NormalSuccessfulCreate13mreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-x5mglNormalSuccessfulCreate13mreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-h6spxNormalSuccessfulCreate3m2sreplicaset-controllerCreatedpod:nginx-deploy-cd55c47f5-vt427
可以看到 ReplicaSet 控制器增加了一个新的 Pod。
- 同样的 Deployment 资源对象的事件中也可以看到完成了扩容的操作:
[root@master1 ~]#kubectl describe deploy nginx-deploy Name:nginx-deployNamespace:defaultCreationTimestamp:Thu,15Dec202207:24:20+0800Labels:<none>Annotations:deployment.kubernetes.io/revision:1Selector:app=nginxReplicas:4desired|4updated|4total|4available|0unavailableStrategyType:RollingUpdateMinReadySeconds:0RollingUpdateStrategy:25%maxunavailable,25%maxsurgePodTemplate:Labels:app=nginxContainers:nginx:Image:nginx:latestPort:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>Conditions:TypeStatusReason----------------AvailableTrueMinimumReplicasAvailableProgressingTrueNewReplicaSetAvailableOldReplicaSets:<none>NewReplicaSet:nginx-deploy-cd55c47f5(4/4 replicascreated)Events:TypeReasonAgeFromMessage-------------------------NormalScalingReplicaSet14mdeployment-controllerScaledupreplicasetnginx-deploy-cd55c47f5to3NormalScalingReplicaSet3m58sdeployment-controllerScaledupreplicasetnginx-deploy-cd55c47f5to4from3
- 注意:水平伸缩,并不是一次升级,因此这里的revision不会变的水平扩容/缩容不会创建新的rs,只有更新镜像,触发滚动升级时才会触发rs,才回记录那个版本; scale up/down
[root@master1 ~]#kubectl get po,rs,deployNAMEREADYSTATUSRESTARTSAGEpod/nginx-deploy-cd55c47f5-gwrb51/1Running016mpod/nginx-deploy-cd55c47f5-h6spx1/1Running016mpod/nginx-deploy-cd55c47f5-vt4271/1Running05m49spod/nginx-deploy-cd55c47f5-x5mgl1/1Running016mNAMEDESIREDCURRENTREADYAGEreplicaset.apps/nginx-deploy-cd55c47f544416mNAMEREADYUP-TO-DATEAVAILABLEAGEdeployment.apps/nginx-deploy4/44416m[root@master1 ~]#kubectl rollout history deployment nginx-deploy deployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>
好了,接下来我们看一下deployment的滚动更新。
2、滚动更新
滚动升级:K8s对Pod升级的默认策略,通过使用新版本Pod逐步更新旧版本Pod,实现零停机发布,用户无感知。
它是一个一个来的,不是一下子来的; Deployment实际上有维护着replicaset控制器,而这个控制器我们是不会直接去操作它的,而是从由deployment管理的,这是他的私属管理器;
如果只是水平扩展/收缩
这两个功能,就完全没必要设计 Deployment 这个资源对象了,Deployment 最突出的一个功能是支持滚动更新
。
- 我们先查看下deployment的升级策略:
[root@master1 ~]#kubectl explain deploy.spec.strategyKIND:DeploymentVERSION:apps/v1RESOURCE:strategy<Object>DESCRIPTION:Thedeploymentstrategytousetoreplaceexistingpodswithnewones.DeploymentStrategydescribeshowtoreplaceexistingpodswithnewones.FIELDS:rollingUpdate<Object>Rollingupdateconfigparams.PresentonlyifDeploymentStrategyType=RollingUpdate.type<string>Typeofdeployment.Canbe"Recreate"or"RollingUpdate".DefaultisRollingUpdate.#升级策略默认是RollingUpdate(滚动升级)
- 首先,我们先把默认的滚动升级策略改为
recreate
,并且把pod模板的image改为nginx:1.7.9
看下效果:
apiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deploynamespace:defaultlabels:#这个标签仅仅用于标记deployment本身这个资源对象role:deployspec:replicas:4#期望的Pod副本数量strategy:type:Recreateselector:#label selectormatchLabels:app:nginxtest:coursetemplate:#Pod模板metadata:labels:#一定要和上面的selector 保持一致app:nginxtest:coursespec:containers:- name:nginximage:nginx:1.7.9ports:- containerPort:80
- 再测试之前,我们先来再次看下当前的测试环境:
[root@master1 ~]#kubectl get po,deploy,rsNAMEREADYSTATUSRESTARTSAGEnginx-deploy-fd46765d4-8nzmp1/1Running09m8snginx-deploy-fd46765d4-9rzqt1/1Running09m8snginx-deploy-fd46765d4-ckdhw1/1Running09m8snginx-deploy-fd46765d4-tdkjv1/1Running02m38sNAMEREADYUP-TO-DATEAVAILABLEAGEdeployment.apps/nginx-deploy4/4443m15sNAMEDESIREDCURRENTREADYAGEreplicaset.apps/nginx-deploy-fd46765d44443m15s[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>
- 此时再打开一个终端,用watch命令监控下pod的变化情况:
[root@master1 ~]#kubectl get po --watchNAMEREADYSTATUSRESTARTSAGEnginx-deploy-fd46765d4-8nzmp1/1Running09m34snginx-deploy-fd46765d4-9rzqt1/1Running09m34snginx-deploy-fd46765d4-ckdhw1/1Running09m34snginx-deploy-fd46765d4-tdkjv1/1Running03m4s--testRecreate----
- 更新一下资源配置清单:
[root@master1 ~]#kubectl apply -f nginx-deploy.yamldeployment.apps/nginx-deployconfigured
- 此时观察下刚才那个watch pod的终端发生的变化:
我们会发现**Recreate**
表示全部重新创建,即把旧的pod全部删除掉,然后再用新镜像创建新版本的pod;
[root@master1 ~]#kubectl get po --watchNAMEREADYSTATUSRESTARTSAGEnginx-deploy-fd46765d4-8nzmp1/1Running09m34snginx-deploy-fd46765d4-9rzqt1/1Running09m34snginx-deploy-fd46765d4-ckdhw1/1Running09m34snginx-deploy-fd46765d4-tdkjv1/1Running03m4s--testRecreate----nginx-deploy-fd46765d4-tdkjv1/1Terminating03m25snginx-deploy-fd46765d4-8nzmp1/1Terminating09m55snginx-deploy-fd46765d4-ckdhw1/1Terminating09m55snginx-deploy-fd46765d4-9rzqt1/1Terminating09m55snginx-deploy-fd46765d4-ckdhw0/1Terminating09m56snginx-deploy-fd46765d4-ckdhw0/1Terminating09m56snginx-deploy-fd46765d4-ckdhw0/1Terminating09m56snginx-deploy-fd46765d4-9rzqt0/1Terminating09m56snginx-deploy-fd46765d4-9rzqt0/1Terminating09m56snginx-deploy-fd46765d4-9rzqt0/1Terminating09m56snginx-deploy-fd46765d4-tdkjv0/1Terminating03m26snginx-deploy-fd46765d4-tdkjv0/1Terminating03m26snginx-deploy-fd46765d4-tdkjv0/1Terminating03m26snginx-deploy-fd46765d4-8nzmp0/1Terminating09m56snginx-deploy-fd46765d4-8nzmp0/1Terminating09m56snginx-deploy-fd46765d4-8nzmp0/1Terminating09m56s#原来old pod被一起删除nginx-deploy-6c5ff87cf-4f2290/1Pending00snginx-deploy-6c5ff87cf-4f2290/1Pending00snginx-deploy-6c5ff87cf-w2csq0/1Pending00snginx-deploy-6c5ff87cf-p866j0/1Pending00snginx-deploy-6c5ff87cf-w2csq0/1Pending00snginx-deploy-6c5ff87cf-ttm2v0/1Pending00snginx-deploy-6c5ff87cf-p866j0/1Pending00snginx-deploy-6c5ff87cf-ttm2v0/1Pending00snginx-deploy-6c5ff87cf-p866j0/1ContainerCreating00snginx-deploy-6c5ff87cf-4f2290/1ContainerCreating01snginx-deploy-6c5ff87cf-w2csq0/1ContainerCreating02snginx-deploy-6c5ff87cf-ttm2v0/1ContainerCreating02snginx-deploy-6c5ff87cf-ttm2v1/1Running04snginx-deploy-6c5ff87cf-p866j1/1Running017snginx-deploy-6c5ff87cf-w2csq1/1Running019snginx-deploy-6c5ff87cf-4f2291/1Running034s[root@master1 ~]#kubectl get deploy,rs,poNAMEREADYUP-TO-DATEAVAILABLEAGEdeployment.apps/nginx-deploy4/44411mNAMEDESIREDCURRENTREADYAGEreplicaset.apps/nginx-deploy-6c5ff87cf44491s#当前rsreplicaset.apps/nginx-deploy-fd46765d400011mNAMEREADYSTATUSRESTARTSAGEpod/nginx-deploy-6c5ff87cf-4f2291/1Running091spod/nginx-deploy-6c5ff87cf-p866j1/1Running091spod/nginx-deploy-6c5ff87cf-ttm2v1/1Running091spod/nginx-deploy-6c5ff87cf-w2csq1/1Running091s[root@master1 ~]#[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>2<none>#当前rs#我们可以在看下当前rs的revisio是不是2呢:[root@master1 ~]#kubectl describe rs nginx-deploy-6c5ff87cf |greprevisiondeployment.kubernetes.io/revision:2#我们再看下nginx-deploy deploy的 event:[root@master1 ~]#kubectl describe deployments.apps |tail-8NewReplicaSet:nginx-deploy-6c5ff87cf(4/4 replicascreated)Events:TypeReasonAgeFromMessage-------------------------NormalScalingReplicaSet12mdeployment-controllerScaledupreplicasetnginx-deploy-fd46765d4to3NormalScalingReplicaSet5m46sdeployment-controllerScaledupreplicasetnginx-deploy-fd46765d4to4NormalScalingReplicaSet2m21sdeployment-controllerScaleddownreplicasetnginx-deploy-fd46765d4to0NormalScalingReplicaSet2m20sdeployment-controllerScaledupreplicasetnginx-deploy-6c5ff87cfto4
上面Recreate
升级策略验证完了,接下来我们验证下RollingUpdate
滚动更新升级策略。
- 先来看下
RollingUpdate
可以配置的选项:
[root@master1 ~]#kubectl explain deploy.spec.strategy.rollingUpdateKIND:DeploymentVERSION:apps/v1RESOURCE:rollingUpdate<Object>DESCRIPTION:Rollingupdateconfigparams.PresentonlyifDeploymentStrategyType=RollingUpdate.Spectocontrolthedesiredbehaviorofrollingupdate.FIELDS:maxSurge<string>Themaximum(最大限度) numberofpodsthatcanbescheduledabove(超过) thedesirednumberofpods.Valuecanbeanabsolute(绝对的) number(ex:5) or a percentage(百分比) of desiredpods(ex:10%). This can not be 0 ifMaxUnavailableis0.Absolutenumberiscalculated(计算) frompercentagebyroundingup(四舍五入).Defaultsto25%.Example:whenthisissetto30%,thenewReplicaSetcanbescaledupimmediatelywhentherollingupdatestarts,suchthatthetotalnumberofoldandnewpodsdonotexceed(超过) 130%ofdesiredpods.Once(一旦) oldpodshavebeenkilled,newReplicaSetcanbescaledupfurther(进一步),ensuringthattotalnumberofpodsrunningatanytimeduringtheupdateisatmost130%ofdesiredpods.maxUnavailable<string>Themaximumnumberofpodsthatcanbeunavailableduringtheupdate.Valuecanbeanabsolutenumber(ex:5) or a percentage of desired pods (ex:10%). Absolute number is calculated from percentage by rounding down(舍入,去尾法). Thiscannotbe0ifMaxSurgeis0.Defaultsto25%.Example:whenthisissetto30%,theoldReplicaSetcanbescaleddownto70%ofdesiredpodsimmediatelywhentherollingupdatestarts.Oncenewpodsareready,oldReplicaSetcanbescaleddownfurther,followedbyscalingupthenewReplicaSet,ensuringthatthetotalnumberofpodsavailableatalltimesduringtheupdateisatleast70%ofdesiredpods.
- 现在我们修改本次的升级策略为RollingUpdate,当然默认的策略就是RollingUpdate。同时,本次nginx镜像tag修改为latest。
#nginx-deploy.yaml apiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deploynamespace:defaultlabels:#这个标签仅仅用于标记deployment本身这个资源对象role:deployspec:replicas:4#期望的Pod副本数量minReadySeconds:5strategy:type:RollingUpdate#指定滚动更新策略,默认rollingUpdate:maxUnavailable:1#最大不可用的pod数量maxSurge:1selector:#label selectormatchLabels:app:nginxtest:coursetemplate:#Pod模板metadata:labels:#一定要和上面的selector 保持一致app:nginxtest:coursespec:containers:- name:nginximage:nginx:latest#本次nginx镜像tag修改为latestports:- containerPort:80
与前面相比较,除了更改了镜像之外,我们还指定了更新策略:
minReadySeconds:5strategy:type:RollingUpdaterollingUpdate:maxSurge:1maxUnavailable:1
**minReadySeconds**
:表示 Kubernetes 在等待设置的时间后才进行升级,如果没有设置该值,Kubernetes 会假设该容器启动起来后就提供服务了,如果没有设置该值,在某些极端情况下可能会造成服务不正常运行,默认值就是0。(这里应该说的是新版本容器启动后需要等待的时间;)type=RollingUpdate
:表示设置更新策略为滚动更新,可以设置为Recreate
和RollingUpdate
两个值,**Recreate**
表示全部重新创建,默认值就是RollingUpdate
。maxSurge
:表示升级过程中最多可以比原先设置多出的 Pod 数量,例如:maxSurage=1,replicas=5
,就表示Kubernetes 会先启动一个新的 Pod,然后才删掉一个旧的 Pod,整个升级过程中最多会有5+1
个 Pod。maxUnavaible
:表示升级过程中最多有多少个 Pod 处于无法提供服务的状态,当**maxSurge**
不为0时,该值也不能为0,例如:maxUnavaible=1
,则表示 Kubernetes 整个升级过程中最多会有1个 Pod 处于无法服务的状态(这里指的是老版本pod的不可用个数)。
maxSurge 和 maxUnavailable 属性的值不可同时为 0,否则 Pod 对象的副本数量在符合用户期望的数量后无法做出合理变动以进行滚动更新操作。
特别注意:
滚动升级时,有可能先创建新的pod,也有可能先删除老的pod,这个和配置的maxSurge和maxUnavailable
参数有关。并且,他们的配置数量也可以大于1;
- 本次测试前的实验话环境我们再次来确认下:
[root@master1 ~]#kubectl get deploy,rs,podNAMEREADYUP-TO-DATEAVAILABLEAGEdeployment.apps/nginx-deploy4/44417mNAMEDESIREDCURRENTREADYAGEreplicaset.apps/nginx-deploy-6c5ff87cf4444m50sreplicaset.apps/nginx-deploy-fd46765d400017mNAMEREADYSTATUSRESTARTSAGEnginx-deploy-6c5ff87cf-4f2291/1Running03m14snginx-deploy-6c5ff87cf-p866j1/1Running03m14snginx-deploy-6c5ff87cf-ttm2v1/1Running03m14snginx-deploy-6c5ff87cf-w2csq1/1Running03m14s[root@master1 ~]#[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>2<none>[root@master1 ~]#[root@master1 ~]#kubectl describe rs nginx-deploy-6c5ff87cf|greprevisiondeployment.kubernetes.io/revision:2[root@master1 ~]#[root@master1 ~]#kubectl describe po nginx-deploy-6c5ff87cf-4f229 |grepImageImage:nginx:1.7.9ImageID:sha256:35d28df486f6150fa3174367499d1eb01f22f5a410afe4b9581ac0e0e58b3eaf[root@master1 ~]#
- 这里和上面一样的方法,我们另外打开一个窗口用
--watch
来监视下pod的变化状态:
[root@master1 ~]#kubectl get po --watchNAMEREADYSTATUSRESTARTSAGEnginx-deploy-6c5ff87cf-4f2291/1Running04m17snginx-deploy-6c5ff87cf-p866j1/1Running04m17snginx-deploy-6c5ff87cf-ttm2v1/1Running04m17snginx-deploy-6c5ff87cf-w2csq1/1Running04m17s----testRoollingupdate-----
- 现在我们来直接更新上面的 Deployment 资源对象:
[root@master1 ~]#kubectl apply -f nginx-deploy.yamldeployment.apps/nginx-deployconfigured
record 参数:我们可以添加了一个额外的 --record
参数来记录下我们的每次操作所执行的命令,以方便后面查看。
说明:这里只有通过命令行来更新镜像的话,这里是才会把当前执行的命令给记录下来;但是如果通过.yaml文件来更新的话,这里依然不会有任何记录的,会出现none;
其实:这种方法也是不好用的;
[root@k8s-master ~]#kubectl set image deployment web777 nginx=nginx:1.20--record-ntest
回滚
#回滚(项目升级失败恢复到正常版本)kubectlrollouthistorydeployment/web# 查看历史发布版本kubectlrolloutundodeployment/web# 回滚上一个版本kubectlrolloutundodeployment/web--to-revision=2# 回滚历史指定版本#注:回滚是重新部署某一次部署时的状态,即当时版本所有配置说明:k8s原生的"回滚"功能非常鸡肋,不能很明显地看到上一个版本具体是什么样的;一般大厂都是自己开发这个"版本控制"功能模块的;我们可以采用如下2种方式来改进:1.写一个shell脚本(可以关联出回滚序号和其相关版本)2.写平台的话,可以在mysql数据量里查询;
- 更新后,我们可以执行下面的
kubectl rollout status
命令来查看我们此次滚动更新的状态:
我们先来看下rollout命令的可以参数:
[root@master1 ~]#kubectl rollout --helpManagetherolloutofaresource.Validresourcetypesinclude:*deployments*daemonsets*statefulsetsExamples:# Rollback to the previous deploymentkubectlrolloutundodeployment/abc# Check the rollout status of a daemonsetkubectlrolloutstatusdaemonset/fooAvailableCommands:historyViewrollouthistorypause(暂停) Mark the provided resource as pausedrestartRestartaresourceresume(恢复) Resume a paused resourcestatusShowthestatusoftherolloutundo(撤销,使恢复原状) Undo a previous rolloutUsage:kubectlrolloutSUBCOMMAND[options]Use"kubectl <command>--help"formoreinformationaboutagivencommand.Use"kubectl options"foralistofglobalcommand-lineoptions(applies toallcommands).[root@master1 ~]#
- 从上面的信息可以看出我们的滚动更新已经有两个 Pod 已经更新完成了,在滚动更新过程中,我们还可以执行如下的命令来暂停更新:
[root@master1 ~]#kubectl rollout pause deployment/nginx-deploydeployment.apps/nginx-deploypaused
这个时候我们的滚动更新就暂停了,此时我们可以查看下 Deployment 的详细信息:
[root@master1 ~]#kubectl describe deployments.apps nginx-deployName:nginx-deployNamespace:defaultCreationTimestamp:Sat,13Nov202110:41:09+0800Labels:role=deployAnnotations:deployment.kubernetes.io/revision:3Selector:app=nginx,test=courseReplicas:4desired|2updated|5total|5available|0unavailableStrategyType:RollingUpdateMinReadySeconds:5RollingUpdateStrategy:1maxunavailable,1maxsurgePodTemplate:Labels:app=nginxtest=courseContainers:nginx:Image:nginx:latestPort:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>Conditions:TypeStatusReason----------------AvailableTrueMinimumReplicasAvailableProgressingUnknownDeploymentPausedOldReplicaSets:nginx-deploy-6c5ff87cf(3/3 replicascreated)NewReplicaSet:nginx-deploy-595b8954f7(2/2 replicascreated)Events:TypeReasonAgeFromMessage-------------------------NormalScalingReplicaSet15mdeployment-controllerScaledupreplicasetnginx-deploy-fd46765d4to3NormalScalingReplicaSet9m8sdeployment-controllerScaledupreplicasetnginx-deploy-fd46765d4to4NormalScalingReplicaSet5m43sdeployment-controllerScaleddownreplicasetnginx-deploy-fd46765d4to0NormalScalingReplicaSet5m42sdeployment-controllerScaledupreplicasetnginx-deploy-6c5ff87cfto4NormalScalingReplicaSet41sdeployment-controllerScaledupreplicasetnginx-deploy-595b8954f7to1NormalScalingReplicaSet41sdeployment-controllerScaleddownreplicasetnginx-deploy-6c5ff87cfto3NormalScalingReplicaSet41sdeployment-controllerScaledupreplicasetnginx-deploy-595b8954f7to2
我们仔细观察 Events 事件区域的变化,上面我们用 kubectl scale
命令将 Pod 副本调整到了 4,现在我们更新的时候是不是声明又变成 3 了,所以 Deployment 控制器首先是将之前控制的 nginx-deploy-85ff79dd56
这个 RS 资源对象进行缩容操作,然后滚动更新开始了,可以发现 Deployment 为一个新的 nginx-deploy-5b7b9ccb95
RS 资源对象首先新建了一个新的 Pod,然后将之前的 RS 对象缩容到 2 了,再然后新的 RS 对象扩容到 2,后面由于我们暂停滚动升级了,所以没有后续的事件了,大家有看明白这个过程吧?这个过程就是滚动更新的过程,启动一个新的 Pod,杀掉一个旧的 Pod,然后再启动一个新的 Pod,这样滚动更新下去,直到全都变成新的 Pod,这个时候系统中应该存在 4 个 Pod,因为我们设置的策略maxSurge=1
,所以在升级过程中是允许的,而且是两个新的 Pod,两个旧的 Pod:
[root@master1 ~]#kubectl get po -l app=nginxNAMEREADYSTATUSRESTARTSAGEnginx-deploy-595b8954f7-sp5nj1/1Running086snginx-deploy-595b8954f7-z6pn41/1Running086snginx-deploy-6c5ff87cf-p866j1/1Running06m27snginx-deploy-6c5ff87cf-ttm2v1/1Running06m27snginx-deploy-6c5ff87cf-w2csq1/1Running06m27s
查看 Deployment 的状态也可以看到当前的 Pod 状态:
[root@master1 ~]#kubectl get deployments.appsNAMEREADYUP-TO-DATEAVAILABLEAGEnginx-deploy5/42516m
- 这个时候我们可以使用
kubectl rollout resume
来恢复我们的滚动更新:
[root@master1 ~]#kubectl rollout resume deployment nginx-deploydeployment.apps/nginx-deployresumed[root@master1 ~]#[root@master1 ~]#kubectl rollout status deployment nginx-deploydeployment"nginx-deploy"successfullyrolledout[root@master1 ~]#
看到上面的信息证明我们的滚动更新已经成功了,同样可以查看下资源状态:
[root@master1 ~]#kubectl get po -l app=nginxNAMEREADYSTATUSRESTARTSAGEnginx-deploy-595b8954f7-p2qht1/1Running068snginx-deploy-595b8954f7-qw6gz1/1Running068snginx-deploy-595b8954f7-sp5nj1/1Running03m25snginx-deploy-595b8954f7-z6pn41/1Running03m25s[root@master1 ~]#kubectl get deployments.appsNAMEREADYUP-TO-DATEAVAILABLEAGEnginx-deploy4/44418m[root@master1 ~]#
这个时候我们查看 ReplicaSet 对象,可以发现会出现3个:
[root@master1 ~]#kubectl get rsNAMEDESIREDCURRENTREADYAGEnginx-deploy-595b8954f74443m43snginx-deploy-6c5ff87cf0008m44snginx-deploy-fd46765d400018m
从上面可以看出滚动更新之前我们使用的 RS 资源对象的 Pod 副本数已经变成 0 了,而滚动更新后的 RS 资源对象变成了 4 个副本,我们可以导出之前的 RS 对象查看:
[root@master1 ~]#kubectl get rs nginx-deploy-6c5ff87cf -oyamlapiVersion:apps/v1kind:ReplicaSetmetadata:annotations:deployment.kubernetes.io/desired-replicas:"4"deployment.kubernetes.io/max-replicas:"5"deployment.kubernetes.io/revision:"2"creationTimestamp:"2021-11-13T02:51:05Z"generation:4labels:app:nginxpod-template-hash:6c5ff87cftest:coursename:nginx-deploy-6c5ff87cfnamespace:defaultownerReferences:-apiVersion:apps/v1blockOwnerDeletion:truecontroller:truekind:Deploymentname:nginx-deployuid:ac7c0147-2ed9-4e61-91fa-b4bfdf185564resourceVersion:"319487"uid:d3455813-e6eb-480d-b88b-d4761d16c131spec:replicas:0selector:matchLabels:app:nginxpod-template-hash:6c5ff87cftest:coursetemplate:metadata:creationTimestamp:nulllabels:app:nginxpod-template-hash:6c5ff87cftest:coursespec:containers:-image:nginx:1.7.9imagePullPolicy:Alwaysname:nginxports:-containerPort:80protocol:TCPresources:{}terminationMessagePath:/dev/termination-logterminationMessagePolicy:FilednsPolicy:ClusterFirstrestartPolicy:AlwaysschedulerName:default-schedulersecurityContext:{}terminationGracePeriodSeconds:30status:observedGeneration:4replicas:0#replicas为0
我们仔细观察这个资源对象里面的描述信息除了副本数变成了 replicas=0
之外,和更新之前没有什么区别吧?大家看到这里想到了什么?有了这个 RS 的记录存在,是不是我们就可以回滚了啊?而且还可以回滚到前面的任意一个版本。
- 这个版本是如何定义的呢?我们可以通过命令
rollout history
来获取:
[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>2<none>3<none>[root@master1 ~]#
其实**rollout history**
中记录的**revision**
是和**ReplicaSets**
**一一对应。**如果我们手动删除某个 ReplicaSet
,对应的rollout history
就会被删除,也就是说你无法回滚到这个revison
了,同样我们还可以查看一个revison
的详细信息:
[root@master1 ~]#kubectl rollout history deployment nginx-deploy --revision=2deployment.apps/nginx-deploywithrevision#2PodTemplate:Labels:app=nginxpod-template-hash=6c5ff87cftest=courseContainers:nginx:Image:nginx:1.7.9Port:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>[root@master1 ~]#
- 我们先来看下当前的revision是几呢:
[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISIONCHANGE-CAUSE1<none>2<none>3<none>[root@master1 ~]#kubectl get poNAMEREADYSTATUSRESTARTSAGEnginx-deploy-595b8954f7-p2qht1/1Running05m51snginx-deploy-595b8954f7-qw6gz1/1Running05m51snginx-deploy-595b8954f7-sp5nj1/1Running08m8snginx-deploy-595b8954f7-z6pn41/1Running08m8s[root@master1 ~]#kubectl describe rs nginx-deploy-595b8954f7 |greprevisiondeployment.kubernetes.io/revision:3#可以看到强的revision是3[root@master1 ~]#
- 假如现在要直接回退到当前版本的前一个版本,我们可以直接使用如下命令进行操作:
➜~kubectlrolloutundodeploymentnginx-deploy
- 当然也可以回退到指定的
revision
版本:
➜~kubectlrolloutundodeploymentnginx-deploy--to-revision=1deployment"nginx-deploy"rolledback
- 本次假设我们回退到
1
版本:
[root@master1 ~]#kubectl rollout undo deployment nginx-deploy --to-revision=1deployment.apps/nginx-deployrolledback
- 回滚的过程中我们同样可以查看回滚状态:
[root@master1 ~]#kubectl rollout status deployment nginx-deployWaitingfordeployment"nginx-deploy"rollouttofinish:2outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:2outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:2outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:2outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:3outof4newreplicashavebeenupdated...Waitingfordeployment"nginx-deploy"rollouttofinish:1oldreplicasarependingtermination...Waitingfordeployment"nginx-deploy"rollouttofinish:3of4updatedreplicasareavailable...Waitingfordeployment"nginx-deploy"rollouttofinish:3of4updatedreplicasareavailable...deployment"nginx-deploy"successfullyrolledout[root@master1 ~]#
- 这个时候查看对应的 RS 资源对象可以看到 Pod 副本已经回到之前的 RS 里面去了。
[root@master1 ~]#kubectl get rsNAME DESIRED CURRENT READY AGEnginx-deploy-595b8954f7 0 0 0 18mnginx-deploy-6c5ff87cf 0 0 0 23mnginx-deploy-fd46765d4 4 4 4 33m[root@master1 ~]#
- 不过需要注意的是回滚的操作滚动的
revision
始终是递增的:
[root@master1 ~]#kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deployREVISION CHANGE-CAUSE2 <none>3 <none>4 <none>[root@master1 ~]#
保留旧版本
在很早之前的 Kubernetes 版本中,默认情况下会为我们暴露下所有滚动升级的历史记录,也就是 ReplicaSet 对象,但一般情况下没必要保留所有的版本,毕竟会存在 etcd 中,我们可以通过配置 spec.revisionHistoryLimit
属性来设置保留的历史记录数量,不过新版本中该值默认为 10,如果希望多保存几个版本可以设置该字段。
- 我们来查看下watch监控的内容
[root@master1 ~]#kubectl get po --watchNAMEREADYSTATUSRESTARTSAGEnginx-deploy-6c5ff87cf-4f2291/1Running04m17snginx-deploy-6c5ff87cf-p866j1/1Running04m17snginx-deploy-6c5ff87cf-ttm2v1/1Running04m17snginx-deploy-6c5ff87cf-w2csq1/1Running04m17s----testRoollingupdate-----nginx-deploy-595b8954f7-z6pn40/1Pending00snginx-deploy-595b8954f7-z6pn40/1Pending00snginx-deploy-6c5ff87cf-4f2291/1Terminating05m1snginx-deploy-595b8954f7-z6pn40/1ContainerCreating00snginx-deploy-595b8954f7-sp5nj0/1Pending00snginx-deploy-595b8954f7-sp5nj0/1Pending00snginx-deploy-595b8954f7-sp5nj0/1ContainerCreating00snginx-deploy-6c5ff87cf-4f2290/1Terminating05m2snginx-deploy-6c5ff87cf-4f2290/1Terminating05m2snginx-deploy-6c5ff87cf-4f2290/1Terminating05m2snginx-deploy-595b8954f7-sp5nj1/1Running02snginx-deploy-595b8954f7-z6pn41/1Running017snginx-deploy-6c5ff87cf-ttm2v1/1Terminating07m18snginx-deploy-6c5ff87cf-w2csq1/1Terminating07m18snginx-deploy-595b8954f7-qw6gz0/1Pending00snginx-deploy-595b8954f7-qw6gz0/1Pending00snginx-deploy-595b8954f7-p2qht0/1Pending00snginx-deploy-595b8954f7-p2qht0/1Pending00snginx-deploy-595b8954f7-qw6gz0/1ContainerCreating00snginx-deploy-595b8954f7-p2qht0/1ContainerCreating00snginx-deploy-6c5ff87cf-ttm2v0/1Terminating07m19snginx-deploy-6c5ff87cf-ttm2v0/1Terminating07m19snginx-deploy-6c5ff87cf-ttm2v0/1Terminating07m19snginx-deploy-6c5ff87cf-w2csq0/1Terminating07m19snginx-deploy-6c5ff87cf-w2csq0/1Terminating07m19snginx-deploy-6c5ff87cf-w2csq0/1Terminating07m19snginx-deploy-595b8954f7-p2qht1/1Running016snginx-deploy-595b8954f7-qw6gz1/1Running017snginx-deploy-6c5ff87cf-p866j1/1Terminating07m40snginx-deploy-6c5ff87cf-p866j0/1Terminating07m40snginx-deploy-6c5ff87cf-p866j0/1Terminating07m40snginx-deploy-6c5ff87cf-p866j0/1Terminating07m40s
- 我们用命令
kubectl describle deploy nginx
反而更清晰可以看到滚动升级的过程:
[root@master1 ~]#kubectl describe deployments.apps nginx-deployName:nginx-deployNamespace:defaultCreationTimestamp:Thu,11Nov202122:04:31+0800Labels:role=deployAnnotations:deployment.kubernetes.io/revision:3Selector:app=nginx,test=courseReplicas:3desired|3updated|3total|3available|0unavailableStrategyType:RollingUpdateMinReadySeconds:5RollingUpdateStrategy:1maxunavailable,1maxsurgePodTemplate:Labels:app=nginxtest=courseContainers:nginx:Image:nginx:latestPort:80/TCPHostPort:0/TCPEnvironment:<none>Mounts:<none>Volumes:<none>Conditions:TypeStatusReason----------------AvailableTrueMinimumReplicasAvailableProgressingTrueNewReplicaSetAvailableOldReplicaSets:<none>NewReplicaSet:nginx-deploy-595b8954f7(3/3 replicascreated)Events:TypeReasonAgeFromMessage-------------------------NormalScalingReplicaSet23mdeployment-controllerScaleddownreplicasetnginx-deploy-6c5ff87cfto3NormalScalingReplicaSet23mdeployment-controllerScaledupreplicasetnginx-deploy-595b8954f7to1NormalScalingReplicaSet23mdeployment-controllerScaleddownreplicasetnginx-deploy-6c5ff87cfto2NormalScalingReplicaSet23mdeployment-controllerScaledupreplicasetnginx-deploy-595b8954f7to2NormalScalingReplicaSet22mdeployment-controllerScaleddownreplicasetnginx-deploy-6c5ff87cfto0NormalScalingReplicaSet22mdeployment-controllerScaledupreplicasetnginx-deploy-595b8954f7to3#最终的目的:就是deployment-controller将old rs的副本数设置为0,将new rs的副本数设置为desired数量[root@master1 ~]#[root@master1 ~]#kubectl get rsNAMEDESIREDCURRENTREADYAGEnginx-deploy-595b8954f733325mnginx-deploy-6c5ff87cf0009hnginx-deploy-fd46765d40009h[root@master1 ~]#
测试结束。😘
滚动升级的注意事项
如果我们强制做一个滚动更新的话,我们的应用如果现在还对外提供服务,那么就有可能正在接收流量,那如果我们做滚动升级的话,那就有可能也会造成业务的请求中断。
那么我们可以用什么样的方式来解决这个"中断"问题呢?
- 就是我们前面所说的
preStaop
钩子,就是我们在停止之前,可以做一个什么样的事情呢?比如nginx,可以做一个优雅退出。让它把我们现在的请求处理完成之后,就是不接受请求了,再停止pod。所以,对于我们线上的应用,基本上会加上这里的优雅退出
,在我们的preStop
做这样一个事情。 - 你直接在这个
preStop
里面直接sleep一下,就是让我们这里的请求/连接有足够的时间处理完成。所以,我用个preStop里面直接sleep也是可以的。
- 应用升级(更新镜像三种方式,自动触发滚动升级)
1、kubectlapply-fxxx.yaml(推荐使用)2、kubectlsetimagedeployment/webnginx=nginx:1.173、kubectleditdeployment/web#使用系统编辑器打开
案例:零宕机
本小节内容具体看如下链接:
https:# ...lifecycle:preStop:exec:command:["/bin/bash","-c","sleep 20"]
我们这里使用 preStop
设置了一个 20s 的宽限期,Pod 在真正销毁前会先 sleep 等待 20s,这就相当于留了时间给 Endpoints 控制器和 kube-proxy 更新去 Endpoints 对象和转发规则,这段时间 Pod 虽然处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。
现在,当我们去查看滚动更新期间的 Pod 行为时,我们将看到正在终止的 Pod 处于 Terminating
状态,但是在等待时间结束之前不会关闭的,如果我们使用 Fortio
重新测试下,则会看到零失败请求的理想状态。
关于我
我的博客主旨:
- 排版美观,语言精炼;
- 文档即手册,步骤明细,拒绝埋坑,提供源码;
- 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!
🍀 微信二维码
x2675263825 (舍得), qq:2675263825。
🍀 微信公众号
《云原生架构师实战》
🍀 语雀