0%

控制器-Deployment

控制器-Deployment

一、Deployment 概述

英文 英文简称 中文
Pod Pod 容器组
Controller Controller 控制器
ReplicaSet ReplicaSet 副本集
Deployment Deployment 部署

Deployment 是最常用的用于部署无状态服务的方式。Deployment 控制器使得您能够以声明的方式更新 Pod(容器组)和 ReplicaSet(副本集)。

声明式配置

声明的方式是相对于非声明方式而言的。例如,以滚动更新为例,假设有 3 个容器组,现需要将他们的容器镜像更新为新的版本。

  • 非声明的方式,需要手动逐步执行以下过程:
    • 使用 kubectl 创建一个新版本镜像的容器组
    • 使用 kubectl 观察新建容器组的状态
    • 新建容器组的状态就绪以后,使用 kubectl 删除一个旧的容器组
    • 重复执行上述过程,直到所有容器组都已经替换为新版本镜像的容器组
  • 声明的方式,只需要执行:
    • 使用 kubectl 更新 Deployment 定义中 spec.template.spec.containers[].image 字段

二、创建 Deployment

下面的 yaml 文件定义了一个 Deployment,该 Deployment 将创建一个有 3 个 nginx Pod 副本的 ReplicaSet(副本集):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 的 Deployment(部署),名称由 .metadata.name 字段指定
  • 该 Deployment 将创建 3 个 Pod 副本,副本数量由 .spec.replicas 字段指定
  • .spec.selector 字段指定了 Deployment 如何找到由它管理的 Pod。此案例中,我们使用了 Pod template 中定义的一个标签(app: nginx)。对于极少数的情况,这个字段也可以定义更加复杂的规则
  • .template 字段包含了如下字段:
    • .template.metadata.labels 字段,指定了 Pod 的标签(app: nginx)
    • .template.spec.containers[].image 字段,表明该 Pod 运行一个容器 nginx:1.7.9
    • .template.spec.containers[].name 字段,表明该容器的名字是 nginx

注意:

必须为 Deployment 中的 .spec.selector.template.metadata.labels 定义一个合适的标签(这个例子中的标签是 app: nginx)。请不要使用与任何其他控制器(其他 Deployment / StatefulSet 等)相同的 .spec.selector.template.metadata.labels。否则可能发生冲突。

1.执行命令以创建 Deployment, 可以为该命令增加 –record 选项, 这样可以回顾某一个 Deployment 版本变化的原因

1
2
[root@k8s-master 0402]# kubectl apply -f deploy.yaml --record 
deployment.apps/nginx-deployment configured

2.查看 Deployment 的创建情况

1
2
3
4
[root@k8s-master 0402]# kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp 2/2 2 2 2d1h
nginx-deployment 2/3 3 2 11m

字段含义

字段名称 说明
NAME Deployment name
DESIRED Deployment 期望的 Pod 副本数,即 Deployment 中 .spec.replicas 字段指定的数值。该数值是“期望”值
CURRENT 当前有多少个 Pod 副本数在运行
UP-TO-DATE Deployment 中,符合当前 Pod Template 定义的 Pod 数量
AVAILABLE 当前对用户可用的 Pod 副本数
AGE Deployment 部署以来到现在的时长

3.查看该 Deployment 创建的 ReplicaSet(rs),执行命令 kubectl get rs,输出结果如下所示:

1
2
3
4
[root@k8s-master 0402]# kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp-57c9b8fc4 2 2 2 2d1h
nginx-deployment-54f57cf6bf 3 3 2 26m

4.查看 Pod 的标签,执行命令 kubectl get pods --show-labels,输出结果如下所示:

