面试官:“计数性Job默认完成模式是什么?Indexed模式如何发布自定义索引呢?”
面试官:“k8s的Job Pod 中的容器可能因为多种不同原因失效,想要更加稳定的使用Job负载,有哪些可以注意的地方?“
面试官:“为什么k8s建议在调试 Job 时将
restartPolicy
设置为 "Never"?”面试官:“Job 终止与清理了解嘛?Pod重试次数还未 达到
backoffLimit
所设的限制,为什么突然被终止了?猜测原因?“
计数性Job默认完成模式是无索引模式NonIndexed
。
实际上,带有 确定完成计数 的 Job,即 .spec.completions
不为 null 的 Job, 都可以在其 .spec.completionMode
中设置完成模式:NonIndexed
(默认)和Indexed
两种。
先看默认模式NonIndexed,无索引模式
1、每个Job完成事件都是独立无关且同质的
2、成功完成的Pod个数达到.spec.completions值时认为Job已经完成
3、当.spec.completions取值null时,Job被隐式处理为NonIndexed
再看Indexed,索引模式
1、Job 的 Pod 会分配对应的完成索引
2、索引取值为 0 到.spec.completions-1
3、当每个索引都对应一个完成的 Pod 时,Job 被认为是已完成的
4、同一索引值可能被分配给多个Pod,但是只有一个会被记入完成计数
对于索引模式来说,我下发10个索引,我不关注10个索引分别由多少个Pod去完成,我只关注10个索引任务是否按需完成即可。
Indexed模式下,索引有三种获取方式:
第一种:基于注解,Pod 索引在注解 batch.kubernetes.io/job-completion-index
中呈现,具体表示为一个十进制值字符串。
第二种:基于主机名,作为 Pod 主机名的一部分,遵循模式 $(job-name)-$(index)
。当你同时使用带索引的 Job(Indexed Job)与服务(Service), Job 中的 Pods 可以通过 DNS 使用确切的主机名互相寻址。
第三种:基于环境变量,对于容器化的任务,在环境变量 JOB_COMPLETION_INDEX
中体现。
Indexed模式如何发布自定义索引呢?
上面提到了三种获取索引的方式:注解,主机名,环境变量。
Downward API
机制有两种方式可以把将 Pod 和 Container
字段信息呈现给 Pod 中运行的容器:
环境变量
卷文件
你使用 Job 控制器为所有容器设置的内置 JOB_COMPLETION_INDEX
环境变量。Init 容器将索引映射到一个静态值,并将其写入一个文件,该文件通过 emptyDir 卷与运行 worker 的容器共享。
举例
定义使用带索引完成信息的 Job 清单。
Downward API
将 Pod 索引注释作为环境变量或文件传递给容器。例如环境变量控制平面自动设置 downward API 以在 JOB_COMPLETION_INDEX
环境变量中公开索引
根据该清单启动一个带索引(Indexed
)的 Job。
首先需要理解的是,失效有两种形式,需要适配的能力也不同。
第一种Pod管理的部分容器失效
第二种Pod失效
第一种Pod管理的部分容器失效
Pod 中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非零, 或者容器因为超出内存约束而被杀死等。
如果发生这类事件,并且 .spec.template.spec.restartPolicy = "OnFailure"
, Pod 则继续留在当前节点,但容器会被重新运行。
面对这种场景,你的程序需要具备能够处理在本地被重启的情况的能力,或者容器设置 .spec.template.spec.restartPolicy = "Never"
注意,即使你将 .spec.parallelism
设置为 1,且将 .spec.completions
设置为 1,并且 .spec.template.spec.restartPolicy
设置为 "Never",同一程序仍然有可能被启动两次,程序猿思维:“永远不要假想某某情况不会发生”。
它就发生了,你能咋滴,不管啊???
第二种Pod失效
整个 Pod 也可能会失败,且原因各不相同。
例如,当 Pod 启动时,节点失效(被升级、被重启、被删除等)
或者其中的容器失败并且设置了 .spec.template.spec.restartPolicy = "Never"
。
当 Pod 失败时,Job 控制器会启动一个新的 Pod 替身,去接替失败的Pod未处理完成的工作。
这意味着,你的应用需要处理在一个新 Pod 中被重启的情况。尤其是应用需要处理之前运行所产生的临时文件、锁、不完整的输出等问题。
再次注意
如果你将 .spec.parallelism
和 .spec.completions
都设置为比 1 大的值, 那就有可能同时出现多个 Pod 运行的情况。
为此,你的 Pod 也必须能够处理并发性问题☺️。
回答这个问题前,先看下Job Pod 回退失效策略
在有些情形下,你可能希望 Job 在经历若干次重试之后直接进入失败状态,因为这很可能意味着Job遇到了配置错误。
.spec.backoffLimit
字段设置Job Pod 回退失效策略,标识Job失败重试次数,失效回退的限制值默认为 6。
与 Job 相关的失效的 Pod 会被 Job 控制器重建,同时回退重试时间将会按指数增长 (从 10 秒、20 秒到 40 秒)最多至 6 分钟。
当 Job 的 Pod 被删除,或者 Pod 成功时没有其它 Pod 处于失败状态,失效回退的次数也会被重置(为 0)。
好了,这下可以回答刚才的问题,为什么重启策略要设置为Never?
如果你的 Job 的 restartPolicy
被设置为 "OnFailure
",那么该 Job 管理的 Pod 会在 Job 到达失效回退次数上限时自动被终止。
Pob 被终止,那么调试 Job 中可执行文件的工作变得非常棘手,难以把控。也许你刚调试没多久,结果Pod终止了,调试过程中断了,绝望不!!!
为了解决Pod终止后 Jobs 的输出遗失掉的问题,k8s建议在调试 Job 时将 restartPolicy
设置为 "Never", 或者使用日志系统来确保失效 Jobs 的输出不会意外遗失。
Job终止和清理策略
Job 完成时不会再创建新的 Pod,不过已有的 Pod 也不会被删除。
保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告 或者其它诊断性输出。
Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。
删除老的 Job 的操作留给了用户自己,在查看了 Job 状态之后,你可以使用 kubectl
来删除 Job(例如,kubectl delete jobs/pi
或者 kubectl delete -f ./job.yaml
)。当使用 kubectl
来删除 Job 时,该 Job 所创建的 Pods 也会被删除。
默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never
) 或者某个容器出错退出(restartPolicy=OnFailure
)。这时,Job 基于前述的 spec.backoffLimit
来决定是否以及如何重试。一旦重试次数到达 .spec.backoffLimit
所设的上限,Job 会被标记为失败, 其中运行的 Pods 都会被终止。
终止 Job 的另一种方式是设置一个活跃期限。你可以为 Job 的 .spec.activeDeadlineSeconds
设置一个秒数值。该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止,并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
注意 Job 的 .spec.activeDeadlineSeconds
优先级高于其 .spec.backoffLimit
设置。因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds
所设的时限,即不再部署额外的 Pod,即使其重试次数还未达到 backoffLimit
所设的限制。
注意问题
Job 规约和 Job 中的Pod 模版规约都有 activeDeadlineSeconds
字段。请确保你在合适的层次设置正确的字段。
还要注意的是,restartPolicy
对应的是 Pod,而不是 Job 本身:一旦 Job 状态变为 type: Failed
,就不会再发生 Job 重启的动作。换言之,由 .spec.activeDeadlineSeconds
和 .spec.backoffLimit
所触发的 Job 终结机制 都会导致 Job 永久性的失败,而这类状态都需要手工干预才能解决。