deployment,有点长

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了deployment,有点长相关的知识,希望对你有一定的参考价值。

让我这个小白对这两张图的作者致以最高的敬意!!!


没有这张图做铺垫,直接看会晕的。

文章目录


实操部分

简介

一个 Deployment 为 Pods 和 ReplicaSets 提供声明式的更新能力。你负责描述 Deployment 中的 目标状态,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 接收其资源。


创建 Deployment

下面是 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx Pods:

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.14.2
        ports:
        - containerPort: 80

你可以设置 --record 标志将所执行的命令写入资源注解 kubernetes.io/change-cause 中。这对于以后的检查是有用的。
使用方式如:

kubectl rollout history deployment.v1.apps/nginx-deployment
kubectl get deployments -A
NAMESPACE     NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
default       nginx                     1/1     1            1           7h3m
kube-system   calico-kube-controllers   1/1     1            1           11d
kube-system   coredns                   2/2     2            2           11d
w             pc-deployment             3/3     3            3           2d11h
w             pc-deployment2            3/3     3            3           6h57m

在检查集群中的 Deployment 时,所显示的字段有:

  • NAME 列出了集群中 Deployment 的名称。
  • READY 显示应用程序的可用的 副本 数。显示的模式是“就绪个数/期望个数”。
  • UP-TO-DATE 显示为了达到期望状态已经更新的副本数。
  • AVAILABLE 显示应用可供用户使用的副本数。
  • AGE 显示应用程序运行的时间。

查看 Deployment 创建的 ReplicaSet(rs):

[root@k8s-master wlf]# kubectl get replicaSet -A
NAMESPACE     NAME                                 DESIRED   CURRENT   READY   AGE
default       nginx-55d9d65854                     1         1         1       7h9m
kube-system   calico-kube-controllers-6d4bfc7c57   1         1         1       11d
kube-system   coredns-9d85f5447                    2         2         2       11d
w             pc-deployment-6696798b78             3         3         3       2d11h
w             pc-deployment2-6696798b78            3         3         3       3m44s

注意 ReplicaSet 的名称始终被格式化为[Deployment名称]-[随机字符串]。 其中的随机字符串是使用 pod-template-hash 作为种子随机生成的。

Deployment 控制器将 pod-template-hash 标签添加到 Deployment 所创建或收留的 每个 ReplicaSet 。此标签可确保 Deployment 的子 ReplicaSets 不重叠。 标签是通过对 ReplicaSet 的 PodTemplate 进行哈希处理。 所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet 可能拥有的任何现有 Pod 中。

[root@k8s-master wlf]# kubectl get pods -n w --show-labels
NAME                              READY   STATUS    RESTARTS   AGE     LABELS
pc-deployment-6696798b78-2jpf4    1/1     Running   1          2d11h   app=nginx-pod,pod-template-hash=6696798b78
pc-deployment-6696798b78-lggkb    1/1     Running   1          2d11h   app=nginx-pod,pod-template-hash=6696798b78
pc-deployment-6696798b78-vsxj8    1/1     Running   1          2d11h   app=nginx-pod,pod-template-hash=6696798b78
pc-deployment2-6696798b78-mkj9b   1/1     Running   0          6m      app=nginx-pod,pod-template-hash=6696798b78
pc-deployment2-6696798b78-rcjhs   1/1     Running   0          6m      app=nginx-pod,pod-template-hash=6696798b78
pc-deployment2-6696798b78-ztw9r   1/1     Running   0          6m      app=nginx-pod,pod-template-hash=6696798b78

当 Deployment 创建或者接管 ReplicaSet 时,Deployment controller 会自动为 Pod 添加 pod-template-hash label。这样做的目的是防止 Deployment 的子 ReplicaSet 的 pod 名字重复。通过将 ReplicaSet 的 PodTemplate 进行哈希散列,使用生成的哈希值作为 label 的值,并添加到 ReplicaSet selector 里、 pod template label 和 ReplicaSet 管理中的 Pod 上。


更新 Deployment

仅当 Deployment Pod 模板(即 .spec.template)发生改变时,才会触发Deployment 上线。 其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作。