1
2
3
4
5
6
7
8
9
[root@k8s-master 0402]# kubectl get pods --show-labels 
NAME READY STATUS RESTARTS AGE LABELS
init-demo 1/1 Running 0 42h <none>
lifecycle-demo 1/1 Running 0 4d20h <none>
myapp-57c9b8fc4-b4cqb 1/1 Running 0 2d app=myapp,pod-template-hash=57c9b8fc4
myapp-57c9b8fc4-xtcwv 1/1 Running 0 2d1h app=myapp,pod-template-hash=57c9b8fc4
nginx-deployment-54f57cf6bf-4kl6r 1/1 Running 0 26m app=nginx,pod-template-hash=54f57cf6bf
nginx-deployment-54f57cf6bf-7zc2j 1/1 Running 0 26m app=nginx,pod-template-hash=54f57cf6bf
nginx-deployment-54f57cf6bf-jnzkj 0/1 ImagePullBackOff 0 26m app=nginx,pod-template-hash=54f57cf6bf

三、更新 Deployment

使用下述步骤更新您的 Deployment

方式一:

  1. 执行以下命令,将容器镜像从 nginx:1.7.9 更新到 nginx:1.9.1
1
2
3
[root@k8s-master 0402]# kubectl --record deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1
deployment.apps/nginx-deployment image updated
deployment.apps/nginx-deployment image updated

方式二:

使用 edit 该 Deployment,并将 .spec.template.spec.containers[0].imagenginx:1.7.9 修改为 nginx:1.9.1

1
2
[root@k8s-master 0402]# kubectl edit deployment nginx-deployment
deployment.apps/nginx-deployment edited
  • 如果想要修改这些新的 Pod,只需要再次修改 Deployment 的 Pod template。
  • Deployment 将确保更新过程中,任意时刻只有一定数量的 Pod 被关闭。默认情况下,Deployment 确保至少 .spec.replicas 的 75% 的 Pod 保持可用(25% max unavailable)
  • Deployment 将确保更新过程中,任意时刻只有一定数量的 Pod 被创建。默认情况下,Deployment 确保最多 .spec.replicas 的 25% 的 Pod 被创建(25% max surge)

Deployment Controller 先创建一个新 Pod,然后删除一个旧 Pod,然后再创建新的,如此循环,直到全部更新。Deployment Controller 不会 kill 旧的 Pod,除非足够数量的新 Pod 已经就绪,Deployment Controller 也不会创新新 Pod 直到足够数量的旧 Pod 已经被 kill。这个过程将确保更新过程中,任意时刻,最少 2 个 / 最多 4 个 Pod 可用。

覆盖更新 Rollover (更新过程中再更新)

每创建一个 Deployment,Deployment Controller 都为其创建一个 ReplicaSet,并设定其副本数为期望的 Pod 数( .spec.replicas 字段)。如果 Deployment 被更新,旧的 ReplicaSet 将被 Scale down,新建的 ReplicaSet 将被 Scale up;直到最后新旧两个 ReplicaSet,一个副本数为 .spec.replias,另一个副本数为 0。这个过程称为 rollout。

当 Deployment 的 rollout 正在进行中的时候,如果再次更新 Deployment 的信息,此时 Deployment 将再创建一个新的 ReplicaSet 并开始将其 scale up,将先前正在 scale up 的 ReplicaSet 也作为一个旧的 ReplicaSet,并开始将其 scale down。

例如:

  • 假设创建了一个 Deployment 有 5 个 nginx:1.7.9 的副本;
  • 立刻更新该 Deployment 使得其 .spec.replicas 为 5,容器镜像为 nginx:1.9.1,而此时只有 3 个 nginx:1.7.9 的副本已创建;
  • 此时,Deployment Controller 将立刻开始 kill 已经创建的 3 个 nginx:1.7.9 的 Pod,并开始创建 nginx:1.9.1 的 Pod。Deployment Controller 不会等到 5 个 nginx:1.7.9 的 Pod 都创建完之后在开始新的更新

四、回滚 Deployment

