手记

TTL 机制排毒,线上k8s的Job已经通过API 增加了Job的TTL 时长,且成功响应,为什么系统还是清理了Job?

TTL 机制排毒,线上k8s的Job已经通过API 增加了Job的TTL 时长,且成功响应,为什么系统还是清理了Job?

面试官:"已完成 Job 的 TTL 机制了解嘛?简单说说TTL存在的时间偏差问题?"
面试官:"能简单描述一下什么是TTL-after-finished 控制器嘛?"
面试官:"我明明已经通过API 增加了Job的TTL 时长,且得到了成功的响应,为什么系统还是清理了Job?"
面试官:"如何更加准确的跟踪 Job 完成情况?了解 Finalizer 追踪 Job嘛?"
面试官:"说说什么场景下CronJob 无法被调度?"

囧么肥事-胡说八道



已完成 Job 的 TTL 机制了解嘛?简单说说TTL存在的时间偏差问题?

完成的 Job 通常不需要继续留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。

实际上自动清理完成的 Job有两种常规方式:

1、更高级别的控制器管理

2、已完成 Job 的 TTL 机制 

更高级别的控制器管理

如果 Job 由某种更高级别的控制器来管理,例如CronJobs, 则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。

已完成 Job 的 TTL 机制

自动清理已完成 Job (状态为 CompleteFailed)的另一种方式是使用由TTL-after-finished控制器所提供 的 TTL 机制。 通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉 已结束的资源

注意点一:TTL 控制器清理 Job 时,会级联式地删除 Job 对象。 换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。

注意点二:当 Job 被删除时,系统会考虑其生命周期保障,其生命周期函数也将被触发,例如 Finalizers

面试官:“能简单描述一下什么是TTL-after-finished 控制器嘛?”

TTL-after-finished 控制器只支持 Job。集群操作员可以通过指定 Job 的 .spec.ttlSecondsAfterFinished 字段来自动清理已结束的作业(Job状态为Complete或Failed)。

TTL-after-finished 控制器假设作业能在执行完成后的 TTL 秒内被清理,也就是当 TTL 过期后,当 TTL 控制器清理作业时,它将做级联删除操作,即删除资源对象的同时也删除其依赖对象。 注意,当资源被删除时,由该资源的生命周期保证其终结器(Finalizers)等被执行。

Job可以随时设置 TTL 秒,可以植入多种不同的需求场景,以下是设置 Job 的 .spec.ttlSecondsAfterFinished 字段的一些示例:

  • 在作业清单(manifest)中指定此字段,以便 Job 在完成后的某个时间被自动清除
  • 将此字段设置为现有的、已完成的作业,以采用此新功能。
  • 创建作业时使用 mutating admission webhook 动态设置该字段。集群管理员可以使用它对完成的作业强制执行 TTL 策略
  • 作业完成后使用 mutating admission webhook 动态设置该字段,并根据作业状态、标签等选择不同的 TTL 值。

字段解释 ttlSecondsAfterFinished

  • Job pi-with-ttlttlSecondsAfterFinished 值为 100,则在其结束 100 秒之后,Job将可以被自动删除
  • 如果 ttlSecondsAfterFinished 被设置为 0,则 TTL 控制器在 Job 执行结束后,立刻就可以清理该 Job 及其 Pod
  • 如果 ttlSecondsAfterFinished 值未设置,则 TTL 控制器不会清理该 Job

面试官:“我明明已经通过API 增加了Job的TTL 时长,且得到了成功的响应,为什么系统还是清理了Job?”

这里涉及到两个TTL的概念:时间偏差和更新TTL秒数周期

时间偏差问题

由于 TTL-after-finished 控制器使用存储在 Kubernetes 资源中的时间戳来确定 TTL 是否已过期, 该功能对集群中的时间偏差很敏感,所以,设置非零 TTL 时,可能导致 TTL-after-finished 控制器在错误的时间清理资源对象。

更新 TTL 秒数问题

在创建 Job 或已经执行结束后,仍可以修改其 TTL 周期,例如 Job 的 .spec.ttlSecondsAfterFinished 字段。

但是一旦 Job 变为可被删除状态(当其 TTL 已过期时),即使通过 API 增加其 TTL 时长得到了成功的响应系统也不保证 Job 将被保留

如何更加准确的跟踪 Job 完成情况?了解 Finalizer 追踪 Job嘛?

想要更加准确的跟踪 Job 完成情况,需要为API 服务器和控制器管理器启用 JobTrackingWithFinalizers特性,该特性默认是禁用的。

