<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CronJob · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/cronjob/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/cronjob/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes CronJob 需要历史清理策略</title><link>https://www.xbcatm.com/zh/posts/kubernetes-cronjob-cleanup-hooks/</link><pubDate>Fri, 25 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/kubernetes-cronjob-cleanup-hooks/</guid><description>CronJob 默认历史、Job TTL、所属关系和外部日志分别约束不同保留边界。</description><content:encoded>&lt;p&gt;CronJob 按计划创建 Job，再由 Job 管理真正执行工作的 Pod。完成后的对象可以保留有用状态，但保留量受到多种独立机制影响。尤其要注意，CronJob 本来就有默认历史上限；省略显式历史字段，并不意味着普通已完成运行会无限堆积。分析清理问题时，先分清对象类型与状态，再判断是哪项机制没有覆盖。&lt;/p&gt;
&lt;h2 id="历史记录不等于日志归档"&gt;历史记录不等于日志归档&lt;/h2&gt;
&lt;p&gt;CronJob 默认最多保留三个成功 Job 和一个失败 Job，控制器分别协调两类记录。某类上限设为零，表示不保留该类历史，但清理是异步过程，不是任务结束那个瞬间必然消失。仍在运行的 Job 不属于完成历史，需要单独观察，不能期待这两个数量上限替它们设置运行期限。&lt;/p&gt;
&lt;p&gt;三十天每小时一次意味着七百二十个计划机会，不一定保留七百二十个 Job，更不一定恰好产生相同数量的 Pod。错过调度、并发策略、重试与并行度都会改变关系，一个 Job 也可能拥有多个 Pod。用运行次数估算存储或控制面容量之前，应把这些假设说清楚，不能把理想时间表直接当成当前对象数量。&lt;/p&gt;
&lt;p&gt;Pod 对象仍在，也不保证容器日志仍然可读。节点故障、日志轮转、容器清理和驱逐都会影响访问。真正需要按时长保留的证据，应交付到合适的日志系统。Kubernetes 对象便于调查状态，但不能替代归档保证；只延长 Job 历史，不会自动解决节点日志的生命周期问题。&lt;/p&gt;
&lt;p&gt;大量历史仍有对象存储、列表大小和排查噪声等成本。Watch 接收相关变化，不会因为一个对象仍存在，就持续重新发送它所有未变化的状态。应测量真实对象数和控制面行为，避免把保留状态描述为永不停歇的控制器活动。调查困难可能值得优化，但应使用正确机制解释成本。&lt;/p&gt;
&lt;h2 id="数量与年龄是独立策略"&gt;数量与年龄是独立策略&lt;/h2&gt;
&lt;p&gt;CronJob 历史限制的是已结束 Job 的数量，不是至少保留多久。密集运行可能很快替换最后一条失败记录，稀疏运行则可能保留很长时间。应根据查看间隔和失败频率选择数量，不能认为保留一次失败就足够覆盖所有工作负载，也不能把数量设置说成按时间执行的保留窗口。&lt;/p&gt;
&lt;p&gt;TTL-after-finished 写在 Job 上。若希望应用到今后由 CronJob 创建的任务，应将 ttlSecondsAfterFinished 放在 Job 模板的 spec 中。计时从 Job 达到 Complete 或 Failed 开始，过期只代表具备级联删除资格。零表示完成后立即具备资格，省略则没有这项按年龄清理的规则，不能混淆省略与零的含义。&lt;/p&gt;
&lt;p&gt;历史控制器与 TTL 都可能删除同一个 Job。因此，历史数量很大或 TTL 很长，都不保证最低保留时长，只要另一机制允许更早删除即可。Finalizer 和控制器进度还可能推迟实际消失。修改 CronJob 模板影响未来 Job，不会替所有旧 Job 自动重写现存 TTL；验证时应分别读取模板与既有对象。&lt;/p&gt;
&lt;h2 id="所属关系决定级联行为"&gt;所属关系决定级联行为&lt;/h2&gt;
&lt;p&gt;CronJob 创建的 Job 引用其所有者，Job 与 Pod 之间也形成从属关系。普通级联删除可以沿这条链清理。显式 orphan 删除则保留从属对象，并移除相关所有者链接，不是故意留下普通悬空引用再等待垃圾收集器追赶。两种删除方式具有不同结果，操作前应核对传播策略。&lt;/p&gt;
&lt;p&gt;Job 被孤立后，原 CronJob 不再裁剪它们的历史，但已经配置的 TTL 仍可作用于各个完成任务。直接创建的 Job 同样需要明确保留决策。不同客户端的删除选项不能凭记忆统一推断，也不能认为孤儿马上就会消失。确认谁负责清理，比仅仅看到一个空 owner 字段更重要。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;始终没有进入终止条件的 Job，不会因完成后 TTL 获得清理资格，历史上限也不会限制活动任务。合适的 activeDeadlineSeconds 可以约束运行时间，concurrencyPolicy 则控制计划任务重叠。它们解决不同问题，而且都不能证明底层业务重复执行安全；重试、副作用和幂等性仍然需要应用负责。&lt;/p&gt;
&lt;p&gt;历史上限为零可能在调查前就移除失败记录。缩短保留前，应把必要日志与结果文件保存在其他地方。反过来，所有者关系不存在或 TTL 缺失，只是需要调查的信号，不能直接授权删除；其他控制器或业务保留要求可能有意承担清理责任，不能把所有未自动消失的记录都当成泄漏。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下保留的只读诊断本轮没有重新执行。应明确选择临时测试上下文和命名空间，并对照存储的 CronJob、它实际拥有的 Job、完成条件与创建时间。命名空间总数包含无关工作负载和活动 Job，因此不能拿它直接与单个 CronJob 的两项历史上限比较。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get cronjob hourly-rollup -o jsonpath=&amp;#39;{.spec.successfulJobsHistoryLimit} {.spec.failedJobsHistoryLimit} {.spec.jobTemplate.spec.ttlSecondsAfterFinished}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get jobs --sort-by=.metadata.creationTimestamp --no-headers | wc -l
kubectl get pods --field-selector=status.phase=Succeeded --no-headers | wc -l
kubectl get jobs -o jsonpath=&amp;#39;{range .items[*]}{.metadata.name} ttl={.spec.ttlSecondsAfterFinished} owner={.metadata.ownerReferences[0].name}{&amp;#34;\n&amp;#34;}{end}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;即使源清单省略历史字段，第一行通常也会显示 API 默认值。空输出不证明无限保留，应检查查询错误和完整对象。最后一行只显示第一个所有者引用。TTL 与 owner 同时为空，只说明尚未证明这两项机制负责清理，不表示永远不可能删除。跨多个完成周期复查，期望所属完成历史在协调后受限，同时单独评估活动任务与无关对象。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/"&gt;Kubernetes Job&lt;/a&gt;，&amp;ldquo;Clean up finished jobs automatically&amp;rdquo; 与已完成 Job 的 TTL 机制，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/"&gt;Kubernetes CronJob&lt;/a&gt;，默认历史上限与 job template，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/architecture/garbage-collection/"&gt;Kubernetes 垃圾回收&lt;/a&gt;，owner reference 与级联删除，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/"&gt;Primary reference&lt;/a&gt;，TTL 资格与 finalizer，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/cluster-administration/logging/"&gt;Primary reference&lt;/a&gt;，日志轮转与节点保留，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>CronJob 把时间表变成 Job</title><link>https://www.xbcatm.com/zh/posts/k8s-cronjob-schedules-batch-work/</link><pubDate>Fri, 17 Dec 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/k8s-cronjob-schedules-batch-work/</guid><description>CronJob 近似按时间表创建 Job；并发策略、启动截止时间与业务幂等性共同决定批处理是否有用。</description><content:encoded>&lt;p&gt;CronJob 把一份 Job 模板与循环时间表组合起来。CronJob 控制器负责创建 Job，再由 Job 管理执行工作的 Pod。一次运行可以包含重试、替换 Pod，或按照 Job 配置启动多个并行工作者，不应把它简化为每次只启动一个短命容器。两次运行之间不需要专门的应用进程等候触发，但已经完成的 Job 和 Pod 可能继续保留，供排查与审计使用。&lt;/p&gt;
&lt;p&gt;时间表只是近似兑现。某个时刻可能生成重复 Job，也可能完全漏掉，因此不能无条件承诺每个计划时刻都至少执行一次。应用既要能安全重复执行，也要记录哪些业务周期真正完成。幂等性解决重复效果，独立的完整性检查解决根本没有运行的工作，这两个问题不能互相替代。&lt;/p&gt;
&lt;h2 id="时间表产生的是-job"&gt;时间表产生的是 Job&lt;/h2&gt;
&lt;p&gt;五个字段依次表示分、时、日、月与星期，支持区间、步长和 &lt;code&gt;@daily&lt;/code&gt; 等宏。&lt;code&gt;*/10 * * * *&lt;/code&gt; 表示每十分钟的计划时刻，并不保证 Pod 恰好在那个瞬间开始执行。即使 Job 已经建立，节点资源不足、镜像拉取或调度过程仍可能让实际工作晚于计划。&lt;/p&gt;
&lt;p&gt;如果业务依赖当地钟表时间，应明确设置 &lt;code&gt;spec.timeZone&lt;/code&gt;。省略时使用控制器管理器的本地时区；换一台机器本身不会必然改变行为，只有相关时区配置变化才会影响解释。不要把 TZ 或 CRON_TZ 塞进 schedule 字符串，应使用专门字段。夏令时切换以及报表所覆盖的业务日期，仍需要应用自己的明确规则，不能靠时区名称代替业务定义。&lt;/p&gt;
&lt;p&gt;CronJob 名称应控制在五十二个字符以内，为生成 Job 名称时追加的后缀留空间。从 Kubernetes 1.32 起，生成的 Job 带有 &lt;code&gt;batch.kubernetes.io/cronjob-scheduled-timestamp&lt;/code&gt;，可以把原定时间与创建、启动时间比较，判断延迟发生在哪一层。这个注解是诊断依据之一，并非唯一审计来源，更不能证明备份、报表或其他业务结果已经成功完成。&lt;/p&gt;
&lt;h2 id="并发策略只约束同一个-cronjob"&gt;并发策略只约束同一个 CronJob&lt;/h2&gt;
&lt;p&gt;默认的 &lt;code&gt;Allow&lt;/code&gt; 允许 Job 重叠。&lt;code&gt;Forbid&lt;/code&gt; 在前一次仍活跃时拒绝新的启动；跳过的时间计入漏跑，之后是否还能启动仍取决于截止时间。&lt;code&gt;Replace&lt;/code&gt; 则用新的 Job 替换当前 Job。这些策略不会协调另一个 CronJob，也不会阻止集群外的工作者同时修改相同数据。&lt;/p&gt;
&lt;p&gt;如果任务每次都比调度间隔长，Replace 可能不断打断尚未完成的工作。Forbid 用漏掉启动机会换取减少重叠，并没有建立一个持久任务队列。对于重复写入有害的操作，应设计业务级去重或协调，同时考虑替换期间已经提交了一半的结果。持有一把锁本身不代表中途失败之后就能安全继续，更不代表漏掉的业务周期会自动补齐。&lt;/p&gt;
&lt;h2 id="启动期限挂起与历史保留"&gt;启动期限、挂起与历史保留&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;startingDeadlineSeconds&lt;/code&gt; 限制的是允许多晚开始，而不是任务最多运行多久。不设置时，每次计划没有单独的启动截止时间。概念文档描述超过一百次漏跑时拒绝补启动，但此次核对的 1.34.0 调度辅助函数会产生警告并返回最近的合格时刻，并不单凭这个计数拒绝启动。应核对服务端版本，不要看到警告就推断永久停摆。设置启动期限后，统计窗口受该期限限制；小于十秒的值可能错过控制器检查，不能当作精确实时调度器。&lt;/p&gt;
&lt;p&gt;挂起会停止创建新 Job，但不会终止已存在的 Job。挂起期间的计划时间也算漏跑，所以恢复时可能立即发生补调度，仍受其他规则限制。不要把这理解成每个历史时刻都必然对应一个补建 Job。若每一期报表都必须处理，应使用持久的业务工作清单记录完成情况，而不是把 CronJob 历史当作可靠队列。&lt;/p&gt;
&lt;p&gt;历史保留默认留下三个成功 Job 和一个失败 Job。它控制的是已完成对象的保留数量，不是同时运行数，也不替代执行期限。修改模板只影响将来创建的 Job；已经启动的 Job 继续使用自己的规格。调度配置、单次任务配置和结果保留策略需要分别审阅，才能知道一次变更真正影响哪批工作。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;一个运行二十五分钟的任务放在十分钟时间表上，使用 Allow 时可能出现重叠和重复写入。应查看真实所属关系、活跃 Pod 与完成记录，再判断业务重复是否由调度造成。启动期限应该反映结果迟到多久仍然有用，而不是随便选一个数字，只为了让控制器不再产生警告。&lt;/p&gt;
&lt;p&gt;lastScheduleTime 长时间不更新也可能是预期行为：挂起、Forbid、稀疏的日历表达式或时区改变，都可能解释这个现象。事件会过期，成功创建 Job 也不等于备份可恢复。监控应同时关注最后一次经过验证的可用结果，以及调度本身的健康，避免每天都有绿色 Job，却从未检查产物是否真的能用。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;在指定的只读集群上下文与命名空间中，检查已有的 &lt;code&gt;nightly-report&lt;/code&gt;。下列命令保留自原文，本轮没有在真实集群重新执行。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get cronjob nightly-report -o jsonpath=&amp;#39;{.spec.schedule} {.spec.concurrencyPolicy} {.spec.startingDeadlineSeconds}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get cronjob nightly-report -o jsonpath=&amp;#39;{.status.lastScheduleTime}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get jobs --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,START:.status.startTime,SUCCEEDED:.status.succeeded
kubectl get events --field-selector involvedObject.kind=CronJob --sort-by=.lastTimestamp | tail -5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Job 列表还会包含同一命名空间里的其他任务，判断之前必须通过 ownerReferences 核对所属 CronJob。只有 START 时间不能证明运行重叠，SUCCEEDED 为空也不能证明任务仍在执行；还要结合 active、failed 与完成条件，以及对应的原定时间。时间间隔要按实际日历和策略解释，保留的事件没有说明时，再寻找控制器日志，不能根据一条缺失记录直接认定整个控制面停过。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/"&gt;Kubernetes CronJob&lt;/a&gt;，调度语法、并发策略、漏跑一百次的规则与历史上限，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/kubernetes-api/batch/cron-job-v1/"&gt;CronJob v1 API 参考&lt;/a&gt;，CronJob spec 的字段级定义，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/kubernetes/kubernetes/v1.34.0/pkg/controller/cronjob/utils.go"&gt;Kubernetes 1.34.0 CronJob scheduling helper&lt;/a&gt;, mostRecentScheduleTime and nextScheduleTime select the latest missed time and warn above 100，已核验 2026-10-15。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>