在印度机房用 cron 跑备份,PATH 让我丢了三天日志

印度孟买机房,Ubuntu 22.04,内核 5.15,四台跑日志聚合的机器。上周发现一台机器上 /var/log/app/ 里连续三天的备份文件全是 0 字节,但 cron 日志里每条任务都显示执行成功。数据量不大,每天约 1.2 GB 的文本日志,压完 180 MB 左右,文件时间戳正常,就是内容为空。

我的做法是先复现。手动跑脚本没问题,文件正常产出;换成 cron 的触发方式就复现。于是把脚本里的命令换成绝对路径继续测,同时把 cron 的环境变量 dump 出来对比:

# 在 crontab 里临时加一行,抓真实环境
* * * * * env > /tmp/cron_env.txt
# 对比交互式 shell
diff <(sort /tmp/cron_env.txt) <(env | sort)

差异集中在 PATH。cron 的默认 PATH 是 /usr/bin:/bin,而我的脚本里用了 tar、gzip 之外还调了 /usr/local/bin 下的一个自定义轮转工具,以及 bash 的 source 去加载一个 alias 文件。脚本第一行写的是 #!/bin/bash,但里面用了 [[ ]] 和数组,这些在 cron 下没问题;真正的问题是那个轮转工具不在 cron 的 PATH 里,command -v 返回空,脚本里用 || 兜底走了 true,于是压缩那步被静默跳过,只生成了空文件。

修法是双管齐下。脚本内部不依赖 PATH:

#!/bin/bash
set -euo pipefail
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
ROTATE=/usr/local/bin/rotate_logs
[ -x "$ROTATE" ] || { echo "missing rotate_logs" >&2; exit 1; }
"$ROTATE" --src /var/log/app --dst /backup/app --keep 7

crontab 里显式声明环境,避免继承不可控:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=ops@example.internal
30 2 * * * /opt/backup/run_backup.sh >> /var/log/backup.log 2>&1

关键取舍是:脚本里把 PATH 写死并加 set -euo pipefail,代价是可移植性下降,换机器要改一行;换来的好处是 cron 下行为可预测,任何缺失命令都会立即失败而不是静默通过。|| true 这种兜底在 cron 里是危险的,它把错误变成了“成功”。

边界条件:如果脚本依赖 ~/.bashrc 里的 alias 或函数,cron 根本不会加载,必须显式 source 一个无交互的 env 文件;另外 MAILTO 设置后如果本机没有 MTA,失败邮件会进本地 spool,反而掩盖问题,我后来改成把 stderr 重定向到日志并用外部监控抓关键字。

你们在 cron 里是统一写死 PATH,还是靠 wrapper 脚本注入环境?有没有遇到过 exit 0 但产物为空的静默失败?

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。