试想一个场景

现在有一个正常运行的多副本应用,要对应用进行更新(比如更新image),Kubernetes会启动新副本来代理旧副本,然后发生了以下事件:

1、正常情况下新副本需要10s左右来完成准备工作,在此之前无法响应更新后的业务请求。

2、但由于人为配置错误,副本始终无法完成准备工作(比如无法连接后端数据库)

如果在没有配置Health Check的情况下,容器是可以正常运行的,只是无法提供正常服务,当所有的旧副本被新副本更新完成替代后,这个环境是不可用的,无法提供服务,如果发生在生产环境中,后果很严重。

相反,如果配置了Health Check的Readiness探测,只有通过这个探测才会被添加到Service中,如果没有通过探测,则旧副本继续运行,不影响生产业务。

模拟多副本应用的滚动更新故障

[root@node1 ~]# vim app.v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      run: app
  replicas: 10
  template:
    metadata:
      labels:
        run: app
    spec:
      containers:
      - name: app
        image: busybox
        args:
        - /bin/sh
        - -c
        - sleep 10; touch /tmp/healthy; sleep 300000
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 10
          periodSeconds: 5

因为一会要模拟滚动更新,所以复制一个文件进行小改动

[root@node1 ~]# scp app.v1.yml app.v2.yml 
[root@node1 ~]# vim app.v2.yml 
# 修改args部分
        args:
        - /bin/sh
        - -c
        - sleep 300000

先运行v1的正确的pod

[root@node1 ~]# kubectl apply -f app.v1.yml --record

查看10个Pod

[root@node1 ~]# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
app-688f655f7b-2clr7   1/1     Running   0          4m52s
app-688f655f7b-2zdkc   1/1     Running   0          4m52s
app-688f655f7b-74f9d   1/1     Running   0          4m52s
app-688f655f7b-9zrdj   1/1     Running   0          4m52s
app-688f655f7b-bbh8p   1/1     Running   0          4m52s
app-688f655f7b-gh5xn   1/1     Running   0          4m52s
app-688f655f7b-htsd2   1/1     Running   0          4m52s
app-688f655f7b-jx6rx   1/1     Running   0          4m52s
app-688f655f7b-ml99l   1/1     Running   0          4m52s
app-688f655f7b-tf22x   1/1     Running   0          4m52s

这个运行是没有问题的。现在使用v2进行滚动更新,v2的健康检查是肯定会失败的。

[root@node1 ~]# kubectl apply -f app.v2.yml --record
deployment.apps/app configured

查看Pod更新,准备删除2个旧副本,运行5个新副本

[root@node1 ~]# kubectl get pod
NAME                   READY   STATUS              RESTARTS   AGE
app-647855b8dc-7lnjs   0/1     ContainerCreating   0          12s
app-647855b8dc-c5cjd   0/1     ContainerCreating   0          12s
app-647855b8dc-jfqqs   0/1     ContainerCreating   0          12s
app-647855b8dc-kjcph   0/1     ContainerCreating   0          12s
app-647855b8dc-zctt7   0/1     ContainerCreating   0          12s
app-688f655f7b-2clr7   1/1     Running             0          7m29s
app-688f655f7b-2zdkc   1/1     Running             0          7m29s
app-688f655f7b-74f9d   1/1     Running             0          7m29s
app-688f655f7b-9zrdj   1/1     Running             0          7m29s
app-688f655f7b-bbh8p   1/1     Running             0          7m29s
app-688f655f7b-gh5xn   1/1     Running             0          7m29s
app-688f655f7b-htsd2   1/1     Terminating         0          7m29s
app-688f655f7b-jx6rx   1/1     Running             0          7m29s
app-688f655f7b-ml99l   1/1     Running             0          7m29s
app-688f655f7b-tf22x   1/1     Terminating         0          7m29s

查看Deployment

[root@node1 ~]# kubectl get deployments app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    8/10    5            8           8m16s

一段时间后,一共13个Pod在运行,其中5个新副本的READY不是正常的

[root@node1 ~]# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
app-647855b8dc-7lnjs   0/1     Running   0          2m31s
app-647855b8dc-c5cjd   0/1     Running   0          2m31s
app-647855b8dc-jfqqs   0/1     Running   0          2m31s
app-647855b8dc-kjcph   0/1     Running   0          2m31s
app-647855b8dc-zctt7   0/1     Running   0          2m31s
app-688f655f7b-2clr7   1/1     Running   0          9m48s
app-688f655f7b-2zdkc   1/1     Running   0          9m48s
app-688f655f7b-74f9d   1/1     Running   0          9m48s
app-688f655f7b-9zrdj   1/1     Running   0          9m48s
app-688f655f7b-bbh8p   1/1     Running   0          9m48s
app-688f655f7b-gh5xn   1/1     Running   0          9m48s
app-688f655f7b-jx6rx   1/1     Running   0          9m48s
app-688f655f7b-ml99l   1/1     Running   0          9m48s

证明新的副本一直没有通过Readiness的探测