启用后,控制面会追踪新的 Job,对现有 Job 不受影响

未启用JobTrackingWithFinalizers特性前是如何跟踪Job完成情况的?

该功能未启用时,Job控制器依靠计算k8s集群中存在的 Pod 来跟踪作业状态。

也就是说,Job控制器需要去维护一个统计 succeededfailed 的 Pod 的计数器。

然而,Pod 可能会因为一些原因被移除,导致统计不准确:

  • 当一个节点宕机时,垃圾收集器会删除孤立(Orphan)Pod
  • 垃圾收集器在某个阈值后删除已完成的 Pod(处于 SucceededFailed 阶段)。
  • 人工干预删除 Job 的 Pod。
  • 一个外部控制器(不包含于 Kubernetes)来删除或取代 Pod。

启用JobTrackingWithFinalizers特性后是如何跟踪Job完成情况的?

如果集群启用了 JobTrackingWithFinalizers 特性,控制面会跟踪属于任何 Job 的 Pod。 并注意是否有任何这样的 Pod 被从 API 服务器上删除。 为了实现这一点,Job 控制器创建的 Pod 带有 Finalizer batch.kubernetes.io/job-tracking。 控制器只有在 Pod 被记入 Job 状态后才会移除 Finalizer,允许 Pod 可以被其他控制器或用户删除。

注意:Job 控制器只对新的 Job 使用新的算法。在启用该特性之前创建的 Job 不受影响。 你可以根据检查 Job 是否含有 batch.kubernetes.io/job-tracking 注解,来确定 Job 控制器是否正在使用 Pod Finalizer 追踪 Job。 注意不应该给 Job 手动添加或删除该注解。

前面你提到了CronJobs负载,它编排的Job为什么需要是幂等的?

CronJob 创建基于时隔重复调度的 Jobs。CronJob 用于执行周期性的动作,例如备份、报告生成等。 你可以定义任务开始执行的时间间隔,这些每一个任务都应该配置为周期性重复的(例如:每天/每周/每月一次);

CronJob 根据计划编排,在每次该执行任务的时候大约会创建一个 Job。 之所以说 “大约”,是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。 k8s 试图使这些情况尽量少发生,但暂时还不能完全杜绝。因此,Job 应该是 幂等的

注意:如果 startingDeadlineSeconds 设置为很大的数值或未设置(默认),并且 concurrencyPolicy 设置为 Allow,则作业将始终至少运行一次。

思考什么场景下CronJob 无法被调度?

无法调度,第一种,startingDeadlineSeconds值低于CronJob 周期检查时间,第二种,多次错过调度。

如果 startingDeadlineSeconds 的设置值低于 10 秒钟,CronJob 可能无法被调度。 因为 CronJob 控制器每 10 秒钟执行一次检查。

对于每个 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 设置的值到现在而不是从上一个计划时间到现在错过了多少次 Job。

例如,如果 startingDeadlineSeconds200,则控制器会统计在过去 200 秒中错过了多少次 Job。

如果未能在调度时间内创建 CronJob,则计为错过。 例如,如果 concurrencyPolicy 被设置为 Forbid,并且当前有一个调度仍在运行的情况下, 试图调度的 CronJob 将被计算为错过。

例如,假设一个 CronJob 被设置为从 08:30:00 开始每隔一分钟创建一个新的 Job,并且它的 startingDeadlineSeconds 字段未被设置。

如果 CronJob 控制器从 08:29:0010:21:00 终止运行,则该 Job 将不会启动,因为其错过的调度 次数超过了 100。

进一步分析

假设将 CronJob 设置为从 08:30:00 开始每隔一分钟创建一个新的 Job,并将其 startingDeadlineSeconds 字段设置为 200 秒。

如果 CronJob 控制器恰好在与上一个示例相同的时间段(08:29:0010:21:00)终止运行, 则 Job 仍将从 10:22:00 开始。

造成这种情况的原因是控制器现在检查在最近 200 秒(即 3 个错过的调度)中发生了多少次错过的 Job 调度,而不是从现在为止的最后一个调度时间开始。

这里理解一个概念

CronJob 仅负责创建与其调度时间相匹配的 Job,而 Job 又负责管理其代表的 Pod。

Kubernetes 推荐学习书

Kubernetes权威指南PDF
链接:https://pan.baidu.com/s/11huLHJkCeIPZqSyLEoUEmQ 提取码:sa88

k8s系列所有问题更新记录:GitHub

0人推荐
随时随地看视频
慕课网APP