05-作业副本与水平扩展

Deployment实现了Kubernetes项目中一个非常重要的功能:Pod的水平扩展/收缩(Horizontal scaling out/in)。

如果修改了Deployment的Pod模板,那么Deployment就需要遵循滚动更新(rolling update)的方式来升级现有容器。

这个能力的实现依赖的是kubernetes项目中的另一个API 对象 : ReplicaSet。

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: nginx-set
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9

一个ReplicaSet对象就是由副本数目的定义和一个Pod模板组成。它是Deployment的一个子集

Deployment控制器实际操纵的,就是ReplicaSet对象,而不是Pod对象。即,Pod的ownerReference是ReplicaSet。

image

层层控制关系:其中ReplicaSet负责通过控制器模式,来保证系统中Pod的个数永远等于指定的个数。这也正是Deployment只允许容器的restartPolicy=Always的主要原因:只有在容器能保证自己始终Running的状态下,ReplicaSet调整Pod的个数才有意义

在此基础上,Deployment同样通过控制器模式,来操作ReplicaSet的个数和属性,进而实现水平扩展/收缩滚动更新,这两个编排动作。

水平扩展/收缩

  • 水平扩展/收缩:Deployment Controller只需要修改它所控制的ReplicaSet的Pod副本个数就可以了。

通过kubectl scale指令实现:

$ kubectl scale deployment nginx-deployment --replicas=4
deployment.apps/nginx-deployment scaled

滚动更新

  • 滚动更新
$ kubectl create -f nginx-deployment.yaml --record    //创建一个Deployment  --record参数的含义是记录每次操作所执行的命令

$ kubectl get deployments       //查看创建的deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         0         0            0           1s

返回结果中的四个状态字段的含义:

  • DESIRED:用户期望的Pod副本个数(spec.replicas的值)
  • CURRENT:当前处于Running状态的Pod的个数
  • UP-TO-DATE:当前处于最新版的Pod的个数,所谓最新版指的是Pod的Spec部分与Deployment里Pod模板的定义完全一致。
  • AVAILABLE:当前已经可用的Pod的个数,即,既是Running又是最新版,并且已经处于Ready(健康检查正确)状态的Pod个数。

AVAILABLE字段,描述的才是用户所期望的最终状态。

$ kubectl rollout status deployment/nginx-deployment         //实时查看Deployment对象的状态变化
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...       //意味着有2个Pod到了UP-TO-DATE状态
deployment.apps/nginx-deployment successfully rolled out

$ kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           20s


$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-3167673210   3         3         3       20s

当用户提交了Deployment对象后,Deployment Controller就会立即创建一个Pod副本个数为3的ReplicaSet。这个ReplicaSet的名字,则是由Deployment的名字和一个随机字符串共同组成的。

随机字符串叫作pod-template-hash,ReplicaSet会把这个随机字符串加在它所控制的所有Pod的标签里,从而保证这些Pod不会与集群里的其他Pod混淆。

ReplicaSet也有DESIRED、CURRENT、READY字段,Deployment只是多加了UP-TO-DATE这个与版本有关的状态字段。

这个时候修改Deployment的Pod模板,滚动更新会自动触发。

修改Deployment的方法

  1. 修改yaml文件
  2. 直接修改Etcd里的API对象,使用kubectl edit指令
$ kubectl edit deployment/nginx-deployment      //kubectl edit 指令直接打开nginx-deployment的API对象,然后就可以修改Pod模板部分
...
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1 # 1.7.9 -> 1.9.1
        ports:
        - containerPort: 80
...
deployment.extensions/nginx-deployment edited     //编辑完成后,保存退出,kubernetes会立刻触发滚动更新的过程。

$ kubectl rollout status        //查看deployment的状态变化 deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.extensions/nginx-deployment successfully rolled out

$ kubectl describe deployment nginx-deployment     //通过查看Evemts,来查看滚动更新的流程
...
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
...
  Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 1
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 2
  Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 2
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 1
  Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1764197365 to 3
  Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-3167673210 to 0

kubectl edit 只不过是将API对象的内容下载到本地文件,修改完成后再提交上去。

  1. 修改Deployment里的Pod定义后,Deployment Controller会使用这个修改后的Pod模板,创建一个新的ReplicaSet(hash=1764197365),这个新的ReplicaSet的初始Pod副本数是0;
  2. 在Age=24S的位置,Deployment Controller开始将新的ReplicaSet所控制的Pod副本数从0变成1,即水平扩展出1个副本;
  3. 在Age=22S的位置,Deployment Controller又将旧的ReplicaSet(hash=316763210)所控制的旧Pod副本数减少1个,即水平收缩成为2个副本;
  4. 如此交替,直到新ReplicaSet管理的副本数从0变到3,旧ReplicaSet管理的Pod副本数从3变到0。

这样就完成了这一组Pod的版本升级过程。

将一个集群中正在运行的多个Pod版本,交替地逐一升级的过程,就是滚动更新。

$ kubectl get rs        //滚动更新完成后,查看ReplicaSet的最终状态
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1764197365   3         3         3       6s
nginx-deployment-3167673210   0         0         0       30s

滚动更新的好处

比如,在升级刚开始的时候,集群里只有 1 个新版本的 Pod。如果这时,新版本 Pod 有问题启动不起来,那么“滚动更新”就会停止,从而允许开发和运维人员介入。而在这个过程中,由于应用本身还有两个旧版本的 Pod 在线,所以服务并不会受到太大的影响。

