Kubernetes工作负载管理(下)
作者 | 乔克
来源 |运维开发故事(ID:mygsdcsf)
转载请联系授权(微信ID:wanger5354)
回滚
有更新,就有回滚,它们是苦命鸳鸯。
在Kubernetes中,回滚使用kubectl rollout命令。在滚动更新的章节,我们更新了Nginx应用,现在新版本如果有问题,需要进行回滚操作。
(1)查看可以回滚的历史版本
$ kubectl rollout history deployment nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 <none>
2 <none>
发现有两个版本,现在使用的就是2版本,我们需要回滚到1版本 。
(2)执行以下命令回滚到老版本
$ kubectl rollout undo deployment nginx-deployment --to-revision 1
deployment.apps/nginx-deployment rolled back
(3)通过查看RS,查看是否回滚到老版本
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-6c74f576b9 3 3 3 27m
nginx-deployment-778d9f5866 0 0 0 25m
如果可以明确直接回滚到上一个版本,可以直接使用kubectl rollout undo deployment nginx-deployment。
回滚的操作比较简单,但是如果发布比较频繁,历史数据超过一定版本(默认10个版本)后,就无法回滚到更老的版本了。
当然,我们可以通过定义spec.revisionHistoryLimit来定义保留多少版本号,默认是10个,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx-deployment
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: nginx
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:1.8
imagePullPolicy: IfNotPresent
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
以上就是Deployment的回滚操作,操作命令比较简单,主要是了解到Kubernetes为我们提供了这个功能,以备不时之需。
总结
从全文可知,Deployment实际是一个两层控制器:(1)、它通过ReplicaSet的个数来描述应用版本个数;(2)、它通过ReplicaSet的属性来保证Pod的副本数;而且Deployment的灵活控制,很方便水平扩展/收缩还有滚动更新以及回滚操作。
DaemonSet
DaemonSet保证在每个Node上都运行一个Pod,如果新增一个Node,这个Pod也会运行在新增的Node上,如果删除这个DadmonSet,就会清除它所创建的Pod。常用来部署一些集群日志收集,监控等全局应用。
常见的场景如下:
1、运行存储集群daemon,比如ceph,glusterd等;
2、运行一个日志收集daemon,比如logstash,fluentd等;
3、运行监控daemon,比如Prometheus Node Exporter,collectd,New Relic agent,Ganglia gmond等;
比如运行一个filebeat的DaemonSet,定义如下:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-ds
namespace: default
spec:
selector:
matchLabels:
app: filebeat
role: logstorage
template:
metadata:
labels:
app: filebeat
role: logstorage
spec:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.5-alpine
env:
- name: REDIS_HOST
value: redis.default.svc.cluster.local
执行过后,就可以看到在kk-node01节点上运行了filebeat,如下:
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
filebeat-ds-kgqcq 1/1 Running 0 28s 172.16.51.212 kk-node01 <none> <none>
可能有人好奇,集群本身有两个节点,为何只部署了一个Pod?
那是因为我master(控制节点)有污点,而我上面的DaemonSet没有容忍这个污点,所以就没有调度上去,具体的调度策略,我们留到kubernetes调度管理章节进行讲解。
DaemonSet也是支持更新和回滚的,具体操作和Deployment类似,这里就不再赘述。
不过,这里要介绍一下DaemonSet的更新策略,目前支持两种更新策略:
- OnDelete:先删后起,也就是先删除老的Pod,再启动新的Pod,这种策略会导致节点在更新的过程中出现断连的情况。
- RollingUpdate:滚动更新,和Deployment滚动方式一样,默认的策略。
值得一提的是rollingUpdate的更新策略,在老的Kubernetes版本中只有maxUnavailable而没有maxSurge,因为DaemonSet只允许在Node上运行一个。但是在新版本中有了maxSurge这个参数,由它来控制多少个节点可用,比如总共有100个节点,maxSurge配置30%,则表示至少保证30个节点可用。
Job/CronJob
Kubernetes的主要任务是保证Pod中的应用长久稳定的运行,但是我们有时候也需要一些只需要运行一次,执行完就退出了的"短时"任务,这时候使用Deployment等这类控制器就无法满足我们的需求,Kubernetes就诞生了Job Controller,专门用来处理这类需求。
Job
Job负责处理仅执行一次的任务,它保证批处理的任务的一个或多个成功结束,我们可以通过kubectl explain job来查看具体语法。
基本操作
Job的定义语法和Deployment、Pod差不多,定义一个简单的Job,如下:
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
namespace: default
spec:
template:
metadata:
name: job-demo
spec:
containers:
- name: test-job
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "for i in $(seq 10); do echo $i; done"
restartPolicy: Never
backoffLimit: 4
这个Job简单执行一个脚本,循环10次并输出,通过kubectl apply -f job-demo.yaml创建Job,如下:
$ kubectl apply -f job-demo.yaml
job.batch/job-demo created
然后可以通过kubectl logs来查看日志输出,如下:
$ kubectl logs job-demo-wd67s
一切都符合我们的预期,现在再来看看Job和Pod的状态,如下:
$ kubectl get jobs.batch
NAME COMPLETIONS DURATION AGE
job-demo 1/1 23s 112s
$ kubectl get po
NAME READY STATUS RESTARTS AGE
job-demo-wd67s 0/1 Completed 0 114s
Job的状态没有类似Deployment的Ready关键字,而是COMPLETIONS(完成),1/1表示完成了这个Job。而Job所控制的Pod的状态也是Completed,如果是这种状态,就表示这个Job是成功的。
如果成功,Job的Pod运行一次就结束了,如果失败呢?
现在将刚才的Job的YAML改成如下:
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
namespace: default
spec:
template:
metadata:
name: job-demo
spec:
containers:
- name: test-job
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "xxxxx"
restartPolicy: Never
backoffLimit: 4
执行过后可以看到Pod在不断的重建,如下:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
job-demo-kwsl8 0/1 Error 0 3s
job-demo-ltsvq 0/1 ContainerCreating 0 0s
job-demo-w54s4 0/1 Error 0 6s
为什么是重建而不是重启呢?
因为我们在上面的YAML里配置了restartPolicy: Never,如果Job失败就只会重建,如果要使用重启,可以配置restartPolicy: OnFaliure,表示只有在状态为Failure的时候才会重启,Job没有Always参数。
把上面YAML中restartPolicy改成OnFaliure,效果如下:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
job-demo-p9dkp 0/1 CrashLoopBackOff 3 (24s ago) 68s
可以看到该Job在不断的重启。
还有一种情况,如果这个Job一直不肯结束怎么办呢?比如我们将上面的YAML文件做如下修改:
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
namespace: default
spec:
template:
metadata:
name: job-demo
spec:
containers:
- name: test-job
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "sleep 3600"
restartPolicy: OnFailure
backoffLimit: 4
为了避免这种情况,可以在YAML里加入activeDeadlineSeconds参数来指定Pod的存活时间,如下:
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
namespace: default
spec:
template:
metadata:
name: job-demo
spec:
containers:
- name: test-job
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "sleep 3600"
restartPolicy: OnFailure
backoffLimit: 4
activeDeadlineSeconds: 10
该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failed 及 reason: DeadlineExceeded。
Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod, 即使其重试次数还未达到 backoffLimit 所设的限制。
并行控制
在Job对象中,负责控制并行的参数为:
- completions:定义Job至少要完成的Pod数目,既Job的最小完成数;
- parallelism:定义一个Job在任意时间最多可以启动多少个Pod;
我们定义下面一个Job的YAML文件:
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
namespace: default
spec:
parallelism: 2
completions: 4
template:
metadata:
name: job-demo
spec:
containers:
- name: test-job
image: busybox
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-c"
args:
- "for i in $(seq 10); do echo $i; done"
restartPolicy: OnFailure
backoffLimit: 4
activeDeadlineSeconds: 100
parallelism: 2 和 completions: 4表示要完成4个pod,每次可以同时运行两个Pod,我们创建这个Job,观察结果如下:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
job-demo-5wlp8 0/1 Completed 0 2s
job-demo-6wfkw 0/1 Completed 0 2s
job-demo-d54vz 0/1 Completed 0 5s
job-demo-x5mpz 0/1 Completed 0 5s
从上面可以知道,Job Controller实际控制的就是Pod,它在创建的时候会在Job和Pod里自动生成随机字符串的label,然后将它们进行绑定。
Job Controller在实际的调谐操作是根据实际在running状态的Pod数,还有已经退出的Pod数以及parallelism和completions的参数值共同计算出在Job周期内应该创建或者删除多少Pod,然后调用kube-api来执行这类操作。
所以Job Controller实际上是控制的Pod的并行度以及总共要完成的任务数这两个重要的参数。
CronJob
CronJob其实就在Job的基础上加了时间调度,类似于用Deployment管理Pod一样。它和我们Linux上的Crontab差不多。
比如定义简单的CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
command:
- "/bin/sh"
- "-c"
args:
- "for i in $(seq 10); do echo $i; done"
restartPolicy: OnFailure
我们可以看到spec里其实就是一个Job Template。另外其schedule就是一个便准的Cron格式,如下:
分钟 小时 日 月 星期
* * * * *
运行过后,查看状态如下:
$ kubectl get cronjobs.batch
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 0 45s 69s
$ kubectl get po
NAME READY STATUS RESTARTS AGE
hello-27628291-h8skg 0/1 Completed 0 50s
需要注意的是,由于cron的特殊性,有时候会存在由于上一个定时任务还没有执行完成,新的定时任务又开始了的情况,我们可以通过定义spec.concurrencyPolicy字段来定义规则,比如:
- concurrencyPolicy=Allow:表示这些Job可以同时存在
- concurrencyPolicy=Firbid:表示不会创建新的Job,也就是这个定时任务被跳过
- concurrencyPolicy=Replace:表示产生的新Job会替代旧的Job
如果某一个Job创建失败,那么这次创建就会被标记为miss,当在指定的时间窗口内,Miss的数达到100,那么CronJob就会停止再创建这个Job。这个时间窗口可以通过spec.startingDeadlineSeconds来指定。
总结
上面介绍的是日常工作中常用的控制器,其中Deployment和DaemonSet的使用频率最高,熟练掌握这些控制器,并且学会在什么时候选择什么样的控制器,合理使用使工作效率最高。