目录

Kubernetes Deployment 源码分析(一)

概述

Deployment 是最常用的 Kubernetes 原生 Workload 资源之一,我们一开始尝试使用 Kubernetes 的时候大概率就是从运行一个 Deployment 类型的工作负载开始的。今天开始我们计划分成几讲来从 Deployment 的特性介绍、源码分析等方面深度剖析 Deployment 资源和其背后的 Deployment 控制器。

Deployment 的基础特性大家基本都熟悉,所以本文我们不计划赘述 Deployment 的所有功能细节,而是从滚动更新等不算太基础的特性入手,看下 Deployment 支持哪些玩法,为后面分析源码做准备。

《Kubernetes Deployment Controller 源码分析》分为两讲:

Deployment 基础

我们创建一个简单的 Deployment,然后看下一些小细节。

创建 Deployment

以运行 nginx 为例,我们可以通过 Deployment 来拉起一个 3 副本的 nginx 负载:nginx-dp

  • nginx-dp.yaml
 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-dp
  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

通过 kubectl create -f nginx-dp.yaml 我们可以创建这个 Deployment 资源。

1
2
3
4
5
6
7
8
# kubectl create -f nginx-dp.yaml
deployment.apps/nginx-dp created
# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
nginx-dp   1/3     3            1           3s
# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
nginx-dp   3/3     3            3           10s

等一会,就可以通过 kubectl get deploy 命令看到所有 Pod 都起来了,这里关注下输出字段含义(NAME 和 AGE 就不用说了):

  • UP-TO-DATE:多少副本已经更新到期望状态了
  • AVAILABLE:多少副本已经可以提供服务了
  • READY:可以提供服务的副本数/期望副本数

ReplicaSet

  • 查询 ReplicaSet
1
2
3
# kubectl get rs --selector=app=nginx
NAME                  DESIRED   CURRENT   READY   AGE
nginx-dp-66b6c48dd5   3         3         3       9m54s

前面创建了 Deployment 之后可以看到集群里多了一个 ReplicaSet 资源,也就是说 Deployment 管理的其实是 ReplicaSet 而不是直接管理 Pod,我们继续看下这个 ReplicaSet 的定义来验证下这个想法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# kubectl get rs nginx-dp-66b6c48dd5 -o yaml
apiVersion: apps/v1
kind: ReplicaSet
// ……
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: Deployment
    name: nginx-dp
    uid: 97736b65-0171-4916-bb18-feccc343ac14
  resourceVersion: "1099157"
  uid: 83ac5660-28eb-4d40-beb1-cb5ceb6928b6
// ……

这里可以看到这个 ReplicaSet 属于 Deployment 类型的 nginx-dp 资源。同样的方法可以看到对应 Pod 是属于 ReplicaSet 管理的。

到这里,我们可以猜下 Deployment Controller 的实现原理,大概可以想到其通过管理 ReplicaSet 的生命周期,借助 ReplicaSet Controller 提供的能力间接完成了 Pod 生命周期的管理;另外可以通过创建多个 ReplicaSet 资源,控制其副本数来实现滚动更新和回滚等操作。这样 Deployment Controller 的实现逻辑就相对“高层”了。

滚动更新

  • 通过 kubectl set 命令来更新镜像:
1
2
# kubectl set image deployment/nginx-dp nginx=nginx:1.16.1
deployment.apps/nginx-dp image updated
  • 查看 Event
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# kubectl describe deploy nginx-dp
// ……

Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  26m   deployment-controller  Scaled up replica set nginx-dp-66b6c48dd5 to 3
  Normal  ScalingReplicaSet  88s   deployment-controller  Scaled up replica set nginx-dp-559d658b74 to 1
  Normal  ScalingReplicaSet  87s   deployment-controller  Scaled down replica set nginx-dp-66b6c48dd5 to 2
  Normal  ScalingReplicaSet  87s   deployment-controller  Scaled up replica set nginx-dp-559d658b74 to 2
  Normal  ScalingReplicaSet  86s   deployment-controller  Scaled down replica set nginx-dp-66b6c48dd5 to 1
  Normal  ScalingReplicaSet  86s   deployment-controller  Scaled up replica set nginx-dp-559d658b74 to 3
  Normal  ScalingReplicaSet  84s   deployment-controller  Scaled down replica set nginx-dp-66b6c48dd5 to 0

从 Event 里可以看到 deployment-controller 通过调整 ReplicaSet 资源 nginx-dp-66b6c48dd5 和 nginx-dp-559d658b74 的副本数完成了这次滚动更新,先看下这两个 ReplicaSet:

1
2
3
4
# kubectl get rs --selector=app=nginx
NAME                  DESIRED   CURRENT   READY   AGE
nginx-dp-559d658b74   3         3         3       134m
nginx-dp-66b6c48dd5   0         0         0       159m

可以看到这时候新增了一个 nginx-dp-559d658b74,副本数是 3,同时老的 nginx-dp-66b6c48dd5 变成了 0 副本。这个过程大概是这样:

  1. nginx-dp-66b6c48dd5 to 3 / replica set nginx-dp-559d658b74 to 1 -> 新 rs 增加一个副本到 1;合计 4 副本
  2. Scaled down replica set nginx-dp-66b6c48dd5 to 2 -> 老 rs 减少一个副本到 2;合计 3 副本
  3. Scaled up replica set nginx-dp-559d658b74 to 2 -> 新 rs 增加一个副本到 2;合计 4 副本
  4. Scaled down replica set nginx-dp-66b6c48dd5 to 1 -> 老 rs 减少一个副本到 1;合计 3 副本
  5. Scaled up replica set nginx-dp-559d658b74 to 3 -> 新 rs 增加一个副本到 3;合计 4 副本
  6. Scaled down replica set nginx-dp-66b6c48dd5 to 0 -> 老 rs 减少一个副本到 0;合计 3 副本

