0%

学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!!

关于 Kubernetes 系列其他文章的传送门

  1. 《 部署 Kubernetes 也能如此简单吗? 》

  2. 《 初识 k8s,新时代的宠儿 》

  3. 《 深度学习 Pod 》

  4. 《 学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!! 》     您当前所在位置

  5. 《 k8s 中的网络入口 – Service 资源管理 》

  6. 《 k8s 中的 7 层调度 – Ingress 》

  7. 《 k8s 中的存储系统 – Volumes 》

  8. 《 k8s 中强大的配置中心 – ConfigMap 》

  9. 《 k8s 中的部署神器 – Statefulset 控制器 》

  10. 《 k8s 中的安全守护神 – 你不知道不代表它不在 》

  11. 《 k8s 中的图形接口 – Dashboard 》

  12. 《 k8s 中的网络插件 – flannel 和 Calico 》

  13. 《 k8s 中的调度器 – 亲和性、污点和容忍性 》

  14. 《 k8s 中对资源的监控 – 资源指标、Prometheus、弹性伸缩器 HPA 》

  15. 《 k8s 中的云原生应用管理利器 – Helm 》

  16. 《 理解 k8s 高可用,让你的集群稳如泰山 》



logo

一、关于 k8s 控制器

Controllers 的工作任务就是通过内部的和解循环,来确保一个对象存在且吻合于用户所期望的状态。

k8s 内键了 4、50 种 Controller,为了统一管理,都被打包在 Kubernetes-controller-manager 中,这是 k8s 控制平面中核心的守护进程。

Kubernetes-controller-manager 在启动 k8s 时可以使用 --controllers= 参数来控制要加载哪些控制器,可用参数有:

  1. *:启用所有控制器,除了如下两个;

    • bootstrapsignertokencleaner:默认不加载的控制器;
  2. -NAME:可使用 - 加上名字指定不加载哪个控制器;

而如果使用 kubeadm 部署的 k8s,controller-manager 会被运行为一个名为 kube-controller-manager-master.monster.com 的 Pod。

可以在 controller-manager 的配置文件 ↓ 中看到:

  • /etc/kubernetes/manifests/kebe-controller-manager.yaml
  • 默认使用 --controllers=*,bootstrapsigner,tokencleaner 参数把所有的控制器都加载。

此配置文件修改后,不需要人工介入,k8s 会在很短的时间内把旧的 Pod 干掉,按照新的配置文件重建一个 Pod。

二、Pod 控制器的类型

控制器按照应用程序可划分为多种类型:

  • 守护进程型:
    • 无状态:
      • 非系统级:DeploymentReplicaSet;如 Nginx、Apache;
      • 系统级:DaemonSet;需要在每个 node 节点上都运行一份,但只能运行一份的程序,如 ZABBIX;
    • 有状态:StatefulSet
  • 非守护进程:
    • Job:专门控制作业的控制器;比如备份一次;
    • CronJob:如果作业需要周期性运行则使用此控制器;比如每周备份一次;

1. ReplicaSet 控制器

ReplicaSet 是专门运行无状态类型应用的 Pod 控制器。用于确保在任何时间,必须有精确数量的 Pod 副本处于运行状态。而且所有 Pod 被无区别对待,没有任何个性,任何一个 Pod 出现故障,都可以被其他新的 Pod 取代。

控制器通过标签选择器来过滤出 Pod,并核对数量和预期的数量是否一致。如果不一致,则根据创建 Pod 时使用的模板来创建。

控制器创建 Pod 时,会根据 控制器模板 创建,并附加一个随机的名字,但容器内部所使用的镜像和配置等应该都是相近的。

使用 kubectl get rs 可以查看当前运行的属于 ReplicaSet 类型的 Pod。

查看 ReplicaSet 的 API 参数:

1
~]# kubectl explain rs.spec
  • replicas:期望的 Pod 副本数;
  • selector:标签选择器;
    • matchLabels:可以直接指明标签来做匹配;可以指定多个,当指定多个时为“与”关系;
    • matchExpressions:使用表达式;
      • key:指定要匹配的 Key;
      • operator:指定匹配时的操作符;
      • values:指定匹配值;
  • template:Pod 模板;
    • metadata:定义元数据,一般不需要,否则会导致重名;
    • spec:定义 Pod 的 spec;