模拟更新错误

  • 假设您在更新 Deployment 的时候,犯了一个拼写错误,将 nginx:1.9.1 写成了 nginx:1.91

    1
    2
    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.91 --record=true
    deployment.apps/nginx-deployment image updated
  • 该更新将卡住,执行命令 kubectl rollout status deployment.v1.apps/nginx-deployment 检查其状态,输出结果如下所示:

    1
    Waiting for rollout to finish: 1 out of 3 new replicas have been updated...

检查Deployment的更新历史

1
2
3
4
5
[root@k8s-master 0402]# kubectl rollout history deployment nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
2 kubectl deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
3 kubectl deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true

执行命令 kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2,查看 revision(版本)的详细信息,输出结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master 0402]# kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
Labels: app=nginx
pod-template-hash=56f8998dbc
Annotations: kubernetes.io/change-cause:
kubectl deployment.apps/nginx-deployment set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record=true
Containers:
nginx:
Image: nginx:1.9.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>

回滚到前一个 revision(版本)

下面的步骤可将 Deployment 从当前版本回滚到前一个版本(version 2)

执行命令 kubectl rollout undo deployment.v1.apps/nginx-deployment 将当前版本回滚到前一个版本,输出结果如下所示:

1
deployment.apps/nginx-deployment

或者,也可以使用 --to-revision 选项回滚到前面的某一个指定版本,执行如下命令:

1
kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2
  • 此时,Deployment 已经被回滚到前一个稳定版本。可以看到 Deployment Controller 为该 Deployment 产生了 DeploymentRollback event。
  • 执行命令 kubectl get deployment nginx-deployment,检查该回滚是否成功,Deployment 是否按预期的运行。

五、伸缩 Deployment

执行伸缩

  • 执行命令 kubectl scale deployment nginx-deployment --replicas=5,可以伸缩 Deployment,输出结果如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master 0402]# kubectl scale deployment nginx-deployment --replicas=5
deployment.apps/nginx-deployment scaled

[root@k8s-master 0402]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 4/5 5 4 5h59m

[root@k8s-master 0402]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-54f57cf6bf-4kl6r 1/1 Running 0 5h59m
nginx-deployment-54f57cf6bf-7zc2j 1/1 Running 0 5h59m
nginx-deployment-54f57cf6bf-lch2d 1/1 Running 0 35s
nginx-deployment-54f57cf6bf-mzrzq 1/1 Running 0 35s
nginx-deployment-54f57cf6bf-pmgj8 0/1 ImagePullBackOff 0 3h22m

如果集群启用了自动伸缩,执行以下命令,就可以基于 CPU 的利用率在一个最大和最小的区间自动伸缩您的 Deployment:

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

按比例伸缩

例如,假设已经运行了一个 10 副本数的 Deployment,其 maxSurge=3, maxUnavailable=2。

  • 执行命令 kubectl scale deployment.v1.apps/nginx-deployment --replicas=15,将 Deployment 的 replicas 调整到 15。此时,Deployment Controller 需要决定如何分配新增的 5 个 Pod 副本。根据“按比例伸缩”的原则:
    • 更大比例的新 Pod 数被分配到副本数最多的 ReplicaSet
    • 更小比例的新 Pod 数被分配到副本数最少的 ReplicaSet
    • 如果还有剩余的新 Pod 数未分配,则将被增加到副本数最多的 ReplicaSet
    • 副本数为 0 的 ReplicaSet,scale up 之后,副本数仍然为 0
  • 在本例中,3 个新副本被添加到旧的 ReplicaSet,2个新副本被添加到新的 ReplicaSet。如果新的副本都达到就绪状态,滚动更新过程最终会将所有的副本数添加放到新 ReplicaSet。

六、暂停和继续 Deployment

可以先暂停 Deployment,然后再触发一个或多个更新,最后再继续(resume)该 Deployment。这种做法使得在暂停和继续中间对 Deployment 做多次更新,而无需触发不必要的滚动更新。