失败回滚

历史版本

先看下怎么查询更新历史:

1
2
3
4
5
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

这里可以看到一个细节,CHANGE-CAUSE 是空的,这个字段其实是从 kubernetes.io/change-cause 注解中拿的,我们加个注解试一下:

1
kubectl annotate deployment/nginx-dp kubernetes.io/change-cause="image updated to 1.16.1"

再查一次:

1
2
3
4
5
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
1         <none>
2         image updated to 1.16.1

第一个版本怎么办呢?这里大概可以猜到要存储多个版本的 CHANGE-CAUSE 信息,这个注解应该是用的 ReplicaSet 里的,所以我们尝试这样补充第一个版本的注解:

1
kubectl annotate rs/nginx-dp-66b6c48dd5 kubernetes.io/change-cause="nginx deployment created"

再查一次:

1
2
3
4
5
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
1         nginx deployment created
2         image updated to 1.16.1

现在就比较和谐了,需要指定版本回滚的时候不迷路。

回滚

  • 设置一个不存在的镜像版本来模拟更新失败场景:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# kubectl set image deployment/nginx-dp nginx=nginx:1.161
deployment.apps/nginx-dp image updated
# kubectl get rs --selector=app=nginx
NAME                  DESIRED   CURRENT   READY   AGE
nginx-dp-559d658b74   3         3         3       168m
nginx-dp-66b6c48dd5   0         0         0       3h13m
nginx-dp-66bc5d6c8    1         1         0       6s
# kubectl get pod --selector=app=nginx
NAME                        READY   STATUS             RESTARTS   AGE
nginx-dp-559d658b74-l4bq7   1/1     Running            0          170m
nginx-dp-559d658b74-qhh8m   1/1     Running            0          170m
nginx-dp-559d658b74-vbtl5   1/1     Running            0          170m
nginx-dp-66bc5d6c8-tl848    0/1     ImagePullBackOff   0          2m2s
  • 设置个注解:
1
2
3
4
5
6
7
8
# kubectl annotate deployment/nginx-dp kubernetes.io/change-cause="image updated to 1.161"
deployment.apps/nginx-dp annotated
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
1         nginx deployment created
2         image updated to 1.16.1
3         image updated to 1.161
  • 回滚到 revision 2:
1
2
3
4
5
6
7
8
# kubectl rollout undo deployment/nginx-dp
deployment.apps/nginx-dp rolled back
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
1         nginx deployment created
3         image updated to 1.161
4         image updated to 1.16.1

这时候版本 2 变成了最新的版本:4

  • 查看某个版本的详细配置:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# kubectl rollout history deployments/nginx-dp --revision=1
deployment.apps/nginx-dp with revision #1
Pod Template:
  Labels:	app=nginx
	pod-template-hash=66b6c48dd5
  Annotations:	kubernetes.io/change-cause: nginx deployment created
  Containers:
   nginx:
    Image:	nginx:1.14.2
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>
  • 指定版本回滚:
1
2
3
4
5
6
7
8
# kubectl rollout undo deployment/nginx-dp --to-revision=1
deployment.apps/nginx-dp rolled back
# kubectl rollout history deployments/nginx-dp
deployment.apps/nginx-dp
REVISION  CHANGE-CAUSE
3         image updated to 1.161
4         image updated to 1.16.1
5         nginx deployment created

其他特性

最后我们看下 Deployment 类型 spec 的全部属性:

  • minReadySeconds:默认值为 0,表示一个 pod reday 之后多长时间可以提供服务;换句话说配置成1就是 pod ready 之后 1s 才对外提供服务;
  • paused:挂起;
  • progressDeadlineSeconds:默认 600,表示处理一个 Deployment 任务的超时时间,比如 10 分钟到了还没有升级成功,则标记为 failed 状态;
  • replicas:副本数;
  • revisionHistoryLimit:默认是 10,表示保留的历史版本数量;
  • selector:标签选择器;
  • strategy:表示 Deployment 更新 pod 时的替换策略;
  • template:Pod 模板;

这里的 strategy 有两个属性,分别是:typerollingUpdate。type 可选值是:“Recreate” 和 “RollingUpdate”,默认为 “RollingUpdate”。strategy.rollingUpdate 有两个属性:

  • maxSurge: 表示滚动更新的时候最多可以比期望副本数多几个,数字或者百分比配置都行;比如 1 表示更新过程中最多同时新增 1 个副本,然后等一个老副本删掉之后才能继续增加 1 个新副本;百分比计算的时候向上取整;
  • maxUnavailable:表示滚动更新的时候可以有多少副本不可用,同样是数字或者百分比配置;比如期望副本数是 3,1 则表示最多删除副本到剩下 2,然后要等新副本创建才能继续删除;百分比计算的时候向下取整;

小结

本文我们的目的是知道 Deployment 的全部特性,进而为后面的源码分析做准备。在这个过程中我们没有赘述 Deployment 的基础特性,而是主要介绍“滚动更新”和“回滚”等主要功能,另外简单过一下 Deployment 的 spec 包含的全量配置项,从而心中有个概念,知道 Deployment 的能力边界在那里,从而后面看源码时更有针对性。