27 滚动更新:如何做到平滑的应用升级降级? 你好,我是Chrono。
上次课里我们学习了管理有状态应用的对象StatefulSet,再加上管理无状态应用的Deployment和DaemonSet,我们就能在Kubernetes里部署任意形式的应用了。
不过,只是把应用发布到集群里是远远不够的,要让应用稳定可靠地运行,还需要有持续的运维工作。
如果你还记得在[第18节课]里,我们学过Deployment的“应用伸缩”功能就是一种常见的运维操作,在Kubernetes里,使用命令
kubectl scale ,我们就可以轻松调整Deployment下属的Pod数量,因为StatefulSet是Deployment的一种特例,所以它也可以使用
kubectl scale 来实现“应用伸缩”。
除了“应用伸缩”,其他的运维操作比如应用更新、版本回退等工作,该怎么做呢?这些也是我们日常运维中经常会遇到的问题。
今天我就以Deployment为例,来讲讲Kubernetes在应用管理方面的高级操作:滚动更新,使用
kubectl rollout 实现用户无感知的应用升级和降级。
Kubernetes如何定义应用版本
应用的版本更新,大家都知道是怎么回事,比如我们发布了V1版,过了几天加了新功能,要发布V2版。
不过说起来简单,版本更新实际做起来是一个相当棘手的事。因为系统已经上线运行,必须要保证不间断地对外提供服务,通俗地说就是“给空中的飞机换引擎”。尤其在以前,需要开发、测试、运维、监控、网络等各个部门的一大堆人来协同工作,费时又费力。
但是,应用的版本更新其实是有章可循的,现在我们有了Kubernetes这个强大的自动化运维管理系统,就可以把它的过程抽象出来,让计算机去完成那些复杂繁琐的人工操作。
在Kubernetes里,版本更新使用的不是API对象,而是两个命令:
kubectl apply 和
kubectl rollout ,当然它们也要搭配部署应用所需要的Deployment、DaemonSet等YAML文件。
不过在我们信心满满开始操作之前,首先要理解在Kubernetes里,所谓的“版本”到底是什么?
我们常常会简单地认为“版本”就是应用程序的“版本号”,或者是容器镜像的“标签”,但不要忘了,在Kubernetes里应用都是以Pod的形式运行的,而Pod通常又会被Deployment等对象来管理,所以应用的“版本更新”实际上更新的是整个Pod。
那Pod又是由什么来决定的呢?
仔细回忆一下之前我们创建的那么多个对象,你就会发现,Pod是由YAML描述文件来确定的,更准确地说,是Deployment等对象里的字段
template 。
所以,**在Kubernetes里应用的版本变化就是
template 里Pod的变化**,哪怕
template 里只变动了一个字段,那也会形成一个新的版本,也算是版本变化。
但
template 里的内容太多了,拿这么长的字符串来当做“版本号”不太现实,所以Kubernetes就使用了“摘要”功能,用摘要算法计算
template 的Hash值作为“版本号”,虽然不太方便识别,但是很实用。
我们就拿[第18讲]里的Nginx Deployment作为例子吧,创建对象之后,使用
kubectl get 来查看Pod的状态:
Pod名字里的那串随机数“6796……”就是Pod模板的Hash值,也就是Pod的“版本号”。
如果你变动了Pod YAML描述,比如把镜像改成
nginx:stable-alpine ,或者把容器名字改成
nginx-test ,都会生成一个新的应用版本,
kubectl apply 后就会重新创建Pod:
你可以看到,Pod名字里的Hash值变成了“7c6c……”,这就表示Pod的版本更新了。
Kubernetes如何实现应用更新
为了更仔细地研究Kubernetes的应用更新过程,让我们来略微改造一下Nginx Deployment对象,看看Kubernetes到底是怎么实现版本更新的。
首先修改ConfigMap,让它输出Nginx的版本号,方便我们用curl查看版本: apiVersion: v1 kind: ConfigMap metadata: name: ngx-conf data: default.conf: | server { listen 80; location / { default_type text/plain; return 200 ‘ver : $nginx_version\nsrv : $server_addr:$server_port\nhost: $hostname\n’; } }
然后我们修改Pod镜像,明确地指定版本号是
1.21-alpine ,实例数设置为4个:
apiVersion: apps/v1 kind: Deployment metadata: name: ngx-dep spec: replicas: 4 … … containers: - image: nginx:1.21-alpine … …
把它命名为
ngx-v1.yml ,然后执行命令
kubectl apply 部署这个应用:
kubectl apply -f ngx-v1.yml
我们还可以为它创建Service对象,再用
kubectl port-forward 转发请求来查看状态:
kubectl port-forward svc/ngx-svc 8080:80 & curl 127.1:8080
从curl命令的输出中可以看到,现在应用的版本是
1.21.6 。
现在,让我们编写一个新版本对象
ngx-v2.yml ,把镜像升级到
nginx:1.22-alpine ,其他的都不变。
**因为Kubernetes的动作太快了,为了能够观察到应用更新的过程,我们还需要添加一个字段
minReadySeconds **,让Kubernetes在更新过程中等待一点时间,确认Pod没问题才继续其余Pod的创建工作。
要提醒你注意的是,
minReadySeconds 这个字段不属于Pod模板,所以它不会影响Pod版本: apiVersion: apps/v1 kind: Deployment metadata: name: ngx-dep spec: minReadySeconds: 15 /# 确认Pod就绪的等待时间 replicas: 4 … … containers: - image: nginx:1.22-alpine … …
现在我们执行命令
kubectl apply 来更新应用,因为改动了镜像名,Pod模板变了,就会触发“版本更新”,然后用一个新命令:
kubectl rollout status ,来查看应用更新的状态:
kubectl apply -f ngx-v2.yml kubectl rollout status deployment ngx-dep
更新完成后,你再执行
kubectl get pod ,就会看到Pod已经全部替换成了新版本“d575……”,用curl访问Nginx,输出信息也变成了“1.22.0”:
仔细查看
kubectl rollout status 的输出信息,你可以发现,Kubernetes不是把旧Pod全部销毁再一次性创建出新Pod,而是在逐个地创建新Pod,同时也在销毁旧Pod,保证系统里始终有足够数量的Pod在运行,不会有“空窗期”中断服务。
新Pod数量增加的过程有点像是“滚雪球”,从零开始,越滚越大,所以这就是所谓的“滚动更新”(rolling update)。
使用命令
kubectl describe 可以更清楚地看到Pod的变化情况: kubectl describe deploy ngx-dep
- 一开始的时候V1 Pod(即ngx-dep-54b865d75)的数量是4;
- 当“滚动更新”开始的时候,Kubernetes创建1个 V2 Pod(即ngx-dep-d575d5776),并且把V1 Pod数量减少到3;
- 接着再增加V2 Pod的数量到2,同时V1 Pod的数量变成了1;
- 最后V2 Pod的数量达到预期值4,V1 Pod的数量变成了0,整个更新过程就结束了。
看到这里你是不是有点明白了呢,其实“滚动更新”就是由Deployment控制的两个同步进行的“应用伸缩”操作,老版本缩容到0,同时新版本扩容到指定值,是一个“此消彼长”的过程。
这个滚动更新的过程我画了一张图,你可以参考它来进一步体会:
Kubernetes如何管理应用更新
Kubernetes的“滚动更新”功能确实非常方便,不需要任何人工干预就能简单地把应用升级到新版本,也不会中断服务,不过如果更新过程中发生了错误或者更新后发现有Bug该怎么办呢?
要解决这两个问题,我们还是要用
kubectl rollout 命令。
在应用更新的过程中,你可以随时使用
kubectl rollout pause 来暂停更新,检查、修改Pod,或者测试验证,如果确认没问题,再用
kubectl rollout resume 来继续更新。
这两个命令比较简单,我就不多做介绍了,要注意的是它们只支持Deployment,不能用在DaemonSet、StatefulSet上(最新的1.24支持了StatefulSet的滚动更新)。
对于更新后出现的问题,Kubernetes为我们提供了“后悔药”,也就是更新历史,你可以查看之前的每次更新记录,并且回退到任何位置,和我们开发常用的Git等版本控制软件非常类似。
查看更新历史使用的命令是
kubectl rollout history : kubectl rollout history deploy ngx-dep
它会输出一个版本列表,因为我们创建Nginx Deployment是一个版本,更新又是一个版本,所以这里就会有两条历史记录。
但
kubectl rollout history 的列表输出的有用信息太少,你可以**在命令后加上参数
–revision 来查看每个版本的详细信息**,包括标签、镜像名、环境变量、存储卷等等,通过这些就可以大致了解每次都变动了哪些关键字段: kubectl rollout history deploy –revision=2
假设我们认为刚刚更新的
nginx:1.22-alpine 不好,**想要回退到上一个版本,就可以使用命令
kubectl rollout undo ,也可以加上参数
–to-revision 回退到任意一个历史版本**: kubectl rollout undo deploy ngx-dep
kubectl rollout undo 的操作过程其实和
kubectl apply 是一样的,执行的仍然是“滚动更新”,只不过使用的是旧版本Pod模板,把新版本Pod数量收缩到0,同时把老版本Pod扩展到指定值。
这个V2到V1的“版本降级”的过程我同样画了一张图,它和从V1到V2的“版本升级”过程是完全一样的,不同的只是版本号的变化方向:
Kubernetes如何添加更新描述
讲到这里,Kubernetes里应用更新的功能就学得差不多了。
不过,你有没有觉得
kubectl rollout history 的版本列表好像有点太简单了呢?只有一个版本更新序号,而另一列
CHANGE-CAUSE 为什么总是显示成
更多学习
更多实时资讯,前沿技术,生活趣事。尽在【老马啸西风】
交流社群:[交流群信息](https://mp.weixin.qq.com/s/rkSvXxiiLGjl3S-ZOZCr0Q)