【k8s】Deployment滚动更新(十六)

  • Post author:
  • Post category:其他





前言

上篇笔记我们学习了管理有状态应用的对象 StatefulSet,再加上管理无状态应用的 Deployment 和 DaemonSet,我们就能在 Kubernetes 里部署任意形式的应用了。

只是把应用发布到集群里是远远不够的,要让应用稳定可靠地运行,还需要有持续的运维工作。



【k8s】Deployment让应用永不宕机(八)

里,我们学过 Deployment 的

应用伸缩

功能就是一种常见的运维操作,在 Kubernetes 里,使用命令

kubectl scale

,我们就可以轻松调整 Deployment 下属的 Pod 数量,因为 StatefulSet 是 Deployment 的一种特例,所以它也可以使用 kubectl scale 来实现

应用伸缩

除了

应用伸缩

,其他的运维操作比如应用更新、版本回退等工作,该怎么做呢?这些也是我们日常运维中经常会遇到的问题。

今天就以 Deployment 为例,来讲讲 Kubernetes 在应用管理方面的高级操作:

滚动更新

,使用 kubectl rollout 实现用户无感知的应用升级和降级。



一、Kubernetes 应用版本更新

应用的版本更新,大家都知道是怎么回事,比如我们发布了

V1

版,过了几天加了新功能,要发布

V2

版。

在 Kubernetes 里,版本更新使用的不是 API 对象,而是两个命令:

kubectl apply



kubectl rollout

,当然它们也要搭配部署应用所需要的 Deployment、DaemonSet 等 YAML 文件。

Kubernetes 里的应用都是以 Pod 的形式运行的,而 Pod 通常又会被 Deployment 等对象来管理,所以应用的

版本更新

实际上更新的是整个 Pod。

Pod 是由 YAML 描述文件来确定的,更准确地说,是 Deployment 等对象里的字段 template。

所以,在 Kubernetes 里应用的版本变化就是 template 里 Pod 的变化,哪怕 template 里只变动了一个字段,那也会形成一个新的版本,也算是版本变化。

Kubernetes 使用了

摘要

功能,用

摘要

算法计算 template 的 Hash 值作为

版本号

,虽然不太方便识别,但是很实用。

来看一个例子:

在这里插入图片描述

Pod 名字里的那串随机数

676475ddfd

就是 Pod 模板的 Hash 值,也就是 Pod 的

版本号

如果变动了 Pod YAML 描述,比如把镜像改成

nginx:stable-alpine

,或者把容器名字改成

nginx-tes

t,都会生成一个新的应用版本,kubectl apply 后就会重新创建 Pod

在这里插入图片描述

你可以看到,Pod 名字里的 Hash 值变成了

56b6b4f4c9

,这就表示 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:
  labels:
    app: ngx-dep
  name: ngx-dep

spec:
  replicas: 4
  selector:
    matchLabels:
      app: ngx-dep
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:1.21-alpine
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/
          name: ngx-conf-vol

把它命名为 ngx-v1.yml,然后执行命令 kubectl apply 部署这个应用:

kubectl apply -f ngx-v1.yml

我们还可以为它创建 Service 对象,利用NodePort类型进行转发:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: ngx-dep
  name: ngx-svc
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: ngx-dep

在这里插入图片描述

curl 127.1:30657

在这里插入图片描述

从 curl 命令的输出中可以看到,现在应用的版本是

1.21.6

。现在,让我们编写一个新版本对象

ngx-v2.yml

,把镜像升级到

nginx:1.22-alpine

,其他的都不变。

因为

Kubernetes 的动作太快了

,为了能够观察到应用更新的过程,我们还需要添加一个字段

minReadySeconds

,让 Kubernetes 在更新过程中等待一点时间,确认 Pod 没问题才继续其余 Pod 的创建工作。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep
  annotations:
    kubernetes.io/change-cause: update to v2, ngx=1.22
spec:
  minReadySeconds: 15 # 确认Pod就绪的等待时间
  replicas: 4
  selector:
    matchLabels:
      app: ngx-dep
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:1.22-alpine
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/
          name: ngx-conf-vol

现在我们执行命令

kubectl apply

来更新应用,因为改动了镜像名,Pod 模板变了,就会触发

版本更新

,然后用一个新命令:

kubectl rollout status

,来查看应用更新的状态:

kubectl apply -f ngx-v2.yml
kubectl rollout status deployment ngx-dep

在这里插入图片描述

更新完成后,你再执行 kubectl get pod,就会看到 Pod 已经全部替换成了新版本“7cf8……”,用 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-676475ddfd)的数量是 4;
  • 当“滚动更新”开始的时候,Kubernetes 创建 1 个 V2 Pod(即 ngx-dep-7cf8546bf9),并且把 V1 Pod 数量减少到 3;
  • 接着再增加 V2 Pod 的数量到 2,同时 V1 Pod 的数量变成了 2 ;
  • 最后 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
[root@k8s-console nginx]# kubectl rollout history deploy ngx-dep
deployment.apps/ngx-dep 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

它会输出一个版本列表,因为我们创建 Nginx Deployment 是一个版本,更新又是一个版本,所以这里就会有两条历史记录。

但 kubectl rollout history 的列表输出的有用信息太少,可以在命令后加上参数 –revision 来查看每个版本的详细信息,包括标签、镜像名、环境变量、存储卷等等,通过这些就可以大致了解每次都变动了哪些关键字段:

kubectl rollout history deploy --revision=2
[root@k8s-console nginx]# kubectl rollout history deploy --revision=2
deployment.apps/ngx-dep with revision #2
Pod Template:
  Labels:	app=ngx-dep
	pod-template-hash=7cf8546bf9
  Containers:
   nginx:
    Image:	nginx:1.22-alpine
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:
      /etc/nginx/conf.d/ from ngx-conf-vol (rw)
  Volumes:
   ngx-conf-vol:
    Type:	ConfigMap (a volume populated by a ConfigMap)
    Name:	ngx-conf
    Optional:	false

假设我们认为刚刚更新的 nginx:1.22-alpine 不好,想要回退到上一个版本,就可以使用命令

kubectl rollout undo

,也可以加上参数

--to-revision

回退到任意一个历史版本:

[root@k8s-console nginx]# kubectl rollout history deploy ngx-dep
deployment.apps/ngx-dep 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>

kubectl rollout undo 的操作过程其实和 kubectl apply 是一样的,执行的仍然是

滚动更新

,只不过使用的是旧版本 Pod 模板,把新版本 Pod 数量收缩到 0,同时把老版本 Pod 扩展到指定值。

这个 V2 到 V1 的

版本降级

的过程同样画了一张图,它和从 V1 到 V2 的

版本升级

过程是完全一样的,不同的只是版本号的变化方向:

在这里插入图片描述



四、Kubernetes 添加更新描述

kubectl rollout history 的版本列表好像有点太简单了呢?只有一个版本更新序号,而另一列 CHANGE-CAUSE 为什么总是显示成 呢?能不能像 Git 一样,每次更新也加上说明信息呢?

这当然是可以的,做法也很简单,我们只需要在

Deployment 的 metadata 里加上一个新的字段

annotations


annotations 字段的含义是

注解``注释

,形式上和 labels 一样,都是 Key-Value,也都是给 API 对象附加一些额外的信息,但是用途上区别很大。

  • annotations 添加的信息一般是给 Kubernetes 内部的各种对象使用的,有点像是

    扩展属性

  • labels 主要面对的是 Kubernetes 外部的用户,用来筛选、过滤对象的。

如果用一个简单的比喻来说呢,annotations 就是包装盒里的产品说明书,而 labels 是包装盒外的标签贴纸。

借助

annotations

,Kubernetes 既不破坏对象的结构,也不用新增字段,就能够给 API 对象添加任意的附加信息,这就是面向对象设计中典型的 OCP

开闭原则

,让对象更具扩展性和灵活性。

annotations 里的值可以任意写,Kubernetes 会自动忽略不理解的 Key-Value,但要

编写更新说明

就需要使用特定的字段

kubernetes.io/change-cause

下面来操作一下,我们创建 3 个版本的 Nginx 应用,同时添加更新说明:


ngx-v1

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep
  annotations:
    kubernetes.io/change-cause: v1, nginx=1.21
spec:
  replicas: 4
  selector:
    matchLabels:
      app: ngx-dep
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:1.21-alpine
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/
          name: ngx-conf-vol


ngx-v2

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep
  annotations:
    kubernetes.io/change-cause: update to v2, ngx=1.22
spec:
  minReadySeconds: 15
  replicas: 4
  selector:
    matchLabels:
      app: ngx-dep
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:1.22-alpine
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/
          name: ngx-conf-vol


ngx-v3

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep
  annotations:
    kubernetes.io/change-cause: update to v3, change name
spec:
  minReadySeconds: 15
  replicas: 4
  selector:
    matchLabels:
      app: ngx-dep
  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf
      containers:
      - image: nginx:1.22-alpine
        name: nginx-3
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d/
          name: ngx-conf-vol

你需要注意 YAML 里的

metadata

部分,使用

annotations.kubernetes.io/change-cause

描述了版本更新的情况,相比

kubectl rollout history --revision

的罗列大量信息更容易理解。

依次使用 kubectl apply 创建并更新对象之后,我们再用 kubectl rollout history 来看一下更新历史:

kubectl rollout history deployment ngx-dep

在这里插入图片描述

这次显示的列表信息就好看多了,每个版本的主要变动情况列得非常清楚,和 Git 版本管理的感觉很像。



总结

  1. 在 Kubernetes 里应用的版本不仅仅是容器镜像,而是整个 Pod 模板,为了便于处理使用了摘要算法,计算模板的 Hash 值作为版本号。
  2. Kubernetes 更新应用采用的是滚动更新策略,减少旧版本 Pod 的同时增加新版本 Pod,保证在更新过程中服务始终可用。
  3. 管理应用更新使用的命令是 kubectl rollout,子命令有 status、history、undo 等。
  4. Kubernetes 会记录应用的更新历史,可以使用 history –revision 查看每个版本的详细信息,也可以在每次更新时添加注解 kubernetes.io/change-cause。

另外,在 Deployment 里还有其他一些字段可以对滚动更新的过程做更细致的控制,它们都在 spec.strategy.rollingUpdate 里,比如 maxSurge、maxUnavailable 等字段,分别控制最多新增 Pod 数和最多不可用 Pod 数,一般用默认值就足够了,你如果感兴趣也可以查看 Kubernetes 文档进一步研究



版权声明:本文为qq_57414752原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。