例如:

kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 --record

或者:

kubectl edit deployment.v1.apps/nginx-deployment
[root@k8s-master wlf]# kubectl describe deployments/pc-deployment2 -n w
Name:                   pc-deployment2
Namespace:              w
CreationTimestamp:      Tue, 10 May 2022 07:24:22 -0700
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 2
                        kubectl.kubernetes.io/last-applied-configuration:
                          "apiVersion":"apps/v1","kind":"Deployment","metadata":"annotations":"deployment.kubernetes.io/revision":"1","creationTimestamp":"2022-...
                        kubernetes.io/change-cause: kubectl set image deployment/pc-deployment2 nginx=nginx:1.16.1 --namespace=w --record=true
Selector:               app=nginx-pod
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx-pod
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   pc-deployment2-684d778d49 (3/3 replicas created)
Events:
  Type    Reason             Age    From                   Message
  ----    ------             ----   ----                   -------
  Normal  ScalingReplicaSet  3m55s  deployment-controller  Scaled up replica set pc-deployment2-684d778d49 to 1
  Normal  ScalingReplicaSet  2m57s  deployment-controller  Scaled down replica set pc-deployment2-6696798b78 to 2
  Normal  ScalingReplicaSet  2m57s  deployment-controller  Scaled up replica set pc-deployment2-684d778d49 to 2
  Normal  ScalingReplicaSet  2m53s  deployment-controller  Scaled down replica set pc-deployment2-6696798b78 to 1
  Normal  ScalingReplicaSet  2m53s  deployment-controller  Scaled up replica set pc-deployment2-684d778d49 to 3
  Normal  ScalingReplicaSet  2m50s  deployment-controller  Scaled down replica set pc-deployment2-6696798b78 to 0

Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pods 75% 处于运行状态(最大不可用比例为 25%)。

Deployment 还确保仅所创建 Pod 数量只可能比期望 Pods 数高一点点。 默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 25%(最大峰值 25%)。

例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除了一些旧的 Pods, 并创建了新的 Pods。它不会杀死老 Pods,直到有足够的数量新的 Pods 已经出现。 在足够数量的旧 Pods 被杀死前并没有创建新 Pods。它确保至少 2 个 Pod 可用,同时 最多总共 4 个 Pod 可用。

可以看到,当第一次创建 Deployment 时,它创建了一个 ReplicaSet( pc-deployment2-6696798b78) 并将其直接扩容至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet (pc-deployment2-684d778d49),并将其扩容为 1,然后将旧 ReplicaSet 缩容到 2, 以便至少有 2 个 Pod 可用且最多创建 4 个 Pod。 然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。 最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。

每当 Deployment controller 观测到有新的 deployment 被创建时,如果没有已存在的 ReplicaSet 来创建期望个数的 Pod 的话,就会创建出一个新的 ReplicaSet 来做这件事。已存在的 ReplicaSet 控制 label 与 .spec.selector 匹配但是 template 跟 .spec.template 不匹配的 Pod 缩容。最终,新的 ReplicaSet 将会扩容出 .spec.replicas 指定数目的 Pod,旧的 ReplicaSet 会缩容到 0。

如果您更新了一个的已存在并正在进行中的 Deployment,每次更新 Deployment 都会创建一个新的 ReplicaSet 并扩容它,同时回滚之前扩容的 ReplicaSet —— 将它添加到旧的 ReplicaSet 列表中,开始缩容。

例如,假如您创建了一个有 5 个 niginx:1.7.9 replica 的 Deployment,但是当还只有 3 个 nginx:1.7.9 的 replica 创建出来的时候您就开始更新含有 5 个 nginx:1.9.1 replica 的 Deployment。在这种情况下,Deployment 会立即杀掉已创建的 3 个 nginx:1.7.9 的 Pod,并开始创建 nginx:1.9.1 的 Pod。它不会等到所有的 5 个 nginx:1.7.9 的 Pod 都创建完成后才开始改变航道。


Label selector 更新

