容器编排具有k8s中非常重要的功能:水平扩展/收缩。

作业副本与水平伸缩

例如更新了Deployment的Pod模板,那么Deployment就会遵循滚动更新的方式来升级现有的容器,这个功能的实现依赖于k8s中一个非常重要的概念:ReplicaSet。

一个ReplicaSet其实就是由副本数目的定义加上一个Pod模板组成的,例如

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

并且Deployment控制器实际操纵的,正是ReplicaSet对象,而不是Pod对象。所以正如在控制器模型末尾提到的ownerReference,一个Pod的ownerReference正是ReplicaSet对象。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

上面的例子中,声明的ReplicaSet的副本数为3,ReplicaSet通过控制器模式来保证系统中的Pod数目永远等于指定的个数,需要注意的是 Deployment 只允许容器的 restartPolicy=Always 的主要原因:只有在容器能保证自己始终是 Running 状态的前提下,ReplicaSet 调整 Pod 的个数才有意义,挂掉的Pod是没有作用的,要保证Pod出于Running就要能够重启

Deployment同样通过控制器模式来修改它所控制的ReplicaSet的个数和属性。然后ReplicaSet就会自动进行扩容或者收缩。

水平伸缩的命令为:

kubeclt scale deployment nginx-deployment --replicas=4

就算只有一个分片也比较推荐引入deployment进行管理,可以利用伸缩的命令进行启停:

# 启动
kubeclt scale deployment nginx-deployment --replicas=1
# 停止
kubeclt scale deployment nginx-deployment --replicas=0

滚动更新

创建这个nginx-deployment:

$ kubectl create -f nginx-deployment.yaml

通过如下命令可以看到deployment创建后的信息:

kubectl get deployments

还有一条命令可以实时查看deployment对象的状态变化

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.apps/nginx-deployment successfully rolled out

查看ReplicaSet的命令为:

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

一个ReplicaSet的名字由deployment的名称与一串随机字符串组成,ReplicaSet 会把这个随机字符串加在它所控制的所有 Pod 的标签里,从而保证这些 Pod 不会与集群里的其他 Pod 混淆

现在deployment已经启动,相关信息也查看了,那么接下来就是进行水平扩展或者收缩了。要完成这一点,其实非常简单,就是修改deployment的API对象,滚动更新就会自动触发

对于deployment的修改也有很多方式,比如,使用kubectl edit指令直接编辑etcd中的API对象:

$ kubectl edit deployment/nginx-deployment
... 
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1 # 1.7.9 -> 1.9.1
        ports:
        - containerPort: 80
...
deployment.extensions/nginx-deployment edited

编辑完成退出后就会触发滚动更新流程。 为了了解滚动更新的流程,使用 kubectl rollout status 指令查看 nginx-deployment 的状态变化

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment.extensions/nginx-deployment successfully rolled out

也可以查看deployment的events来查看滚动更新的流程

  • 使用修改后的Pod模板,创建一个新的ReplicaSet。
  • 然后将新的ReplicaSet的Pod副本数从0变为1,将旧的ReplicaSet的副本数减少1。
  • 这样交替进行,直到旧的Pod全部替换成新的Pod。

将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是“滚动更新”。这样可以保证在新的Pod启动不起来的时候会停止滚动更新,并且仍然存在旧的副本能够提供服务。这同样要求使用Pod的健康检查来保证应用的状态,而不是容器的状态。

deployment还可以确保在任何时间窗口内只有指定比例的Pod处于离线状态,并且只有指定比例的新Pod被创建出来,这两个比例值都是可以设置的,默认为DESIRE值的25%。这个策略,是 Deployment 对象的一个字段,名叫RollingUpdateStrategy ,如下所示:

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

因为deployment有3个Pod副本,所以这个配置可以保证滚动更新的过程中,永远保证最少有2个可用Pod副本,最多有4个可用的Pod副本。maxSurge 指定的是除了 DESIRED 数量之外,在一次“滚动”中,Deployment 控制器还可以创建多少个新 Pod;而 maxUnavailable 指的是,在一次“滚动”中,Deployment 控制器可以删除多少个旧 Pod。这两个配置还可以用前面我们介绍的百分比形式来表示

如果滚动更新失败,那么会自动停止。可以执行下面的命令回滚到旧版本,其实就是让这个旧 ReplicaSet再次“扩展”成 3 个 Pod,而让新的 ReplicaSet重新“收缩”到 0 个 Pod:

$ kubectl rollout undo deployment/nginx-deployment
deployment.extensions/nginx-deployment

deployment的每次变更都会记录一次版本,使用kubectl rollout histroy可以查看版本变更的历史,从1开始:

$ kubectl rollout history deployment/nginx-deployment
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

通过指定版本可以查看或回滚到任何一个历史版本

# 查看指定版本的API对象细节
$ kubectl rollout history deployment/nginx-deployment --revision=2
 
# 回滚到指定版本
$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment.extensions/nginx-deployment

在前面每次修改都会生成一个新的ReplicaSet对象,有些浪费资源,k8s提供了一个指令,可以使得对deployment的多次修改最后只生成一个ReplicaSet对象。就是kubectl rollout pause指令。用法如下:

$ kubectl rollout pause deployment/nginx-deployment
deployment.extensions/nginx-deployment paused

这条指令将deployment变为暂停状态,对其的任何修改都不会触发滚动更新,修改完成之后再恢复一下,那么所有的更改只会触发一次滚动更新:

$ kubectl rollout resume deployment/nginx-deployment
deployment.extensions/nginx-deployment resumed

就算多个修改只产生一个ReplicaSet对象,随着应用的更新这个对象也是会越来越多的。Deployment 对象有一个字段,叫作 spec.revisionHistoryLimit,就是 Kubernetes 为 Deployment 保留的“历史版本”个数。如果不需要保存历史版本,就将这个字段设置为0。

Deployment 实际上是一个两层控制器。首先,它通过 ReplicaSet 的个数来描述应用的版本;然后,它再通过 ReplicaSet 的属性(比如 replicas 的值),来保证 Pod 的副本数量。


tags: 容器编排 控制器 k8s