Cron 表达式本身没错,跑错点的锅常常在时区。尤其是”跨国部署”或”容器默认 UTC”的场景,同一条 Cron 能在不同实例上撞到完全不同的时刻。
三种最常见的时区翻车
1. 开发机和生产机时区不同
开发机是 Asia/Shanghai,部署容器默认 UTC。0 9 * * * 本意是北京 9 点,实际在北京下午 5 点(UTC 9 点)才跑。
排查:
cat /etc/timezone # Debian / Ubuntu
timedatectl # systemd
date # 终极确认
ls -l /etc/localtime # 链到哪个 zoneinfo
修复两条路:
- Dockerfile 显式设:
ENV TZ=Asia/Shanghai+RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime - 或把 Cron 时刻按 UTC 算回去:北京 9 点 = UTC 1 点 →
0 1 * * *
推荐前者,减少心算错误和新人踩坑。
2. 多机房 / 多实例时区不一致
集群 10 台机器,有的设了 Asia/Shanghai 有的忘了。同一条 Cron 在不同机器错开 8 小时——每台都按”本地 9 点”跑,实际看起来”每天有好几个时刻都在跑”。
修复:
- 基础设施层统一时区(Ansible、镜像模板里固化)
- 更稳的做法是把调度搬到 app 层(Quartz / XXL-Job / Elastic-Job / Airflow),集中读一个时区配置,不依赖机器本地
3. K8s CronJob 1.27 之前没有 timeZone
1.27 之前 K8s CronJob 没有 timeZone 字段,走的是 kube-controller-manager 所在节点的时区(多数镜像默认 UTC)。“凌晨 1 点结算”一不小心变成”上午 9 点结算”。
1.27+ 加了原生支持:
spec:
timeZone: "Asia/Shanghai"
schedule: "0 1 * * *"
但要确认 controller 的 CronJobTimeZone feature gate 打开(1.27 默认开)。1.25 以下版本只能让 Pod 内部时区对齐(镜像设 TZ)或按 UTC 换算。
DST 夏令时漂移
部署在 America/New_York、Europe/London、Australia/Sydney 等地区才遇得到,但遇上就很疼。DST 春天把时钟拨快 1 小时(通常凌晨 2:00 → 3:00),秋天拨慢 1 小时(2:00 → 1:00)。
对 Cron 的影响:
| 表达式 | 春季(跳过 2-3 点) | 秋季(2-3 点重复) |
|---|---|---|
0 2 * * *(固定 2 点) | 不执行或顺延到 3:00 | 执行两次 |
0 * * * *(每小时整点) | 缺一次 | 多一次 |
*/5 * * * *(每 5 分钟) | 缺 12 次 | 多 12 次 |
具体行为看实现——Vixie cron(Linux 默认)文档里明确定义了跳过/重复规则,Quartz、Spring、K8s 各自略有差异。
中国大陆自 1992 年起取消 DST,Asia/Shanghai 全年不会发生。但上了 AWS us-east / eu-west 或选了 EST / CET / PST 时区的机器就逃不掉。
关键任务避开 02:00–03:00 这个窗口,放 04:00 之后最安全——全球所有 DST 切换都在这之前完成。
Cron 写不出的几种时间
| 需求 | Linux | Quartz | 替代方案 |
|---|---|---|---|
| 每月最后一天 | ✗ | L(0 0 L * ?) | 脚本判断 [ $(date -d tomorrow +%d) = 01 ] 再执行 |
| 每月第 N 个周 X | ✗ | #(0 0 ? * 2#1=每月第一个周一) | app 层判断 weekday == MON && day <= 7 |
| 工作日(跳过节假日) | ✗ | ✗ | 查节假日表 / 借助 chinese-calendar 类库 |
| 秒级 | ✗ | 6 字段 | systemd timer + OnUnitActiveSec=10s,或 app 内 setInterval |
| 每台机器错峰(避免雷同打) | ✗ | ✗ | 脚本开头 sleep $((RANDOM % 60)) |
Linux Cron 本身很”纯粹”,复杂调度交给 Quartz、应用层调度或外部调度系统是正确方向。
部署检查清单
- 明确时区:Dockerfile / K8s 里
TZ,或 Cron 用 UTC 换算,二选一讲清楚 - 避开 DST 窗口:美欧部署不要把任务放
02:00–03:00,改04:00之后 - 看未来执行时间:拿 Cron 解析器预览 5 次,带时区一起看
- 关键任务加看门狗:任务应在某时刻前上报心跳,否则告警——防止调度器默默不跑
- 幂等化任务:即使 DST 重复执行两次,业务层靠幂等 key / 分布式锁去重
一句话
Cron 表达式描述”什么时刻”,但”在哪个时区”和”在哪台机器上”才决定它真正跑在何时。写完 Cron 对着解析器填上时区再看一眼——一分钟省未来一整个排查上午。