当然,这也就要求你一定要使用 Pod 的 Health Check 机制检查应用的运行状态,而不是简单地依赖于容器的 Running 状态。要不然的话,虽然容器已经变成 Running 了,但服务很有可能尚未启动,“滚动更新”的效果也就达不到了。

而为了进一步保证服务的连续性,Deployment Controller 还会确保,在任何时间窗口内,只有指定比例的 Pod 处于离线状态。同时,它也会确保,在任何时间窗口内,只有指定比例的新 Pod 被创建出来。这两个比例的值都是可以配置的,默认都是 DESIRED 值的 25%

所以,在上面这个 Deployment 的例子中,它有 3 个 Pod 副本,那么控制器在“滚动更新”的过程中永远都会确保至少有 2 个 Pod 处于可用状态,至多只有 4 个 Pod 同时存在于集群中。

这个策略,是 Deployment 对象的一个字段,名叫 RollingUpdateStrategy,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
...
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1

在上面这个 RollingUpdateStrategy 的配置中:

  • maxSurge 指定的是除了 DESIRED 数量之外,在一次“滚动”中,Deployment 控制器还可以创建多少个新 Pod;
  • maxUnavailable 指的是,在一次“滚动”中,Deployment 控制器可以删除多少个旧 Pod。

同时,这两个配置还可以用前面我们介绍的百分比形式来表示,比如:maxUnavailable=50%,指 的是我们最多可以一次删除“50%*DESIRED 数量”个 Pod。

结合以上讲述,现在我们可以扩展一下 Deployment、ReplicaSet 和 Pod 的关系图了。

image

如上所示,Deployment 的控制器,实际上控制的是 ReplicaSet 的数目,以及每个 ReplicaSet 的属性

一个应用的版本,对应的正是一个 ReplicaSet;这个版本应用的 Pod 数量,则由 ReplicaSet 通过它自己的控制器(ReplicaSet Controller)来保证。

通过这样的多个 ReplicaSet 对象,Kubernetes 项目就实现了对多个“应用版本”的描述。

Deployment 对应用进行版本控制的具体原理

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91      //修改Deployment所使用的镜像
deployment.extensions/nginx-deployment image updated

使用一个叫kubectl set image的指令,直接修改 nginx-deployment 所使用的镜像。这个命令的好处就是,你可以不用像 kubectl edit 那样需要打开编辑器。

$ kubectl rollout undo deployment/nginx-deployment     //把整个Deployment回滚到上一个版本
deployment.extensions/nginx-deployment

在具体操作上,Deployment的控制器,让旧的ReplicaSet再次扩展,让新的ReplicaSet重新收缩。

$ kubectl rollout history deployment/nginx-deployment     //查看每次Deployment变更对应的版本,在kubectl create的时候使用--record参数配合,记录所有版本被创建时的kubectl命令
deployments "nginx-deployment"
REVISION    CHANGE-CAUSE
1           kubectl create -f nginx-deployment.yaml --record
2           kubectl edit deployment/nginx-deployment
3           kubectl set image deployment/nginx-deployment nginx=nginx:1.91


$ kubectl rollout history deployment/nginx-deployment --revision=2        //查看每个版本对应的Deployment的AOI对象的细节


$ kubectl rollout undo deployment/nginx-deployment --to-revision=2       //回滚到指定版本,控制前按照滚动更新的方式完成降级操作
deployment.extensions/nginx-deployment

对Deployment的每一次更新操作,都会生成一个新的ReplicaSet对象,浪费资源,使用如下指令,在多次更新后只生成一个ReplicaSet对象:

$ kubectl rollout pause deployment/nginx-deployment     //让Deployment进入暂停状态
deployment.extensions/nginx-deployment paused
  1. 目前Deployment处于暂停状态
  2. 使用kubectl edit 或者 kubectl set image指令修改的Deployment的内容,并不会触发滚动更新,也不会创建新的ReplicaSet
  3. 对Deployment的修改完成后执行如下指令,把Deployment恢复回来
$ kubectl rollout resume deploy/nginx-deployment
deployment.extensions/nginx-deployment resumed

在kubectl rollout resume指令执行之前,在kubectl rollout pause指令执行之后的这段时间里,对Deployment进行的所有修改,最后只会触发一次滚动更新。

Deployment对象有一个字段,叫作spec.revisionHistoryLimit,就是Kubernetes为Deployment保留的“历史版本”个数,把它设置为0,就再也不能回滚了。

总结

Deployment是一个两层控制器:

  1. 通过ReplicaSet的个数来描述应用的版本
  2. 通过ReplicaSet的属性(比如replicas的值)来保证Pod的副本数量

graph LR Deployment-->ReplicaSet版本 ReplicaSet版本-->Pod副本数

Kubernetes 项目通过对Deployment的设计,完成了对应用的抽象,我们可以使用Deployment来描述应用,使用kubectl rollout 命令控制用应用的版本

有了Deployment的能力,可以轻松实现金丝雀发布,蓝绿发布,A/B测试等很多应用发布模式。参考这个GitHub库

滚动更新是一个自动化更新的金丝雀发布。

  • 金丝雀部署:优先发布一台或少量机器升级,等验证无误后再更新其他机器。

    • 优点是用户影响范围小,
    • 不足之处是要额外控制如何做自动更新。
  • 蓝绿部署:2组机器,

    • 蓝代表当前的V1版本,
    • 绿代表已经升级完成的V2版本。

通过LB(Load Balancer)将流量全部导入V2完成升级部署。

  • 优点是切换快速,
  • 缺点是影响全部用户。
上次修改: 14 April 2020