示例:创建 ReplicaSet 类型 Pod 控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
~]# vim rs-example.yaml 

apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp-pod # 标签选择器匹配标签为 app: myapp-pod 的 Pod,一定要和下边模板中定义的标签一致。
template:
metadata:
labels:
app: myapp-pod # 一定要和上边标签选择器中定义的匹配规则中的值一致。
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80

启用 Pod 控制器:

1
~]# kubectl apply -f rs-example.yaml

查看刚才创建的 rs 类型的 Pod 控制器:

1
2
3
~]# kubectl get rs  
NAME DESIRED CURRENT READY AGE
myapp-rs 2 2 2 23s

也可以查看控制器的详细信息:

1
~]# kubectl describe rs myapp-rs

注意:控制器只根据标签进行 Pod 的编排,一定要精心规划,确保标签不要重复。另外在定义标签选择器时建议使用更苛刻一些的匹配条件,来帮助精准匹配。

更改 Pod 控制器下的运行的 Pod 副本数:

1
kubectl scale --replicas=3 rs myapp-rs -n default

2. Deployment 控制器

发布

如果想对程序进行版本升级,使用 ReplicaSet 控制器不能自由的控制升级方式,而且还需要手动去进行。

而 k8s 提供的 Deployment 控制器,则可以自由定义升级方式。它通过控制 ReplicaSets,让 ReplicaSets 去控制 Pods 的方式,来自动完成事先定义好的发布规则,比如灰度发布、蓝绿发布甚至是金丝雀发布等。

Deployment 控制器以配置文件内 rs 模板内容的 hash 值来判断是否属于同一版本;

回滚

Deployment 保留着多个过去的 rs,如果发布后出现 BUG 需要回滚,还可以回滚。当前的 k8s 版本默认保留着所有的历史 rs。

Deployment

创建 Deployment 类型的 Pod 控制器

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
~]# vim deploy-nginx.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: default
spec:
replicas: 3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.10-alpine
ports:
- containerPort: 80
name: http
readinessProbe:
periodSeconds: 1
httpGet:
path: /
port: http

启用 Pod 控制器:

1
~]# kubectl apply -f deploy-nginx.yaml

查看刚刚创建的 Pod 控制器:

1
2
3
~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-nginx 3/3 3 3 78s

这个 deploy 类型的控制器会自动创建 rs 类型的控制器,名字的最后一段的是 Pod 模板的 hash 值:

1
2
3
~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deploy-nginx-5745bb45d7 3 3 3 2m30s

这个 rs 类型的控制器会去创建 Pod,由这个 rs 管理:

1
2
3
4
5
~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-nginx-5745bb45d7-dszvv 1/1 Running 0 6m35s
deploy-nginx-5745bb45d7-jshmw 1/1 Running 0 6m35s
deploy-nginx-5745bb45d7-pk7zm 1/1 Running 0 6m35s

发布、更新

定义更新策略的参数:

  • strategy:定义更新策略:
    • rollingUpdate:定义滚动升级时,副本数量的上下限,可以使用百分比定义;
      • maxSurge:滚动更新的时候,可以超过用户预期的副本数量,此值用来定义可以超过几个;
      • maxUnavailable:定义更新过程中,可以比预期的副本数量少几个;
  • type:定义更新类型:
    • rollingUpdate(默认):滚动升级;
    • recreate:重新创建。

现在编辑刚才的配置文件:

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
~]# vim deploy-nginx.yaml 

apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: default
spec:
replicas: 3
minReadySeconds: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.11.3-alpine # 把模板内镜像版本从 1.10-alpine 升级到 1.11.3-alpine
ports:
- containerPort: 80
name: http
readinessProbe:
periodSeconds: 1
httpGet:
path: /
port: http

如果只修改模板外的参数,比如副本数增加,k8s 只会把缺少的 Pod 补上,而不会动已有的 Pod。而像这样修改模板内部的参数,模板的 hash 值就会变化,那么 k8s 才会更新所有的 Pod;

