目录:
(1)k8s指南-概述
(2)k8s指南-架构
(3)k8s指南-工作负载(1)
(4)k8s指南-工作负载(2)
(5)k8s指南-工作负载(3)
(6)k8s指南-工作负载(4)
(7)k8s指南-Service
(8)k8s指南-Ingress
(9)k8s指南-DNS与服务发现
(10)k8S指南-平滑升级与自动扩缩容
批处理工作负载包括Job和CronJob,前者用于处理一次性任务,后者处理定时任务。
Job
Job会创建一个或多个Pod,并将执行这些创建的pod,直到所有的pod成功终止。
删除Job的操作会清除所创建的全部pod,挂起Job的操作会删除Job的所有活跃Pod,直到Job被再次恢复执行。
示例Job
下面是一个Job配置示例,负责计算圆周率小数点后的2000位,并将结果打印出来。
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
你可以通过以下命令来运行此示例:
kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml
通过describe或者get命令可以看到该工作负载或Pod的状态。查看pod的日志:
kubectl logs pi-8ldmv
3.1415926535897932384626433832795028841971...
Job规约
与Kubernetes中其他资源类似,Job也需要 apiVersion
, kind
和 metadata
字段。Job的名字必须是合法的DNS子域名。另外还需要.spec
节区。
Pod模板
Job的.spec
中只有.spec.template
是必需的字段。
除了作为pod所必需的字段之外,Job中的pod模板必须设置合适的标签和重启策略,重启策略只能为Never
或OnFailure
(当设置为OnFailure
时,需要考虑重启是否会带来幂等性问题)。
字段.spec.selector
是可选的,在绝大多数场合都不需要为其赋值。
Job的并行执行
适合以Job形式来运行的任务主要有三种:
非并行运行Job
- 通常只启动一个Pod,除非该pod失败
- 当pod成功终止时,立即视Job为完成状态
完成计数的并行Job
-
.spec.completions
字段设置为非0的正数值
- Job用来代表整个任务,当成功的pod个数达到
.spec.completions
时,Job被视为完成。
- 当使用
.spec.completionMode="Indexed"
时,每个pod都会获得一个不同的索引值,介于0和.spec.completions-1
之间。
工作队列的并行Job
- 不设置
.spec.completions
,默认值为.spec.parallelism
。
- 多个pod之间必须互相协调,或者借助外部服务确定每个pod要处理哪个工作项。例如,任一pod都可以从工作队列中取走最多N个工作项。
- 每个pod都可以独立确定是否其他pod均已完成,进而确定Job是否完成。
- 当Job中任何pod成功终止,不再创建新pod。
- 一旦至少一个pod成功完成,并且所有pod均已终止,即可宣告Job成功完成。
- 一旦任何pod成功退出,任何其他pod都不应再对此任务执行任何操作或生成任何输出,所有pod都应启动退出过程。
对于非并行的Job,你可以不设置.spec.completions
和.spec.parallelism
。这两个属性都不设置时,默认为1。
对于完成计算类型的Job,应该设置.spec.completions
为需要的完成个数,.spec.parallelism
可以设置也可以不设置,默认值为1。
对于一个工作队列Job,可以不设置.spec.completions
,但必须设置.spec.parallelism
。
并行性请求(.spec.parallelism
)可以设置为任何非负整数,如果为0,则Job相当于启动后便暂停,直到此值被增加。
实际并行性(即任意时刻处于运行状态的pod个数)可能比并行性请求略大或略小。
处理Pod和容器失效
Pod中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非0,或者容器因为超出内存约束而被杀死。如果发生这类事件,并且重启策略为OnFailure
,Pod会继续保留,但容器会被重启。
因此你的程序需要能够处理重启的情况,或者设置重启策略为Never
。
除了容器外,Pod也可能会失败。例如当Pod启动时,节点失效(被重启,被升级或删除等),或者pod中的容器失败而不重启(重启策略为Never)。当Pod失败时,Job控制器会启动一个新的pod。这意味着,你的应用需要处理在一个新的pod中被重启的情况,尤其是应用需要处理之前运行所产生的临时文件、锁、不完整输出等问题。
即使将Job的重启策略设置为Never,同一程序也可能因为pod重启而被执行两次。
Pod的回退失效策略
在有些情况下,你可能希望执行Job的pod在经历若干次失败后,Job直接进入失败状态,比如Job有配置错误。为了实现这一点,可以设置.spec.backoffLimit
参数,表示失败次数限制,默认为6。
计算重试次数有以下两种方法:
- 计算
.status.phase = "Failed"
的pod数量
- 当pod的重启策略为OnFailure时,针对
.status.phase
等于pending
或running
的pod,计算其中所有容器的重启次数。
两种方式中其中一个的值达到失败次数限制时,Job就会被判定为失败。
如果你的Job的重启策略为OnFailure,则pod不会重启,但任务容器会重启,在达到Job失败次数限制时pod会被自动删除,其中的日志会丢失。如下所示:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 98s job-controller Created pod: pi-gddcd
Normal SuccessfulDelete 5s job-controller Deleted pod: pi-gddcd
Warning BackoffLimitExceeded 5s job-controller Job has reached the specified backoff limit
如果你的Job的重启策略为Never,则容器不会被重启。每次失败时,Job-controller都会创建一个新的pod来重试Job,直到达到失败次数限制。失败的pod不会被删除,其中的日志也会保留。如下所示:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 2m9s job-controller Created pod: pi-8whm8
Normal SuccessfulCreate 2m5s job-controller Created pod: pi-lnnps
Normal SuccessfulCreate 110s job-controller Created pod: pi-bvw9t
Normal SuccessfulCreate 85s job-controller Created pod: pi-422xk
Normal SuccessfulCreate 41s job-controller Created pod: pi-86x4z
因此建议将Job的重启策略设置为Never比较好。
activeDeadlineSeconds
除了Job执行结束与失败次数超限会导致Job终止外,还可以通过配置活跃期限(.spec. activeDeadlineSeconds
)来自动停止Job任务。
我们可以为 Job 的 .spec.activeDeadlineSeconds 设置一个秒数值。该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
.spec. activeDeadlineSeconds
的优先级高于.spec.backoffLimit
,也就是说,即使失败次数未达到限制,只要Job活跃时间限制到了,该Job也会被终止。
注意,Job规约和Job的pod模板规约中都有activeDeadlineSeconds
字段,不要设置错了。
重启策略对应的是pod,而不是Job本身。一旦job状态变为失败状态,这就是一个最终状态。
也就是说,由.spec.activeDeadlineSeconds
和 .spec.backoffLimit
所触发的Job终止机制会导致Job永久性的失败,而这类状态都需要人工干预才能解决。
Job自动清理
已完成的Job通常不需要留存在系统中,一直保留它们会给API服务器带来额外的压力。
如果Job由某种更高级的控制器如CronJob来管理,则Job可以被CronJob基于特定的清理策略清理掉。否则的话,就需要指定其过期机制。
Job的TTL机制
特性版本:Kubernetes v1.23 [stable]。
可以通过设置Job的.spec.ttlSecondsAfterFinished
字段来自动清理已完成的Job(状态为Completed
或Failed
)。
例如:
apiVersion: batch/v1
kind: Job
metadata:
name: pi-with-ttl
spec:
ttlSecondsAfterFinished: 100
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
该Job会在结束后的100秒被清理。如果ttl字段设置为0则表示在结束之后立即清理。没有设置则不会被清理。
CronJob
一个CronJob对象就像是一个Linux环境的crontab文件一样,它会在给定的调度周期(crontab格式)内定期的创建一些job。
注意:所有的定时任务的调度周期都基于kube-controller-manager的时区。
通常情况下,CronJob对于创建定期和重复的任务非常有用,比如定期的备份和邮件发送之类的任务场景。
Cron时间表语法
┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 月的某天 (1 - 31)
│ │ │ ┌───────────── 月份 (1 - 12)
│ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
│ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
│ │ │ │ │
│ │ │ │ │
* * * * *
输入 |
描述 |
相当于 |
@yearly (or @annually) |
每年 1 月 1 日的午夜运行一次 |
0 0 1 1 * |
@monthly |
每月第一天的午夜运行一次 |
0 0 1 * * |
@weekly |
每周的周日午夜运行一次 |
0 0 * * 0 |
@daily (or @midnight) |
每天午夜运行一次 |
0 0 * * * |
@hourly |
每小时的开始一次 |
0 * * * * |
例如,下面这行指出必须在每个星期五的午夜以及每个月 13 号的午夜开始任务:
0 0 13 * 5
要生成 CronJob 时间表表达式,你还可以使用crontab.guru之类的 Web 工具。
时区
对于没有指定时区的CronJob,kube-controller-manager基于本地时区解释排期表(Schedule)。
功能特性: Kubernetes v1.25 [beta]。
如果启用了 CronJobTimeZone 特性门控, 你可以为 CronJob 指定一个时区(如果你没有启用该特性门控,或者你使用的是不支持试验性时区功能的Kubernetes版本,集群中所有CronJob的时区都是未指定的)。
启用该特性后,你可以将 spec.timeZone
设置为有效时区名称。 例如,设置 spec.timeZone: "Etc/UTC"
指示 Kubernetes采用UTC来解释排期表。
Go 标准库中的时区数据库包含在二进制文件中,并用作备用数据库,以防系统上没有可用的外部数据库。
Cronjob调度丢失
一个Cronjob每执行一次调度就会创建一个Jobs对象。大概是因为有时候可能会有两个job被创建,或者没有任务创建。官方尝试去解决这个问题,但是目前仍然无法避免。因此在设计过程中,所有的Job都应该是幂等性的(idempotent)。
如果CronJob未能在预定时间创建,则该任务将被视为错过调度。对于每一个Cronjob来说,CronJob控制器会检查从上一次调度时间到现在的持续时间内它错过了多少个调度,如果错过调度100次,它将不再执行调度,并且会有如下相关异常:
Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
值得注意的是,如果设置了startingDeadlineSeconds参数(不为空),控制器统计错过的调度次数将不再是从最后一次调度时间,而是按startingDeadlineSeconds的值统计。比如,如果设置startingDeadlineSeconds:200,控制器会统计在最后200秒内错过了的调度次数。
当设置concurrencyPolicy: Forbid时,如果前一个任务还在运行时,CronJob尝试再次被调度,此时会被forbid掉,也会被记录为错过一次调度。
假设一个定时任务被设置在08:30:00开始每一分钟执行一次,并且startingDeadlineSeconds:200。如果CronJob控制器在8:30-10:30时间段故障了,Job将会在10:30:00开始继续执行。 因为控制器仅会计算在过去的200秒内,错过调度的次数,这远远小于100次,所有定时任务会在控制器恢复后继续调度,而不会影响正常的任务。
另外需要注意的是,CronJob仅负责调度和创建匹配的Jobs,而由Jobs真正去管理真正执行任务的Pods。
Cronjob参数详情
- spec.startingDeadlineSeconds: 表示统计错过调度次数(100次)的开始时间,默认从最后一次调度时间开始统计错过调度次数(超过100不再调度)。
- spec.concurrencyPolicy: 并发调度策略,可选值:{“Allow”:“允许并发”,“Forbid”:“不允许”,“Replace”:“调度覆盖”}。Allow: 注意:当设置为Allow时,需要考虑到任务执行时间和调度周期,因为可能上个任务没执行成功,下个任务就到执行时间了,如此下来可能会有多个相同的任务在同时执行,造成资源浪费或者并发安全问题;Replace: 当使用Replace遇到上述情况,后个任务会将前一个任务替换掉,如此一来所有的任务可能都不会完整执行;Forbid: 不允许并发调度,也即当前任务未完成时,即使到了下一个调度周期,调度任务也不会执行。此种情况下可能出现任务异常阻塞。如果此时startingDeadlineSeconds参数也没有设置,就可能会出现经过100个调度周期后,该任务不再调度。
- spec.schedule: 调度周期,格式为标准的crontab格式[分 时 日 月 周]。
- spec.failedJobsHistoryLimit: 历史失败的任务数限制(通常可以保留1-2个,用于查看失败详情,以调整调度策略)。
- spec.successfulJobsHistoryLimit: 历史成功的任务数限制(可以自己决定保留多少个成功任务)。
- spec.jobTemplate: 标准的pod运行的模板(容器运行时的相关参数)。
- spec.suspend: 可选参数,如果设置为true,所有后续的任务都会被暂停执行,该参数不适用于已经运行的任务,默认为False。
CronJob示例
下面是一个定时任务示例:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
labels:
run: dnsall
name: dnsall
namespace: myapp
spec:
# 强烈建议设置并发策略,根据调度周期和任务特性进行设置
concurrencyPolicy: Forbid
# 强烈建议设置失败任务数,用于排查任务失败根因,以优化任务
failedJobsHistoryLimit: 1
successfulJobsHistoryLimit: 3
# 强烈建议设置错过调度的计算时间
startingDeadlineSeconds: 600
# 调度周期
schedule: '05,15,25,35,45,55 */1 * * *'
suspend: false
jobTemplate:
metadata:
spec:
template:
metadata:
labels:
run: dnsall
spec:
imagePullSecrets:
- name: mydocker
containers:
- args:
- -cmdbtype
- dns
image: harbor.bgbiao.top/cron-job:2019-12-04
imagePullPolicy: Always
name: dnsall
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
# 强烈建议设置任务的重启策略(任务的失败会触及到Jobs控制器中的Backofflimit参数,导致job失败)
restartPolicy: OnFailure
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
查看调度任务:
$ kubectl get cronjob -n myapp
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
dnsall 05,15,25,35,45,55 */1 * * * False 0 8m41s 23h
cronjob其实定期的创建了job,因此具体的任务pod其实是由job控制器来维护的。
这里可以看到,上面的cronjob保存的三个执行成功的任务
$ kubectl get jobs -n myapp | grep dns
dnsall-1577597100 1/1 23s 22m
dnsall-1577597700 1/1 24s 12m
dnsall-1577598300 1/1 24s 2m22s
再查看一个job真正管理的pod任务的执行(任务已经已完成,所以期望值为1,当前值为0):
$ kubectl get pods -n myapp | grep dnsall-1577598300
dnsall-1577598300-hdl4z 0/1 Completed 0 3m29s
参考资料
[1]. https://kubernetes.io/zh/docs/concepts/overview/
[2]. https://blog.csdn.net/weichuangxxb/article/details/103754021