我们通常不鼓励更新 label selector,增添 selector 需要同时在 Deployment 的 spec 中更新新的 label,否则将返回校验错误。此更改是不可覆盖的,这意味着新的 selector 不会选择使用旧 selector 创建的 ReplicaSet 和 Pod,从而导致所有旧版本的 ReplicaSet 都被丢弃,并创建新的 ReplicaSet。
更新 selector,即更改 selector key 的当前值,将导致跟增添 selector 同样的后果。
删除 selector,即删除 Deployment selector 中的已有的 key,不需要对 Pod template label 做任何更改,现有的 ReplicaSet 也不会成为孤儿,但是请注意,删除的 label 仍然存在于现有的 Pod 和 ReplicaSet 中。


回滚 Deployment

# 例如错误的更新到了一个xxx版本
[root@k8s-master01 ~]# kubectl set image deploy nginx nginx=nginx:xxx --record   
deployment.apps/nginx image updated

# 查看kubectl更新的历史命令
[root@k8s-master01 ~]# kubectl rollout history deploy nginx 
deployment.apps/nginx 
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image deploy nginx nginx=nginx:1.15.3 --record=true
3         kubectl set image deploy nginx nginx=nginx:xxx --record=true

# 回滚到上一个版本
[root@k8s-master01 ~]# kubectl rollout undo deploy nginx
deployment.apps/nginx rolled back

回滚到指定版本

# 多次更新错误版本
[root@k8s-master01 ~]# kubectl set image deploy nginx nginx=nginx:aa --record
deployment.apps/nginx image updated
[root@k8s-master01 ~]# kubectl set image deploy nginx nginx=nginx:bb --record
deployment.apps/nginx image updated
[root@k8s-master01 ~]# kubectl set image deploy nginx nginx=nginx:cc --record
deployment.apps/nginx image updated

# 查看kubectl更新的历史命令
[root@k8s-master01 ~]# kubectl rollout history deploy nginx 
deployment.apps/nginx 
REVISION  CHANGE-CAUSE
1         <none>
3         kubectl set image deploy nginx nginx=nginx:xxx --record=true
4         kubectl set image deploy nginx nginx=nginx:1.15.3 --record=true
5         kubectl set image deploy nginx nginx=nginx:aa --record=true
6         kubectl set image deploy nginx nginx=nginx:bb --record=true
7         kubectl set image deploy nginx nginx=nginx:cc --record=true

# 查看指定版本的详细信息 ---看revision对应的数字即可
[root@k8s-master01 ~]# kubectl rollout history deploy nginx --revision=4
deployment.apps/nginx with revision #4
Pod Template:
  Labels:       app=nginx
        pod-template-hash=5dfc8689c6
  Annotations:  kubernetes.io/change-cause: kubectl set image deploy nginx nginx=nginx:1.15.3 --record=true
  Containers:
   nginx:
    Image:      nginx:1.15.3
    Port:       <none>
    Host Port:  <none>
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>
  
# 回滚到指定版本
[root@k8s-master01 ~]# kubectl rollout undo deploy nginx --to-revision=4
deployment.apps/nginx rolled back

缩放 deployment

手动缩放:

kubectl scale deployment.v1.apps/nginx-deployment --replicas=10

假设集群启用了Pod 的水平自动缩放, 你可以为 Deployment 设置自动缩放器,并基于现有 Pods 的 CPU 利用率选择 要运行的 Pods 个数下限和上限。

自动缩放:

kubectl autoscale deployment.v1.apps/nginx-deployment --min=2 --max=10 --cpu-percent=80

弄一下就知道我的电脑原来只支持两个 replica 啊。。。


暂停、恢复 Deployment

你可以在触发一个或多个更新之前暂停 Deployment,然后再恢复其执行。 这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。

使用如下指令暂停运行:

[root@k8s-master wlf]# kubectl rollout pause deployment.v1.apps/pc-deployment -n w
deployment.apps/pc-deployment paused

接下来更新 Deployment 镜像:

[root@k8s-master wlf]# kubectl set image deployment.v1.apps/pc-deployment -n w nginx=nginx:1.16.1
deployment.apps/pc-deployment image updated

注意没有新的上线被触发:

[root@k8s-master wlf]# kubectl rollout history deployment.v1.apps/pc-deployment -n w
deployment.apps/pc-deployment 
REVISION  CHANGE-CAUSE
1         <none>

获取上线状态确保 Deployment 更新已经成功:

$ kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx-2142116321   3         3         3         2m

你可以根据需要执行很多更新操作,例如,可以要使用的资源:

$ kubectl set resources deployment.v1.apps/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
deployment.apps/nginx-deployment resource requirements updated

暂停 Deployment 之前的初始状态将继续发挥作用,但新的更新在 Deployment 被 暂停期间不会产生任何效果。

最终,恢复 Deployment 执行并观察新的 ReplicaSet 的创建过程,其中包含了所应用的所有更新:

$ kubectl rollout resume deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment resumed

#观察上线的状态

$ kubectl get rs -w
[root@k8s-master wlf]# kubectl get rs -n w -w
NAME                        DESIRED   CURRENT   READY   AGE
pc-deployment-6696798b78    0         0         0       2d22h
pc-deployment-745fd7bcf6    2         2         2       49s

说明: 你不可以回滚处于暂停状态的 Deployment,除非先恢复其执行状态。


编排 Deployment

Pod template

  • .spec 中只有 .spec.template 和 .spec.selector 是必需的字段
  • .spec.template 是一个 Pod 模板。它和 Pod 的语法规则完全相同。 只是这里它是嵌套的,因此不需要 apiVersion 或 kind。
  • 除了 Pod 的必填字段外,Deployment 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。对于标签,请确保不要与其他控制器重叠。请参考选择算符。

8.2 Replicas

.spec.replicas 是指定所需 Pod 的可选字段。它的默认值是1

spec.selector

  • .spec.selector 是指定本 Deployment 的 Pod标签选择算符的必需字段。
  • .spec.selector 必须匹配 .spec.template.metadata.labels,否则请求会被 API 拒绝。
  • 在 API apps/v1版本中,.spec.selector 和 .metadata.labels 如果没有设置的话,不会被默认设置为 .spec.template.metadata.labels,所以需要明确进行设置。 同时在apps/v1版本中,Deployment 创建后 .spec.selector 是不可变的

策略

.spec.strategy 策略指定用于用新 Pods 替换旧 Pods 的策略。 .spec.strategy.type 可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。

  • 重新创建 Deployment
    如果 .spec.strategy.type==Recreate,在创建新 Pods 之前,所有现有的 Pods 会被杀死。

  • 滚动更新 Deployment
    Deployment 会在 .spec.strategy.type==RollingUpdate时,采取 滚动更新的方式更新 Pods。你可以指定 maxUnavailable 和 maxSurge 来控制滚动更新 过程。

  • 最大峰值
    .spec.strategy.rollingUpdate.maxSurge 是一个可选字段,用来指定可以创建的超出 期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)。 如果 MaxUnavailable 为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。 此字段的默认值为 25%。

例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod 的总数不超过所需 Pod 总数的 130%。一旦旧 Pods 被杀死,新的 ReplicaSet 可以进一步扩容, 同时确保更新期间的任何时候运行中的 Pods 总数最多为所需 Pods 总数的 130%。

进度期限秒数

.spec.progressDeadlineSeconds 是一个可选字段,用于指定系统在报告 Deployment 进展失败 之前等待 Deployment 取得进展的秒数。 这类报告会在资源状态中体现为 Type=Progressing、Status=False、 Reason=ProgressDeadlineExceeded。Deployment 控制器将持续重试 Deployment。 将来,一旦实现了自动回滚,Deployment 控制器将在探测到这样的条件时立即回滚 Deployment。

如果指定,则此字段值需要大于 .spec.minReadySeconds 取值

最短就绪时间

.spec.minReadySeconds 是一个可选字段,用于指定新创建的 Pod 在没有任意容器崩溃情况下的最小就绪时间, 只有超出这个时间 Pod 才被视为可用。默认值为 0(Pod 在准备就绪后立即将被视为可用)。

Rollback To

.spec.rollbackTo 是一个可以选配置项,用来配置 Deployment 回退的配置。设置该参数将触发回退操作,每次回退完成后,该值就会被清除。