执行命令 kubectl rollout pause deployment.v1.apps/nginx-deployment 暂停 Deployment,输出结果如下所示:

1
2
[root@k8s-master 0402]# kubectl rollout pause deployment nginx-deployment 
deployment.apps/nginx-deployment paused

执行命令 kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.17.9,更新 Deployment 的容器镜像,输出结果如下所示:

1
2
[root@k8s-master 0402]# kubectl set image deployment/nginx-deployment nginx=nginx:1.17.9
deployment.apps/nginx-deployment image updated

执行命令 kubectl rollout resume deployment/nginx-deployment,继续(resume)该 Deployment,可使前面所有的变更一次性生效,输出结果如下所示:

1
2
[root@k8s-master 0402]# kubectl rollout resume deployment nginx-deployment 
deployment.apps/nginx-deployment resumed

执行命令 kubectl get rs -o wide 查看 ResultSet 的最终状态,输出结果如下所示:

1
2
3
4
5
6
[root@k8s-master 0402]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp-57c9b8fc4 2 2 2 3d nginx nginx app=myapp,pod-template-hash=57c9b8fc4
nginx-deployment-54f57cf6bf 1 1 1 23h nginx nginx:1.7.9 app=nginx,pod-template-hash=54f57cf6bf
nginx-deployment-56f8998dbc 0 0 0 23h nginx nginx:1.9.1 app=nginx,pod-template-hash=56f8998dbc
nginx-deployment-6654964744 5 5 3 5m8s nginx nginx:1.17.9 app=nginx,pod-template-hash=6654964744

不能回滚(rollback)一个已暂停的 Deployment,除非继续(resume)该 Deployment。

七、查看Deployment的状态

Progressing 状态

当如下任何一个任务正在执行时,Kubernete 将 Deployment 的状态标记为 progressing

  • Deployment 创建了一个新的 ReplicaSet
  • Deployment 正在 scale up 其最新的 ReplicaSet
  • Deployment 正在 scale down 其旧的 ReplicaSet
  • 新的 Pod 变为 就绪(ready)可用(available)

可以使用命令 kubectl rollout status 监控 Deployment 滚动更新的过程

Complete 状态

如果 Deployment 符合以下条件,Kubernetes 将其状态标记为 complete

  • 该 Deployment 中的所有 Pod 副本都已经被更新到指定的最新版本
  • 该 Deployment 中的所有 Pod 副本都处于 可用(available) 状态
  • 该 Deployment 中没有旧的 ReplicaSet 正在运行

可以执行命令 kubectl rollout status 检查 Deployment 是否已经处于 complete 状态。如果是,则该命令的退出码为 0。

例如,执行命令 kubectl rollout status deployment.v1.apps/nginx-deployment,输出结果如下所示:

Failed 状态

Deployment 在更新其最新的 ReplicaSet 时,可能卡住而不能达到 complete 状态。如下原因都可能导致此现象发生:

  • 集群资源不够
  • 就绪检查(readiness probe)失败
  • 镜像抓取失败
  • 权限不够
  • 资源限制
  • 应用程序的配置错误导致启动失败

指定 Deployment 定义中的 .spec.progressDeadlineSeconds 字段,Deployment Controller 在等待指定的时长后,将 Deployment 的标记为处理失败。

操作处于 Failed 状态的 Deployment

可以针对 Failed 状态下的 Deployment 执行任何适用于 Deployment 的指令,例如:

  • scale up / scale down
  • 回滚到前一个版本
  • 暂停(pause)Deployment,以对 Deployment 的 Pod template 执行多处更新

八、清除策略

通过 Deployment 中 .spec.revisionHistoryLimit 字段,可指定为该 Deployment 保留多少个旧的 ReplicaSet。超出该数字的将被在后台进行垃圾回收。该字段的默认值是 10。

如果该字段被设为 0,Kubernetes 将清理掉该 Deployment 的所有历史版本(revision),将无法对该 Deployment 执行回滚操作 kubectl rollout undo

