你写的 0 点,可能不是服务器的 0 点

举报
yd_235578340 发表于 2026/10/26 13:21:09 2026/10/26
【摘要】 一、你写的 0 点,可能不是服务器的 0 点 这是半夜失败率最高的原因,也是最容易被忽略的。容器镜像默认是 UTC, crontab 按进程自身的时区解析表达式,你在本地 date 看到的是北京时间,容器里跑出来的却是

一、你写的 0 点,可能不是服务器的 0 点

这是半夜失败率最高的原因,也是最容易被忽略的。容器镜像默认是 UTC,crontab 按进程自身的时区解析表达式,你在本地 date 看到的是北京时间,容器里跑出来的却是 UTC 16:00。更隐蔽的是:把 TZ=Asia/Shanghai 写在脚本第一行是无效的,因为 cron 在启动子进程之前就已经解析完时间了。

  • 先确认:docker exec -it xxx date,以及在 cron 里加一条 * * * * * date >> /tmp/tz.log 对比。
  • 正确做法:在 crontab 顶部写 TZ=Asia/Shanghai(部分 cron 实现支持 CRON_TZ),或者干脆在编排层给容器设 TZ 环境变量。
  • 夏令时地区还要注意:0 2 * * * 在春季时钟前跳的那天根本不会触发,秋季则可能触发两次。改用 systemd timer 的 Persistent=true,关机或跳过的任务会在开机后补跑一次。

二、凌晨是全公司最拥挤的时间窗口

你以为半夜机器空闲,实际上 00:00–03:00 是所有默认配置的集中爆发点:数据库全量 mysqldump、logrotate 切割压缩、云厂商磁盘快照、ES 索引滚动、K8s 镜像预热,全都挤在这三小时。

典型症状是任务本身逻辑没错,但报 Lock wait timeout exceeded、connection refused、S3 上传超时。我们遇到过一次:备份任务把磁盘 IO 打满,导致同一时刻的报表任务读配置表超时,白天重跑一次就正常。

  • 错峰:把非强时效任务挪到 04:00 之后,或用 sleep $((RANDOM % 600)) 打散整点并发。
  • 降优先级:nice -n 10 ionice -c2 -n7 python job.py。
  • 给数据库连接显式设置 connectTimeout 和 socketTimeout,不要用默认的无限等待。

三、跨天那一刻,日期变量会“变心”

23:50 启动、跑 20 分钟的任务,会在执行途中跨过零点。如果脚本里多处调用 date +%Y%m%d,前半段写到昨天的分区,后半段写到今天的,清理任务再按“昨天”去删,数据就丢了。

修法很简单:脚本开头只取一次,后续全部复用——RUN_DATE=$(date +%F),并且显式传参给下游。

另一个半夜专属问题是任务重叠:前一次没跑完,下一次又被拉起,两个进程抢同一批数据。别用 ps aux | grep 判断,会有竞态:

  • flock -n /var/lock/report.lock -c 'python report.py',拿不到锁直接退出。
  • K8s CronJob 用 concurrencyPolicy: Forbid 配合 startingDeadlineSeconds。
  • 无论如何都要保证任务幂等,失败重跑不会产生重复数据。

四、cron 的 shell 不是你的 shell

“我手动跑明明没问题”几乎都出在这里。cron 的 PATH 通常只有 /usr/bin:/bin,node、pyenv、nvm 一律找不到;它也不加载 .bashrc / .profile,所以 DB_PASSWORD、API_KEY 全是空的——白天你手动测试时有,半夜自然就失败。

  • 脚本首行 #!/bin/bash 后立刻加 set -euo pipefail,避免静默失败。
  • 需要环境变量就显式 source /etc/profile.d/app.sh,不要指望 shell 帮你加载。
  • 所有命令写绝对路径,cd "$(dirname "$0")" 固定工作目录。
  • 输出必须重定向:>> /var/log/report.log 2>&1,否则报错信息无处可寻。

五、没有观测的定时任务等于不存在

cron 默认把 stdout 通过本地 MTA 发邮件,容器里没有 MTA,输出就被直接丢弃。所以很多任务不是“半夜失败”,而是失败了三个月没人知道。

真正该监控的不是进程是否在跑,而是最后一次成功时间。把每次执行的 exit code、耗时、处理条数打点到 Prometheus Pushgateway 或一张 job_runs 表,再用规则告警:“last_success_at 距今超过 26 小时”。这能同时覆盖“跑失败”和“根本没跑”两种情况——后者其实更常见,比如机器被回收、cron 文件被覆盖、镜像更新后任务没带上。

  • 失败兜底:python job.py || curl -s -X POST "$ALERT_WEBHOOK" -d "job=report failed"。
  • 记录耗时趋势,任务从 2 分钟涨到 40 分钟时,你会在它真正超时之前收到信号。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。