Revision

.spec.rollbackTo.revision 是一个可选配置项,用来指定回退到的 revision。默认是 0,意味着回退到上一个 revision。

修订历史限制

Deployment 的修订历史记录存储在它所控制的 ReplicaSets 中。

.spec.revisionHistoryLimit 是一个可选字段,用来设定出于会滚目的所要保留的旧 ReplicaSet 数量。 这些旧 ReplicaSet 会消耗 etcd 中的资源,并占用 kubectl get rs 的输出。 每个 Deployment 修订版本的配置都存储在其 ReplicaSets 中;因此,一旦删除了旧的 ReplicaSet, 将失去回滚到 Deployment 的对应修订版本的能力。 默认情况下,系统保留 10 个旧 ReplicaSet,但其理想值取决于新 Deployment 的频率和稳定性。

更具体地说,将此字段设置为 0 意味着将清理所有具有 0 个副本的旧 ReplicaSet。 在这种情况下,无法撤消新的 Deployment 上线,因为它的修订历史被清除了。

paused(暂停的)

.spec.paused 是用于暂停和恢复 Deployment 的可选布尔字段。 暂停的 Deployment 和未暂停的 Deployment 的唯一区别是,Deployment 处于暂停状态时, PodTemplateSpec 的任何修改都不会触发新的上线。 Deployment 在创建时是默认不会处于暂停状态。


原理部分

deployment控制器实现流程

1、Deployment 控制器从 Etcd 中获取到所有携带了“app: nginx”标签的 Pod,然后统计它们的数量,这就是实际状态;
2、Deployment 对象的 Replicas 字段的值就是期望状态;
3、Deployment 控制器将两个状态做比较,然后根据比较结果,确定是创建 Pod,还是删除已有的 Pod。

可以看到,一个 Kubernetes 对象的主要编排逻辑,实际上是在第三步的“对比”阶段完成的。这个操作,通常被叫作调谐(Reconcile)。这个调谐的过程,则被称作“Reconcile Loop”(调谐循环)或者“Sync Loop”(同步循环)。

在具体实现中,实际状态往往来自于 Kubernetes 集群本身。比如,

  • kubelet 通过心跳汇报的容器状态和节点状态;
  • 监控系统中保存的应用监控数据;
  • 控制器主动收集的它自己感兴趣的信息。

这些都是常见的实际状态的来源。而期望状态,一般来自于用户提交的 YAML 文件。比如,Deployment 对象中 Replicas 字段的值。很明显,这些信息往往都保存在 Etcd 中。


控制定义(期望)与被控制对象(模板)

其实,像 Deployment 这种控制器的设计原理,就是我们前面提到过的,“用一种对象管理另一种对象”的“艺术”。其中,这个控制器对象本身,负责定义被管理对象的期望状态。比如,Deployment 里的 replicas=2 这个字段。而被控制对象的定义,则来自于一个“模板”。比如,Deployment 里的 template 字段。可以看到,Deployment 这个 template 字段里的内容,跟一个标准的 Pod 对象的 API 定义,丝毫不差。而所有被这个 Deployment 管理的 Pod 实例,其实都是根据这个 template 字段的内容创建出来的。

像 Deployment 定义的 template 字段,在 Kubernetes 项目中有一个专有的名字,叫作 PodTemplate(Pod 模板)。这个概念非常重要,因为后面我要讲解到的大多数控制器,都会使用 PodTemplate 来统一定义它所要管理的 Pod。更有意思的是,我们还会看到其他类型的对象模板,比如 Volume 的模板。至此,我们就可以对 Deployment 以及其他类似的控制器,做一个简单总结了:

如上图所示,类似 Deployment 这样的一个控制器,实际上都是由上半部分的控制器定义(包括期望状态),加上下半部分的被控制对象的模板组成的。

这就是为什么,在所有 API 对象的 Metadata 里,都有一个字段叫作 ownerReference,用于保存当前这个 API 对象的拥有者(Owner)的信息。

