- 什么是控制器
- 控制器类型
- ReplicationController 和 ReplicaSet
- Deployment
- DaemonSet
- Job
- CronJob
- StatefulSet
- Horizontal Pod Autoscaling
Pod 控制器在 这一章节 已经简单的介绍过了,那么本篇文章将继续讲解 Pod 的控制器,并提供一些简单的示例。
-
自主式 Pod
直接创建一个 Pod 而不使用控制器,这种方式创建的 Pod 就称为 自主式 Pod。
这种 Pod 一旦停止或被删除,就无法自动重启或重新创建。
-
控制器管理的 Pod
通过 ReplicaSet、DaemonSet 等控制器创建的 Pod,称为控制器管理的 Pod。
通过控制器来管理的 Pod,会有一个期望的副本数量,一旦 Pod 超出或者达不到这个数量,那么控制器就会通过重启、创建或者删除 Pod 的方式维持 Pod 的数量达到期望值。
什么是控制器
k8s 中内建了很多控制器(controller ),这些相当于一个状态机,用来控制 Pod 的具体状态和行为。
控制器类型
- ReplicationController
- ReplicaSet
- DaemonSet
- StatefulSet
- Job/CronJob
- Horizontal Pod Autoscaling
ReplicationController 和 ReplicaSet
ReplicationController(RC)用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代,而如果异常多出来的容器也会自动回收。
在新版本的 k8s 中建议使用 ReplicaSet(RS)来取代 RC,RS 与 RC 没有本质的不同,只是名字不一样,并且RS 支持集合式的 selector,即通过标签(labels)来管理多个 Pod。
以下是 RS 示例:
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: rs-1
spec:
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/library/mynginx:v1
ports:
- containerPort: 80
说明:
- metadata.name:定义 rs 的名称。
- spec.replicas:期望的 Pod 副本数。
- spec.selector:selector 选择器。其中
matchLabels.tier
是需要匹配的 Pod 标签。 - spec.template:template 中的信息可以理解为就是定义一个 Pod。
- spec.template.metadata:Pod 的元数据信息,其中
labels.tier
是 Pod 的标签,且必须和上面的matchLabels.tier
一致,这样才能被 RS 控制器选择到。 - image:这里使用的镜像是我本地镜像,本质上就是一个 nginx 镜像。
执行以下操作:
[root@k8s-master01 ~]# kubectl create -f rs.yaml
replicaset.extensions/rs-1 created
# 查看 rs 控制器
[root@k8s-master01 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
rs-1 3 3 3 4m33s
# 查看Pod。--show-labels:列出LABELS字段;-l tier=frontend:查看标签是 tier=frontend 的Pod
[root@k8s-master01 ~]# kubectl get pod --show-labels -l tier=frontend
NAME READY STATUS RESTARTS AGE LABELS
rs-1-d96fz 1/1 Running 0 94s tier=frontend
rs-1-rxsls 1/1 Running 0 4m57s tier=frontend
rs-1-zmckx 1/1 Running 0 4m57s tier=frontend
# 将 rs-1-ppmnz 这个Pod的标签改为 frontend1
[root@k8s-master01 ~]# kubectl label pod rs-1-ppmnz tier=frontend1 --overwrite=true
pod/rs-1-ppmnz labeled
# 再次查看Pod。因为 frontend1 这个标签不属于 rs-1 控制器管理,控制器为了达到副本数3,就又创建了一个Pod
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
rs-1-d96fz 1/1 Running 0 12s tier=frontend
rs-1-ppmnz 1/1 Running 0 3m35s tier=frontend1
rs-1-rxsls 1/1 Running 0 3m35s tier=frontend
rs-1-zmckx 1/1 Running 0 3m35s tier=frontend
# 删除 rs-1 控制器
[root@k8s-master01 ~]# kubectl delete rs rs-1
replicaset.extensions "rs-1" deleted
# 查看Pod,发现被控制器管理的Pod全部不存在了
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
rs-1-ppmnz 1/1 Running 0 9m42s tier=frontend1
Deployment
Deployment 为 Pod 和 RS 提供了一个声明式定义(declarative)方法,用来替代以前的 RC 来方便的管理应用。典型的应用场景包括:
-
定义 Deployment 来创建 RS 和 Pod,实际上 Deployment 是通过管理 RS 去管理 Pod的。
-
滚动升级和回滚应用
-
扩容和缩容
-
暂停和继续 Deployment
声明式编程:侧重于定义期望目标,然后告诉程序或者软件,让它来帮我们完成。声明式一般使用
apply
来创建对应的服务。如 Deployment。命令式编程:侧重于如何实现程序,通常需要一步一步定义程序执行步骤,以达到预期目的。命令式一般使用
create
来创建对应的服务。如 ReplicaSet。虽然声明式编程和命令式编程建议区别
aplly
和create
,但是两者混淆使用也不会出问题。
创建 Deployment
以下是 Deployment 示例:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: deployment-1
spec:
replicas: 3
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/library/mynginx:v1
ports:
- containerPort: 80
说明:
- metadata.name:deployment 的名称。
- spec.replicas:期望的 Pod 副本数。
- spec.template:template 中的信息可以理解为就是定义一个 Pod。
- spec.template.metadata:Pod 的元数据信息。其
labels.app
为 Pod 的标签。 - image:这里使用的镜像是我本地镜像,本质上就是一个 nginx 镜像。
执行以下操作:
kubectl 的 --record 的 flag 设置为 true 可以在 annotation 中记录当前命令创建或者升级了该资源。这在未来会很有用,例如,查看在每个 Deployment revision 中执行了哪些命令。
# 创建 Deployment,
[root@k8s-master01 ~]# kubectl apply -f demployment.yaml --record
deployment.extensions/deployment-1 created
# 查看 Deployment
[root@k8s-master01 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
deployment-1 3/3 3 3 31s
# 查看 rs
[root@k8s-master01 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deployment-1-7796b9c74d 3 3 3 37s
# 查看 Pod,--show-labels:显示Pod的标签
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
deployment-1-7796b9c74d-96psh 1/1 Running 0 44s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-gm9z5 1/1 Running 0 44s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-kvtj2 1/1 Running 0 44s app=nginx-app,pod-template-hash=7796b9c74d
扩容
# 将Pod的期望副本数扩容到5个
[root@k8s-master01 ~]# kubectl scale deployment deployment-1 --replicas 5
deployment.extensions/deployment-1 scaled
# 查看 rs,rs没有改变,仅仅是期望的副本数变为5个了
[root@k8s-master01 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deployment-1-7796b9c74d 5 5 5 8m39s
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
deployment-1-7796b9c74d-8rvgk 1/1 Running 0 10s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-96psh 1/1 Running 0 7m22s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-gm9z5 1/1 Running 0 7m22s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-jkwc8 1/1 Running 0 10s app=nginx-app,pod-template-hash=7796b9c74d
deployment-1-7796b9c74d-kvtj2 1/1 Running 0 7m22s app=nginx-app,pod-template-hash=7796b9c74d
滚动更新
滚动更新过程可参考这里:点我直达
Deployment 的 rollout(滚动更新) 当且仅当 Deployment 的 pod template(例如.spec.template)中的 label更新或者镜像更改时被触发。其他更新,例如扩容 Deployment 不会触发 rollout。
假如我们现在想要让 Pod 中的 nginx-container 容器使用 hub.xixihaha.com/library/mynginx:v2
的镜像来代替原来的 hub.xixihaha.com/library/mynginx:v1
的镜像。
[root@k8s-master01 ~]# kubectl set image deployment/deployment-1 nginx-container=hub.xixihaha.com/library/mynginx:v2
deployment.extensions/deployment-1 image updated
# 查看 deployment,发现没什么变化
[root@k8s-master01 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
deployment-1 5/5 5 5 24m
# 查看 rs,出现了一个新的rs,pod数量维持在5个,并且原先的rs的pod数量变为0了
# deployment-1-6fd64dcb5 就是deployment升级后新创建的rs,并且Pod里的容器使用的是新的镜像
[root@k8s-master01 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deployment-1-6fd64dcb5 5 5 5 22s
deployment-1-7796b9c74d 0 0 0 22m
查看更新状态
[root@k8s-master01 ~]# kubectl rollout status deployment/deployment-1
deployment "deployment-1" successfully rolled out
查看升级记录
[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1
deployment.extensions/deployment-1
REVISION CHANGE-CAUSE
1 kubectl apply --filename=demployment.yaml --record=true
2 kubectl apply --filename=demployment.yaml --record=true
因为我们创建 Deployment 的时候使用了 --recored 参数可以记录命令,我们可以很方便的查看每次 revision 的变化。
查看单个revision 的详细信息:
[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1 --revision=2
deployment.extensions/deployment-1 with revision #2
Pod Template:
Labels: app=nginx-app
pod-template-hash=6fd64dcb5
Annotations: kubernetes.io/change-cause: kubectl apply --filename=demployment.yaml --record=true
Containers:
nginx-container:
Image: hub.xixihaha.com/library/mynginx:v2
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
清理 Policy
我们可以设置 Deployment 中的 .spec.revisionHistoryLimit 项来指定保留多少旧的 ReplicaSet。 余下的将在后台被当作垃圾收集。默认的,所有的 revision 历史就都会被保留。在未来的版本中,将会更改为2。
注意: 将该值设置为0,将导致所有的 Deployment 历史记录都会被清除,该 Deployment 就无法再回退了。
回滚更新
假设我们上面升级的镜像出现了问题,现在需要回退一个版本,可以使用以下命令:
kubectl rollout undo deployment/deployment-1
也可以使用 --revision 参数指定某个历史版本:
kubectl rollout undo deployment/deployment-1 --to-revision=2
此时我们查看 rs:
[root@k8s-master01 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deployment-1-6fd64dcb5 0 0 0 4m41s
deployment-1-7796b9c74d 5 5 5 5m13s
版本回退时 Deployment 并没有创建新的 RS,而是将原来的 RS 重新激活了。
我们再次查看版本更新记录:
[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1
deployment.extensions/deployment-1
REVISION CHANGE-CAUSE
2 kubectl apply --filename=demployment.yaml --record=true
3 kubectl apply --filename=demployment.yaml --record=true
[root@k8s-master01 ~]# kubectl rollout history deployment/deployment-1 --revision=3
deployment.extensions/deployment-1 with revision #3
Pod Template:
Labels: app=nginx-app
pod-template-hash=7796b9c74d
Annotations: kubernetes.io/change-cause: kubectl apply --filename=demployment.yaml --record=true
Containers:
nginx-container:
Image: hub.xixihaha.com/library/mynginx:v1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
可以发现,回退版本的操作也会被记录下来。
更新策略
Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)。
Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )。
在未来的 Kuberentes 版本中,将从1-1变成25%-25%。
Rollover(多个rollout并行)
每当 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 都创建完成后才开始改变航道。
DaemonSet
DaemonSet 确保全部(或者一些)node 上运行一个 Pod 的副本。当有新的 node 加入集群时,会自动为他们添加一个这样的 Pod。当有集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它所创建的所有 Pod。
常见场景:
- 运行集群存储 daemon,例如在每个 node 上运行 glusterd、ceph。
- 在每个 node 上运行日期收集 daemon,例如 fluentd、logstash。
- 在每个 node 上运行监控 daemon,例如 Prometheus node Exporter、collectd、Datadog 代理或New Relic 代理。
以下是 DaemonSet 示例:
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: daemonset-1
labels:
app: daemonset-app
spec:
selector:
matchLabels:
name: daemonset-label
template:
metadata:
labels:
name: daemonset-label
spec:
containers:
- name: nginx-container
image: hub.xixihaha.com/library/mynginx:v1
ports:
- containerPort: 80
说明:
- metadata.name:daemonSet 的名称。
- metadata.labels.app:daemonSet 自己的标签。
- spec.selector:标签选择。
matchLabels.name
指定了要选择的 Pod 标签。 - spec.template:template 中的信息可以理解为就是定义一个 Pod。
- spec.template.metadata:Pod 的元数据信息,其中
labels.name
是 Pod 的标签,且必须和上面的matchLabels.name
一致,这样才能被 DS 控制器选择到。
执行以下命令:
[root@k8s-master01 ~]# kubectl create -f daemonset.yaml
daemonset.extensions/daemonset-1 created
# 查看daemonSet
[root@k8s-master01 ~]# kubectl get ds
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset-1 2 2 2 2 2 <none> 113s
# 查看Pod标签
[root@k8s-master01 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
daemonset-1-2ftjc 1/1 Running 0 52s controller-revision-hash=6445f7997f,name=daemonset-label,pod-template-generation=1
daemonset-1-w9cwc 1/1 Running 0 52s controller-revision-hash=6445f7997f,name=daemonset-label,pod-template-generation=1
# 查看Pod运行在哪个节点
[root@k8s-master01 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
daemonset-1-2ftjc 1/1 Running 0 8s 10.244.2.45 k8s-node02 <none> <none>
daemonset-1-w9cwc 1/1 Running 0 8s 10.244.1.14 k8s-node01 <none> <none>
结果显示一共创建了两个 Pod,且分别运行在 k8s-node01 和 k8s-node02 两个节点上。
我们说过,DaemonSet 确保了集群中每一个节点都会运行一个 Pod(master 节点没有运行是因为 污点
的设置)。就算我们删除某个节点的 Pod,DaemonSet 也会马上为这个节点重新创建一个 Pod。
此时如果有新的节点加入到集群中,那么 DaemonSet 也会为这个新节点自动创建一个这样的 Pod。
Job
Job 负责批处理任务,即仅执行一次的任务,它能够确保批处理任务的一个或多个 Pod 运行成功。意思就是,运行一个 Job 来创建 Pod,让里面的容器成功运行了指定的次数,才认为这个 Job 是成功的,那么这个 Job 才算执行完成。
特殊说明:
- spec.template 格式同 Pod
- restartPolicy 策略仅支持 Never 或 OnFailure。
- 单个 Pod 时,默认 Pod 成功运行后 Job 即结束。
- .spec.completions 标志 Job 结束需要成功运行的 Pod 个数,默认为 1。
- .spec.parallelism 标志并行运行的 Pod 个数,默认为 1。
- spec.activeDeadlineSeconds 标志失败的重试最大时间,超过这个时间不会继续重试。
以下是 Job 示例:
apiVersion: batch/v1
kind: Job
metadata:
name: job-1
spec:
template:
metadata:
name: job-app
spec:
containers:
- name: busybox-container
image: busybox
command: ['sh', '-c', 'echo hello world']
restartPolicy: Never
执行以下命令:
# 查看 job
[root@k8s-master01 ~]# kubectl get job
NAME COMPLETIONS DURATION AGE
job-1 1/1 57s 2m10s
# 查看 pod
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
job-1-znjg4 0/1 Completed 0 79s
# 查看 pod 日志
[root@k8s-master01 ~]# kubectl log job-1-znjg4
log is DEPRECATED and will be removed in a future version. Use logs instead.
hello world
CronJob
CronJob 管理基于时间的 Job,即:
- 在给定的时间点运行一次
- 周期性的在给定时间点运行
- 其本质就是在特定的时间循环创建 Job 来执行任务
- 其表达式为:分、时、日、月、周
常用场景:
- 在给定的时间点调度 Job 运行
- 创建周期性的运行的 Job,例如:数据库备份,发送邮件。
CronJob 使用时需要 k8s 版本 >= 1.8。
以下是 CronJob 的示例:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: cronjob-1
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
metadata:
name: cronjob-app
spec:
containers:
- name: busybox-container
image: busybox
command: ['sh', '-c', 'date && echo hello world']
restartPolicy: Never
说明:
-
spec.schedule:调度,必须字段,指定任务运行周期,格式为分、时、日、月、周。
-
spec.jobTemplate:Job 模板,必须字段,指定需要运行的任务,格式同 Job。
-
spec.startingDeadlineSeconds:启动 Job 的期限(秒级别),该字段是可选的,如果因为任何原因而错过了被调度的时间,那么错过指定时间的 Job 将被认为的失败的。默认无期限。
-
spec.concurrencyPolicy:并发策略,可选。它指定了 CronJob 创建的 Job 的并发执行。只允许指定下面策略中的一种。
- Allow:允许兵法执行,默认。
- Forbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个。
- Replace:取消当前中正在执行的 Job,用一个新的来替换。
这个策略只作用于同一个 CronJob 所创建的 Job。如果存在多个 CronJob,它们创建的 Job 之间总是允许并行执行的。
执行以下操作:
# 创建cronjob
[root@k8s-master01 ~]# kubectl apply -f cronjob.yaml
cronjob.batch/cronjob-1 created
# 查看 cronjob
[root@k8s-master01 ~]# kubectl get cj
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob-1 */1 * * * * False 0 <none> 12s
# 查看 job,每隔一分钟创建一个job
[root@k8s-master01 ~]# kubectl get job
NAME COMPLETIONS DURATION AGE
cronjob-1-1585241460 1/1 65s 73s
cronjob-1-1585241520 0/1 13s 13s
# 查看Pod,job一旦被创建出来,那么job会创建一个对应的Pod
[root@k8s-master01 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
cronjob-1-1585241460-hmhr6 0/1 Completed 0 91s
cronjob-1-1585241520-5mjr4 0/1 ContainerCreating 0 31s
# 查看Pod日志
[root@k8s-master01 ~]# kubectl log cronjob-1-1585241460-hmhr6
log is DEPRECATED and will be removed in a future version. Use logs instead.
Thu Mar 26 16:52:09 UTC 2020
hello world
StatefulSet
StatefulSet 为 Pod 提供唯一的标识,它可以保证部署和 scale 的顺序。StatefulSet 是为了解决有状态服努的问题,对应 Deployment 和 ReplicaSet 是为无状态服务而设计。
StatefulSet 有以下特点:
- 稳定的持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于PVC来实现。
- 稳定的网络标志,即 Pod 重新调度后,其 PodName 和 HostName 不变,基于 Headless Service(即没有Cluster IP的 Service)来实现。
- 有序部署,有序扩展。即 Pod 是有顺序的,在部署或扩展的时候要依据定义的顺序依次进行(即从 0 到 N-1,在下一个 Pod 运行之前,之前所有的 Pod 必须都是 Running 和 Ready 状态),基于 init containers 来实现。
- 有序收缩,有序删除(即从 N- 1 到 0)。
Horizontal Pod Autoscaling
应用的资源使用率通常都有高峰和低谷的时候,如何削峰埋谷,提高集群的整体资源利用率,让 service 中的 Pod 个数自动调整昵?这就有赖于 Horizontal Pod Autoscaling 了,顾名思义,使 Pod 水平自动缩放。
-- end --