crontab —— 定时任务

运维工具手册 05-服务与计划任务 5.9 KB 原文

crontab —— 定时任务

分类:05 服务与计划任务 适用:所有 Linux(cronie / vixie-cron / busybox cron) 一句话:备份、清理、巡检脚本的默认调度器。简单可靠,但坑也最多。

1. 它解决什么问题

  • 每天凌晨备份数据库、清理日志。
  • 每 5 分钟检查服务状态,异常时重启。
  • 每月 1 号生成报表。
  • 开机后自动拉起某个脚本。

2. 格式

* * * * * command
│ │ │ │ │
│ │ │ │ └── 星期 (0-7,0 和 7 都是周日)
│ │ │ └──── 月   (1-12)
│ │ └────── 日   (1-31)
│ └──────── 小时 (0-23)
└────────── 分钟 (0-59)
# 每天 2:30
30 2 * * * /opt/scripts/backup.sh

# 每 5 分钟
*/5 * * * * /opt/scripts/check.sh

# 每小时的第 0 分钟
0 * * * * /opt/scripts/hourly.sh

# 工作日 9 点
0 9 * * 1-5 /opt/scripts/report.sh

# 每月 1 号和 15 号 3 点
0 3 1,15 * * /opt/scripts/monthly.sh

# 每隔 2 小时(0,2,4...)
0 */2 * * * /opt/scripts/sync.sh

特殊字符串

写法 等价
@reboot 开机后执行一次
@yearly / @annually 0 0 1 1 *
@monthly 0 0 1 * *
@weekly 0 0 * * 0
@daily / @midnight 0 0 * * *
@hourly 0 * * * *

容易写错的时间表达

写法 实际含义 风险
* 2 * * * 2 点内每分钟执行 以为只执行一次
0 2 * * * 每天 2:00 一次 正确
0 0 31 2 * 2 月 31 日,永不执行 日期不存在
0 0 * * 7 周日 0 与 7 都可

3. 管理命令

crontab -l                    # 查看当前用户
crontab -e                    # 编辑
crontab -r                    # 删除全部(⚠️ 无确认,先备份)
crontab -u nginx -l           # 查看指定用户(需 root)
crontab -u nginx -e
crontab file.txt              # 从文件加载
crontab -l > /tmp/cron.bak    # 备份(重要!)

crontab -r 没有任何确认提示,误触即清空。养成 crontab -l > ~/cron.bak 的习惯。

4. 四个配置位置的区别

位置 格式 说明
crontab -e 5 段 + 命令 写入 /var/spool/cron/<user>,属于该用户
/etc/crontab 5 段 + 用户名 + 命令 系统级,多一个 user 字段
/etc/cron.d/* 同 /etc/crontab 分包管理,推荐 Ansible 下发
/etc/cron.{hourly,daily,weekly,monthly}/ 直接放可执行脚本 由 run-parts 调度,无需写时间

/etc/cron.d/app 示例(注意 user 字段):

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=""

*/10 * * * * app /opt/app/healthcheck.sh

在 /etc/crontab 或 /etc/cron.d 里漏写用户名,任务会静默不执行,这是最常见的坑之一。

5. 环境变量

cron 的环境极简,只有 HOME、LOGNAME、SHELL=/bin/sh、极简 PATH。这导致了 90% 的"手动能跑,cron 不跑"。

在 crontab 顶部显式声明:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=zh_CN.UTF-8
MAILTO=ops@example.com

脚本内部再兜底:

#!/bin/bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export LANG=C.UTF-8
source /etc/profile            # 需要时

6. 输出与日志

默认 cron 会把脚本的 stdout/stderr 通过邮件发给用户(MAILTO)。服务器通常没配 MTA,结果就是日志丢失。

推荐做法:脚本内自己重定向。

30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

或 crontab 顶部:

MAILTO=""

查看 cron 自身日志:

# RHEL 系
tail -f /var/log/cron
journalctl -u crond -f

# Debian 系
grep CRON /var/log/syslog
journalctl -u cron -f

日志能看到"任务有没有被触发"以及执行的命令,是排查第一步。

7. 防止任务重叠(flock)

任务执行时间超过间隔时会叠在一起跑,造成资源争抢或数据错乱。

*/5 * * * * flock -n /var/run/check.lock /opt/scripts/check.sh
  • -n:拿不到锁立即退出(推荐)。
  • -w 10:最多等 10 秒。

脚本内部也可以:

exec 9>/var/run/myscript.lock
flock -n 9 || { echo "already running"; exit 0; }

8. 实战场景

8.1 数据库备份(带日志与保留策略)

# crontab
0 2 * * * /opt/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
DAY=$(date +%F)
DEST=/backup/mysql
mkdir -p "$DEST"
mysqldump --single-transaction --routines --triggers \
  -u backup -p"$MYSQL_PWD" appdb | gzip > "$DEST/appdb-$DAY.sql.gz"
find "$DEST" -name '*.sql.gz' -mtime +14 -delete

8.2 服务健康检查与自愈

*/2 * * * * flock -n /var/run/health.lock /opt/scripts/health.sh
#!/bin/bash
curl -sf --max-time 5 http://127.0.0.1:8080/health >/dev/null && exit 0
echo "$(date) health check failed, restarting" >> /var/log/health.log
systemctl restart myapp

8.3 清理临时文件

0 4 * * * find /tmp -type f -atime +7 -delete
0 4 * * * find /var/log/app -name '*.log' -mtime +30 -delete

8.4 每月 1 号生成报表

0 8 1 * * /opt/scripts/monthly-report.sh

注意:1 号 0 点机器可能在跑其他任务,错开到 8 点更稳。

8.5 时区问题(跨时区业务)

CRON_TZ=UTC
0 2 * * * /opt/scripts/utc-job.sh

或脚本内 TZ=UTC date。CRON_TZ 需要 cronie 支持,busybox cron 不支持。

9. 常见坑(重点)

  1. PATH 不全:脚本里用了 docker、kubectl、java,cron 找不到。写全路径或在 crontab 里补 PATH。
  2. 相对路径失效:cron 的 HOME 可能不是你以为的目录,脚本内全部用绝对路径,或先 cd /opt/app。
  3. % 有特殊含义:crontab 里 % 表示换行,date +%F 必须写成 date +\%F,或把命令放进脚本。
  4. 脚本没有执行权限:chmod +x,或写成 bash /path/script.sh。
  5. shebang 用了 #!/usr/bin/env bash:cron 的 PATH 里可能没有 /usr/bin/env 能找到的路径,直接写 #!/bin/bash。
  6. 环境变量缺失:JAVA_HOME、MYSQL_PWD、代理变量都要在脚本里显式设置。
  7. 忘记 2>&1:错误输出进了邮件黑洞,看不到失败原因。
  8. 任务重叠:加 flock。
  9. crontab -r 无确认:定期备份。
  10. /etc/cron.d 缺 user 字段:任务静默不跑,日志里也没有明显报错。
  11. 秒级需求:cron 最小粒度是分钟,需要秒级用 sleep 拆分或 systemd timer(OnUnitActiveSec 也支持秒)。
  12. 依赖网络/数据库:开机 @reboot 时网络可能未就绪,加 sleep 30 或重试逻辑。
  13. anacron 差异:/etc/cron.daily 在部分系统由 anacron 驱动,关机错过会在开机后补跑,时间不可预期。
  14. SELinux 拦截:脚本放在非标准目录可能被拒,查 ausearch -m avc -ts recent。
  15. /var/spool/cron 权限:手工编辑该目录会破坏 cron,只用 crontab -e。

10. cron vs systemd timer

能力 cron systemd timer
最小粒度 1 分钟 秒级
错过补跑 需 anacron Persistent=true 原生支持
日志 /var/log/cron journalctl 统一
依赖管理 无 After=、Requires=
资源限制 无 MemoryMax、CPUQuota
随机延迟 手动 RandomizedDelaySec
可测试性 差 systemctl start x.service 手动触发

新项目建议用 systemd timer,老环境继续 cron。

11. 速查表

crontab -l                     # 查看
crontab -e                     # 编辑
crontab -l > ~/cron.bak        # 备份
crontab -u <user> -l           # 指定用户

# 格式:分 时 日 月 周 命令
30 2 * * *   /opt/backup.sh >> /var/log/backup.log 2>&1
*/5 * * * *  flock -n /var/run/x.lock /opt/check.sh
0 9 * * 1-5  /opt/report.sh
@reboot      /opt/start.sh

# 日志
tail -f /var/log/cron
journalctl -u cron -f

# 校验
systemctl status cron/crond

写 crontab 的三条铁律:

  1. 全部用绝对路径;
  2. 命令后面永远跟 >> /var/log/xxx.log 2>&1;
  3. 长任务加 flock -n。
crontab —— 定时任务 - 知识库 - 起航社区