容器 cron 9 点变 17 点:默认 UTC、DST 漂移与 K8s CronJob timeZone
现象与根因:cron 读的是系统时区
cron 默认使用系统时区 /etc/localtime。容器镜像没有显式设置时,多数是 UTC,于是:开发机是 Asia/Shanghai,部署到容器后仍写 0 9 * * *,本意是北京时间 9:00,实际却在北京时间 17:00(UTC 09:00)才跑。
更隐蔽的是集群多台机器:有的设过 Asia/Shanghai,有的忘了,同一条 cron 在不同节点会错开 8 小时。
修复:显式设时区,或按 UTC 换算
Dockerfile 里固定时区并同步 /etc/localtime:
ENV TZ=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
不想碰镜像,就把 cron 时刻直接按 UTC 换算:本意北京 9:00 就写 0 1 * * * UTC。
K8s CronJob:1.27 起才有 timeZone 字段
1.27 之前的 CronJob 没有 timeZone 字段,调度按 kube-controller-manager 所在节点时区走(多数镜像默认 UTC)。要指定时区:
spec.timeZone,1.25 进入 beta,1.27 GA。
升级前,只改 CronJob 里的 cron 表达式不解决集群内时区不一致问题;1.27+ 直接显式声明 timeZone 更干净。
夏令时:2:30 要么不存在,要么出现两次
以美国夏令时为例:
| 场景 | 时刻 | cron 行为 |
|---|---|---|
| 春季拨快 | 02:00 变 03:00,02:30 不存在 | 多数 cron 实现不执行,或顺延到 03:00 |
| 秋季拨慢 | 02:00 回拨到 01:00,02:30 出现两次 | 默认可能执行两次 |
Vixie cron(Linux 默认 cron)文档对「跳过/重复」有明确定义;Quartz、Spring、K8s 的语义各有差异。Asia/Shanghai 全年不实行 DST,不受影响,但容器和调度器一旦碰到其他时区就要留意。
CRON_TZ:任务级时区
不想改系统 /etc/localtime,可以在 crontab 顶部为单条任务指定时区:
CRON_TZ=Asia/Shanghai
0 9 * * * /path/to/script
关键任务务必避开 02:00–03:00 窗口,放到 04:00 之后最安全。
anacron:关机后的补跑
anacron 用于系统在计划时间没运行(比如关机)时,下次启动补跑错过的任务。配合 DST 的重复/跳过,补跑机制可能让同一任务出现多跑或晚跑,业务侧需要幂等保护和看门狗:按时间窗口或任务标记去重,再辅以流水线水位告警,不能只依赖 cron 语义兜底。