九、部署策略

通过 Deployment 中 .spec.strategy 字段,可以指定使用 滚动更新 RollingUpdate 的部署策略还是使用 重新创建 Recreate 的部署策略

其中字段的含义如下:

字段名称 可选值 字段描述
类型 滚动更新 重新创建 如果选择重新创建,Deployment将先删除原有副本集中的所有 Pod,然后再创建新的副本集和新的 Pod。如此,更新过程中将出现一段应用程序不可用的情况;
最大超出副本数 数字或百分比 滚动更新过程中,可以超出期望副本数的最大值。 该取值可以是一个绝对值(例如:5),也可以是一个相对于期望副本数的百分比(例如:10%); 如果填写百分比,则以期望副本数乘以该百分比后向上取整的方式计算对应的绝对值; 当最大超出副本数 maxUnavailable 为 0 时,此数值不能为 0;默认值为 25%。 例如:假设此值被设定为 30%,当滚动更新开始时,新的副本集(ReplicaSet)可以立刻扩容, 但是旧 Pod 和新 Pod 的总数不超过 Deployment 期待副本数(spec.repilcas)的 130%。 一旦旧 Pod 被终止后,新的副本集可以进一步扩容,但是整个滚动更新过程中,新旧 Pod 的总 数不超过 Deployment 期待副本数(spec.repilcas)的 130%。
最大不可用副本数 数字或百分比 滚动更新过程中,不可用副本数的最大值。 该取值可以是一个绝对值(例如:5),也可以是一个相对于期望副本数的百分比(例如:10%); 如果填写百分比,则以期望副本数乘以该百分比后向下取整的方式计算对应的绝对值; 当最大超出副本数 maxSurge 为 0 时,此数值不能为 0;默认值为 25%; 例如:假设此值被设定为 30%,当滚动更新开始时,旧的副本集(ReplicaSet)可以缩容到期望 副本数的 70%;在新副本集扩容的过程中,一旦新的 Pod 已就绪,旧的副本集可以进一步缩容, 整个滚动更新过程中,确保新旧就绪副本数之和不低于期望副本数的 70%。

十、金丝雀发布(灰度发布)

可以使用 Deployment 将最新的应用程序版本发布给一部分用户(或服务器),为每个版本创建一个 Deployment,此时,应用程序的新旧两个版本都可以同时获得生产上的流量。

部署第一个版本

第一个版本的 Deployment 包含了 3 个Pod副本,Service 通过 label selector app: nginx 选择对应的 Pod,nginx 的标签为 1.7.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- name: nginx-port
protocol: TCP
port: 80
nodePort: 32600
targetPort: 80
type: NodePort

假设此时想要发布新的版本 nginx 1.8.0,可以创建第二个 Deployment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-canary
labels:
app: nginx
track: canary
spec:
replicas: 1
selector:
matchLabels:
app: nginx
track: canary
template:
metadata:
labels:
app: nginx
track: canary
spec:
containers:
- name: nginx
image: nginx:1.8.0
  • 因为 Service 的LabelSelector 是 app: nginx,由 nginx-deploymentnginx-deployment-canary 创建的 Pod 都带有标签 app: nginx,所以,Service 的流量将会在两个 release 之间分配
  • 在新旧版本之间,流量分配的比例为两个版本副本数的比例,此处为 1:3

局限性

按照 Kubernetes 默认支持的这种方式进行金丝雀发布,有一定的局限性:

  • 不能根据用户注册时间、地区等请求中的内容属性进行流量分配
  • 同一个用户如果多次调用该 Service,有可能第一次请求到了旧版本的 Pod,第二次请求到了新版本的 Pod

在 Kubernetes 中不能解决上述局限性的原因是:Kubernetes Service 只在 TCP 层面解决负载均衡的问题,并不对请求响应的消息内容做任何解析和识别。