那么,对于我们这个 nginx-deployment 来说,它创建出来的 Pod 的 ownerReference 就是 nginx-deployment 吗?或者说,nginx-deployment 所直接控制的,就是 Pod 对象么?不是,是ReplicaSet。

为了实操的时候方便理解,图我已经放在文章开头了。


ReplicaSet

Deployment 看似简单,但实际上,它实现了 Kubernetes 项目中一个非常重要的功能:Pod 的“水平扩展 / 收缩”(horizontal scaling out/in)。这个功能,是从 PaaS 时代开始,一个平台级项目就必须具备的编排能力。

举个例子,如果你更新了 Deployment 的 Pod 模板(比如,修改了容器的镜像),那么 Deployment 就需要遵循一种叫作“滚动更新”(rolling update)的方式,来升级现有的容器。而这个能力的实现,依赖的是 Kubernetes 项目中的一个非常重要的概念(API 对象):ReplicaSet。

ReplicaSet 的结构非常简单,我们可以通过这个 YAML 文件查看一下:

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

从这个 YAML 文件中,我们可以看到,一个 ReplicaSet 对象,其实就是由副本数目的定义和一个 Pod 模板组成的。不难发现,它的定义其实是 Deployment 的一个子集。

更重要的是,Deployment 控制器实际操纵的,正是这样的 ReplicaSet 对象,而不是 Pod 对象。

明白了这个原理,我再来和你一起分析一个如下所示的 Deployment:

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

可以看到,这就是一个我们常用的 nginx-deployment,它定义的 Pod 副本个数是 3(spec.replicas=3)。

开局那张图,再放一下这里要用。

通过这张图,我们就很清楚地看到,一个定义了 replicas=3 的 Deployment,与它的 ReplicaSet,以及 Pod 的关系,实际上是一种“层层控制”的关系。

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

而在此基础上,Deployment 同样通过“控制器模式”,来操作 ReplicaSet 的个数和属性,进而实现“水平扩展 / 收缩”和“滚动更新”这两个编排动作。其中,“水平扩展 / 收缩”非常容易实现,Deployment Controller 只需要修改它所控制的 ReplicaSet 的 Pod 副本个数就可以了。

比如,把这个值从 3 改成 4,那么 Deployment 所对应的 ReplicaSet,就会根据修改后的值自动创建一个新的 Pod。这就是“水平扩展”了;“水平收缩”则反之。


滚动更新

详细流程前文已经提及,这类不再赘述。

将一个集群中正在运行的多个 Pod 版本,交替地逐一升级的过程,就是“滚动更新”。在这个“滚动更新”过程完成之后,你可以查看一下新、旧两个 ReplicaSet 的最终状态:

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1764197365   3         3         3       6s
nginx-deployment-3167673210   0         0         0       30s

前面这个地方没有讲清楚,这里再补充一下下:
其中,旧 ReplicaSet(hash=3167673210)已经被“水平收缩”成了 0 个副本。这种“滚动更新”的好处是显而易见的。

比如,在升级刚开始的时候,集群里只有 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 的关系图了:

如上所示,Deployment 的控制器,实际上控制的是 ReplicaSet 的数目,以及每个 ReplicaSet 的属性。而一个应用的版本,对应的正是一个 ReplicaSet;这个版本应用的 Pod 数量,则由 ReplicaSet 通过它自己的控制器(ReplicaSet Controller)来保证。通过这样的多个 ReplicaSet 对象,Kubernetes 项目就实现了对多个“应用版本”的描述。而明白了“应用版本和 ReplicaSet 一一对应”的设计思想之后,我就可以为你讲解一下Deployment 对应用进行版本控制的具体原理了。

这一次,我会使用一个叫 kubectl set image 的指令,直接修改 nginx-deployment 所使用的镜像。这个命令的好处就是,你可以不用像 kubectl edit 那样需要打开编辑器。不过这一次,我把这个镜像名字修改成为了一个错误的名字,比如:nginx:1.91。这样,这个 Deployment 就会出现一个升级失败的版本。我们一起来实践一下:

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.91
deployment.extensions/nginx-deployment image updated