这就很好的预防了没有健康检查时,滚动更新之后影响生产业务的麻烦,当然是用回滚也是可以预防的。

滚动更新副本更换的比例设置

在上面的滚动更新中发现在启动5个新副本的同时,停掉了2个旧副本,导致了后面运行了13个副本,5个新的,8个旧的。这个数字是怎么计算的。

之前在使用kubectl describe pod xxx的时候有看到过的两个参数maxSurgemaxUnavailable

[root@node1 ~]# kubectl describe deployments app | grep max
RollingUpdateStrategy:  25% max unavailable, 25% max surge

maxSurge

这个参数控制滚动更新过程中副本总数超过期望值(DESIRED)的上限。maxSurge可以是具体的整数(比如:3),也可以是百分比,向上取整。默认为25%

在上面的例子中,期望值为10,副本总数的最大值为:

10+10*25%=13,所以即使更新失败后的总数也是13

maxUnavailable

这个参数控制滚动更新过程中,不可用的副本占期望值的最大比例,也就是当滚动更新时,首先停掉几个旧副本。maxUnavailable 可以是具体的整数(比如 3),也可以是百分百,向下取整。maxUnavailable 默认值为 25%。

上面的例子中,期望值为10,可用的副本数最少为:

10-10*25%=8,所以失败后依然看到8个Pod在提供服务

总结 maxSurge 值越大,更新时创建的新副本数量就越多;maxUnavailable 值越大,更新时销毁的旧副本数量就越多。

理想情况下,这个案例滚动更新的过程应该是这样的:

  1. 首先创建 3 个新副本使副本总数达到 13 个。
  2. 然后销毁 2 个旧副本使可用的副本数降到 8 个。
  3. 当这 2 个旧副本成功销毁后,可再创建 2 个新副本,使副本总数保持为 13 个。
  4. 当新副本通过 Readiness 探测后,会使可用副本数增加,超过 8。
  5. 进而可以继续销毁更多的旧副本,使可用副本数回到 8。
  6. 旧副本的销毁使副本总数低于 13,这样就允许创建更多的新副本。
  7. 这个过程会持续进行,最终所有的旧副本都会被新副本替换,滚动更新完成。

而实际情况是在第 4 步就卡住了,新副本无法通过 Readiness 探测。这个过程可以在 kubectl describe deployment app 的日志部分查看。

最后更新失败之后,可以通过kubectl rollout undo回滚到上一个版本

应用maxSurge和maxUnavailable

因为直接使用上面使用过的文件,所以将创建的Pod先删除

[root@node1 ~]# kubectl delete -f app.v1.yml 
deployment.apps "app" deleted

修改v2文件添加maxSurge和maxUnavailable选项,因为v2会作为滚动更新的版本来使用

[root@node1 ~]# vim app.v2.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  strategy:
    rollingUpdate:
      maxSurge: 35%
      maxUnavailable: 35%
  selector:
    matchLabels:
      run: app
  replicas: 10
  template:
    metadata:
      labels:
        run: app
    spec:
      containers:
      - name: app
        image: busybox
        args:
        - /bin/sh
        - -c
        - sleep 10; touch /tmp/healthy; sleep 300000
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 10
          periodSeconds: 5

运行v1的Pod

[root@node1 ~]# kubectl apply -f app.v1.yml 
deployment.apps/app created

等待Pod创建好之后,查看

[root@node1 ~]# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
app-688f655f7b-27cpx   1/1     Running   0          30s
app-688f655f7b-2hhqq   1/1     Running   0          30s
app-688f655f7b-448z4   1/1     Running   0          30s
app-688f655f7b-6fgh2   1/1     Running   0          30s
app-688f655f7b-kz8vb   1/1     Running   0          30s
app-688f655f7b-szz2w   1/1     Running   0          30s
app-688f655f7b-tx87l   1/1     Running   0          30s
app-688f655f7b-vqqtq   1/1     Running   0          30s
app-688f655f7b-vvd7x   1/1     Running   0          30s
app-688f655f7b-zkhlr   1/1     Running   0          30s

进行v2的滚动更新

[root@node1 ~]# kubectl apply -f app.v2.yml 
deployment.apps/app configured

使用v2更新,v2还是之前不会更新成功的文件

[root@node1 ~]# kubectl apply -f app.v2.yml 
deployment.apps/app configured

主要是为了看出怎么样计算的百分比

[root@node1 ~]# kubectl describe deployment app
...
Replicas:               10 desired | 7 updated | 14 total | 7 available | 7 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  35% max unavailable, 35% max surge.
...

从上面的信息看出10个可用期望值,更新时,先直接启动7个新副本,保持总量新旧副本总量在14,也就是当启动了7个新副本,旧副本需要停掉3个,还剩7个,加上更新的7个就是14个。

如果要想直接根据期望值和更新时的新旧副本总量,来确定,这两个值的百分比的话 需要自己去根据期望值副本一步步推算,也是个繁琐的过程

评论




正在载入...
PoweredHexo
HostedAliyun
DNSAliyun
ThemeVolantis
UV
PV
BY-NC-SA 4.0