启用 Pod 控制器,完成版本的发布:

1
~]# kubectl apply -f deploy-nginx.yaml --record

注意:上面创建时使用了 --record 参数,这样可以把这次创建时的命令记录下来,使用如下操作时,就能看见创建控制器时使用的命令了

1
2
3
4
5
~]# kubectl rollout  history deployment/deploy-nginx
deployment.apps/deploy-nginx
REVISION CHANGE-CAUSE
4 <none>
5 kubectl apply --filename=deploy-nginx.yaml --record=true

使用以下命令可以看到控制器创建 Pod 的过程:加一个新的,减一个旧的以此循环:

1
~]# kubectl get pods  -w

查看 Pod,看名字中的 hash 值已经和之前的不一样了,现在运行的 3 个 Pod 已经是新的 Pod 了:

1
2
3
4
5
~]# kubectl get pods   
NAME READY STATUS RESTARTS AGE
deploy-nginx-57bffc5ddc-mb69v 1/1 Running 0 2m47s
deploy-nginx-57bffc5ddc-nrjfg 1/1 Running 0 2m47s
deploy-nginx-57bffc5ddc-ttn8r 1/1 Running 0 2m11s

查看 rs 可以看到,旧版本的 rs 也保留着,只是不再控制任何 Pod 了。在 Deployment 内保留着所有过去的 rs,这样所有的发布版本就被保留了下来,需要回滚的时候只需要把旧的 rs 激活即可:

1
2
3
4
~]# kubectl get rs            # 看 IMAGES 可以看出一个是老版本,一个是新版本  ↓↓↓
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
deploy-nginx-5745bb45d7 0 0 0 12m nginx nginx:1.10-alpine app=nginx,pod-template-hash=5745bb45d7
deploy-nginx-57bffc5ddc 3 3 3 8m34s nginx nginx:1.11.3-alpine app=nginx,pod-template-hash=57bffc5ddc
另一种更新方法

直接更改指定控制器中指定容器的镜像版本:

1
2
                 # ↓ 控制器类型  # ↓ 控制器名  # ↓ 容器的镜像名
kubectl set image deployment deploy-nginx nginx=nginx:1.11.3-alpine

回滚之前的更新操作

查看滚动更新历史:

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

回滚到指定的一条:

  • --to-revision=0:可用此参数指定要回滚到哪个 ID,默认为 0 代表回滚至前一个;
1
2
~]# kubectl rollout undo deployment/deploy-nginx
deployment.apps/deploy-nginx rolled back

回滚过程中可以使用如下命令查看实时状态:

1
~]# kubectl rollout status deployment/deploy-nginx

金丝雀发布

在发布过程中暂停的参数:

  • pause:暂停
  • resume:继续

刚开始发布就暂停:

1
2
3
~]# kubectl set image deployment deploy-nginx nginx=nginx:1.11.3-alpine  &&  kubectl rollout pause deployment/deploy-nginx
deployment.apps/deploy-nginx image updated
deployment.apps/deploy-nginx paused

可以看到有两个新的,两个旧的:

1
2
3
4
5
6
~]# kubectl get pods        
NAME READY STATUS RESTARTS AGE
deploy-nginx-57bffc5ddc-848xs 1/1 Running 0 50s
deploy-nginx-57bffc5ddc-b5kb2 1/1 Running 0 50s
deploy-nginx-7d6b7b855-ckz6l 1/1 Running 0 2m16s
deploy-nginx-7d6b7b855-t4fmh 1/1 Running 0 2m16s

接下来如果出现问题,直接回滚就可以;如果没问题,就可以继续发布了:

1
kubectl rollout resume deployment/deploy-nginx

3. DaemonSet 控制器

用来在每个计算节点中运行且只运行一个 Pod,运行的数量和节点数一致,无法人为控制。

支持滚动发布,但有所不同的是滚动发布时只能减不能加,因为每个节点只能运行 1 个 Pod。