由于这个 nginx:1.91 镜像在 Docker Hub 中并不存在,所以这个 Deployment 的“滚动更新”被触发后,会立刻报错并停止。这时,我们来检查一下 ReplicaSet 的状态,如下所示:

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1764197365   2         2         2       24s
nginx-deployment-3167673210   0         0         0       35s
nginx-deployment-2156724341   2         2         0       7s

通过这个返回结果,我们可以看到,新版本的 ReplicaSet(hash=2156724341)的“水平扩展”已经停止。而且此时,它已经创建了两个 Pod,但是它们都没有进入 READY 状态。这当然是因为这两个 Pod 都拉取不到有效的镜像。

与此同时,旧版本的 ReplicaSet(hash=1764197365)的“水平收缩”,也自动停止了。此时,已经有一个旧 Pod 被删除,还剩下两个旧 Pod。那么问题来了, 我们如何让这个 Deployment 的 3 个 Pod,都回滚到以前的旧版本呢?我们只需要执行一条 kubectl rollout undo 命令,就能把整个 Deployment 回滚到上一个版本:

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

很容易想到,在具体操作上,Deployment 的控制器,其实就是让这个旧 ReplicaSet(hash=1764197365)再次“扩展”成 3 个 Pod,而让新的 ReplicaSet(hash=2156724341)重新“收缩”到 0 个 Pod。

更进一步地,如果我想回滚到更早之前的版本,要怎么办呢?

首先,我需要使用 kubectl rollout history 命令,查看每次 Deployment 变更对应的版本。而由于我们在创建这个 Deployment 的时候,指定了–ecord 参数,所以我们创建这些版本时执行的 kubectl 命令,都会被记录下来。这个操作的输出如下所示:

$ 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

可以看到,我们前面执行的创建和更新操作,分别对应了版本 1 和版本 2,而那次失败的更新操作,则对应的是版本 3。

当然,你还可以通过这个 kubectl rollout history 指令,看到每个版本对应的 Deployment 的 API 对象的细节,具体命令如下所示:

$ kubectl rollout history deployment/nginx-deployment --revision=2

然后,我们就可以在 kubectl rollout undo 命令行最后,加上要回滚到的指定版本的版本号,就可以回滚到指定版本了。这个指令的用法如下:

$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment.extensions/nginx-deployment

这样,Deployment Controller 还会按照“滚动更新”的方式,完成对 Deployment 的降级操作。不过,你可能已经想到了一个问题:我们对 Deployment 进行的每一次更新操作,都会生成一个新的 ReplicaSet 对象,是不是有些多余,甚至浪费资源呢?

没错。所以,Kubernetes 项目还提供了一个指令,使得我们对 Deployment 的多次更新操作,最后 只生成一个 ReplicaSet。

具体的做法是,在更新 Deployment 前,你要先执行一条 kubectl rollout pause 指令。它的用法如下所示:

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

这个 kubectl rollout pause 的作用,是让这个 Deployment 进入了一个“暂停”状态。

所以接下来,你就可以随意使用 kubectl edit 或者 kubectl set image 指令,修改这个 Deployment 的内容了。由于此时 Deployment 正处于“暂停”状态,所以我们对 Deployment 的所有修改,都不会触发新的“滚动更新”,也不会创建新的 ReplicaSet。而等到我们对 Deployment 修改操作都完成之后,只需要再执行一条 kubectl rollout resume 指令,就可以把这个 Deployment“恢复”回来,如下所示:

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

在这个 kubectl rollout resume 指令执行之前,在 kubectl rollout pause 指令之后的这段时间里,我们对 Deployment 进行的所有修改,最后只会触发一次“滚动更新”。当然,我们可以通过检查 ReplicaSet 状态的变化,来验证一下 kubectl rollout pause 和 kubectl rollout resume 指令的执行效果,如下所示:

$ kubectl get rs
NAME               DESIRED   CURRENT   READY     AGE
nginx云原生 | Kubernetes篇深入了解Deployment

金丝雀发布(Cannary Release)

金丝雀发布(Cannary Release)

金丝雀发布(Cannary Release)

金丝雀发布(Cannary Release)

15-Kubernetes-Pod控制器详解-Deployment(Deploy)