下面是部署一个 filebeat 的示例配置文件,此镜像需要向容器内传递几个变量:

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
~]# vim filebeat-ds.yaml 

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-ds
labels:
app: filebeat # 此标签是控制器标签,不是必须的
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
name: filebeat
spec:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.5-alpine
env: # 启动时向容器传递参数
- name: REDIS_HOST # 变量:告诉 filebeat 把收集的日志发送到哪个 Redis 服务器
value: db.ikubernetes.io:6379
- name: LOG_LEVEL
value: info

指定新镜像以更新程序版本:

1
kubectl set image ds/filebeat-ds filebeat=ikubernetes/filebeat:5.6.6-alpine

查看刚才创建的 ds 类型的控制器:

1
2
3
~]# kubectl get ds   
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
filebeat-ds 3 3 3 3 3 <none> 10m

节点选择器

使用节点选择器,实现只在指定的节点中运行 ds 类型的 Pod:

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
~]# vim filebeat-ds.yaml 

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-ds
labels:
app: filebeat
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
name: filebeat
spec:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.5-alpine
env:
- name: REDIS_HOST
value: db.ikubernetes.io:6379
- name: LOG_LEVEL
value: info
nodeSelector: # 定义一个节点选择器
logcollection: "on" # 自定义一个标签叫 logcollection,只有在节点标签中有此键,且值为 "on" 时才会调度至此节点

使用 kubectl apply -f filebeat-ds.yaml 创建后,可以看到因为当前所有节点都没有匹配的标签,所以没有 Pod 被创建:

1
2
3
4
5
6
~]# kubectl get deploy
No resources found in default namespace.

~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
filebeat-ds 0 0 0 0 0 logcollection=on 25m

现在只为其中一个节点添加这个自定义标签 logcollection

1
~]# kubectl label node node1.monster.com logcollection="on"

再次查看运行的 Pod,可以看到因为 node1 节点的标签,被节点选择器选中,所以在 node1 节点中创建了 Pod:

1
2
3
4
5
6
7
~]# kubectl get ds    
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
filebeat-ds 1 1 1 1 1 logcollection=on 35m

~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
filebeat-ds-79bbt 1/1 Running 0 17s 10.244.1.26 node1.monster.com <none> <none>

4. Job 控制器

可以使用以下方式定义重启策略,如果 Pod 中的所有容器意外停止了,是否要重启:

  • Always:默认策略,当容器退出时总是重启容器;
  • OnFailure:当容器退出时,从不重启;
  • Never:容器正常退出不会再重新启动,退出码非 0 时才重启容器。

单路作业示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~]# vim job-example.yaml 

apiVersion: batch/v1
kind: Job
metadata:
name: job-example
spec:
template:
metadata:
labels:
app: myjob
spec:
containers:
- name: myjob
image: alpine
command: ["/bin/sh", "-c", " sleep 10"]
restartPolicy: Never # 如果意外终止了,不会重启 Pod

10 秒钟后,此 Job 就运行完成了,可以看到状态为 Completed

1
2
3
4
5
6
7
~]# kubectl get job
NAME COMPLETIONS DURATION AGE
job-example 1/1 21s 24s

~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
job-example-fjqjv 0/1 Completed 0 75s

多路作业示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~]# vim job-multi.yaml 

apiVersion: batch/v1
kind: Job
metadata:
name: job-multi
spec:
completions: 5 # 定义要执行的任务总量。如果没指定并行度,就按照单路串行运行
parallelism: 2 # 定义并行度,也就是同时运行几个任务
template:
metadata:
labels:
app: myjob
spec:
containers:
- name: myjob
image: alpine
command: ["/bin/sh", "-c", "sleep 30"]
restartPolicy: Never

5. CronJob 控制器

CronJob 直接控制 Job,再由 Job 控制 Pod。

周期性计划任务和 Linux 中的 crontab 使用方法是一样的:

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
-> # vim cronjob-example.yaml 

apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-example
labels:
app: mycronjob
spec:
schedule: "*/2 * * * *" # 每两分钟执行一次
jobTemplate:
metadata:
labels:
app: mycronjob-jobs
spec:
parallelism: 2
template:
spec:
containers:
- name: myjob
image: alpine
command:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster; sleep 10
restartPolicy: OnFailure