服务器的价值在于你睡觉时它还在干活:备份、日志轮转、证书续期、九点的报告。Linux 为此提供了两种调度器。cron 已有四十年历史,一行就够。systemd 定时器需要两个小文件,换来的是日志、停机后补跑和精确时间。本指南在 Ubuntu 24.04 上介绍这两者,每种行为都在用我们镜像创建的新服务器上验证过,包括会让 cron 任务一声不吭就失败的三个错误。
你需要一个有 sudo 权限的用户;Ubuntu 设置指南会创建一个。
1. 五分钟上手 cron
cron 已安装在我们的 Ubuntu 和 Debian 镜像上并在运行。每个用户都有一张任务表;用下面的命令编辑你自己的:
crontab -e
每一行是五个时间字段加一条命令:
# min hour day month weekday command
30 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
*/5 * * * * /usr/local/bin/check.sh
0 9 * * 1-5 /usr/local/bin/report.sh
@reboot /usr/local/bin/start-tunnel.sh
也就是:每晚 03:30、每 5 分钟、工作日 09:00,以及每次开机后一次。*/5 表示每隔五个值,1-5 表示范围,1,15 表示列表。使用的是服务器的时区,我们的镜像上是 UTC;用主机名和时区指南设置一次,cron 就会跟随。
用 journalctl -u cron 查看运行过什么。每次运行都会记录用户和命令,三个陷阱也会在那里现形。
陷阱 1:输出消失。 cron 会把任务的输出用邮件发给用户。我们的镜像没有邮件服务器,所以 journal 里写着 (CRON) info (No MTA installed, discarding output),输出就没了。请自己重定向,像第一个例子那样用 >> /var/log/backup.log 2>&1。
陷阱 2:百分号。 在 crontab 里,% 会结束命令,并把其余部分变成标准输入。我们安排了 date +%F > /tmp/today。journal 显示 cron 实际执行的是:CMD (date +)。没有写出任何文件,也没有任何错误传到我们这里。请写成 \%,或者把命令放进脚本再安排这个脚本。
陷阱 3:不同的环境。 cron 用 /bin/sh 而不是 bash,在你的主目录里、以精简的环境运行任务。在 Ubuntu 24.04 上,PATH 比它的名声要好:无论 root 还是普通用户,都是包含 /usr/local/bin 和 /snap/bin 的完整系统路径。仍然缺少的是主目录下的一切:~/.local/bin、nvm 的 node、pipx 和 cargo。在任务里使用完整路径,或者让这一行以 . $HOME/.profile; 开头。
属于系统而非某个用户的任务,放在 /etc/cron.d/ 中,作为多一个字段(用户)的文件:
30 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Ubuntu 还会运行 /etc/cron.hourly、cron.daily、cron.weekly 和 cron.monthly 里的所有内容:每小时第 17 分钟、06:25、周日 06:47 以及每月 1 日 06:52。把可执行脚本放进去,它就会和其他脚本一起运行。
2. systemd 定时器
一个定时器由两个单元组成:干活的服务,以及决定何时运行的定时器。先创建脚本,再创建单元。
sudo nano /usr/local/bin/backup.sh
sudo chmod +x /usr/local/bin/backup.sh
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run the backup every night at 03:30
[Timer]
OnCalendar=*-*-* 03:30
Persistent=true
AccuracySec=1m
[Install]
WantedBy=timers.target
启用的是定时器,而不是服务:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer
list-timers 显示下一次运行、距离它还有多久以及上一次运行。日志无需配置:journalctl -u backup.service 里有每次启动、每次结束和每一行输出,都带时间戳。要立刻运行任务,用 sudo systemctl start backup.service;在信任计划之前,也用这个方法测试脚本。
日历语法。 OnCalendar 的读法是 星期 年-月-日 时:分:秒,* 代表任意值。使用前先测试表达式:
systemd-analyze calendar "Mon..Fri *-*-* 03:00" --iterations 2
它会输出规范化后的形式和接下来的日期。我们检查过的一些表达式:
| OnCalendar | 含义 |
|---|---|
*-*-* 03:30 | 每天 03:30 |
Mon..Fri *-*-* 09:00 | 工作日 09:00 |
*:0/15 | 每 15 分钟 |
weekly | 周一 00:00 |
*-*-01 04:00 | 每月 1 日 04:00 |
*-*-* 03:00 Europe/Kyiv | 基辅时间 03:00;在我们的测试中于 01:00 UTC 运行 |
值得了解的三个设置。 Persistent=true 会把上一次运行记录到磁盘,并补跑服务器关机或定时器停止期间错过的执行。我们停掉每两分钟一次的定时器,等一次计划运行过去后再启动它:错过的那次在同一秒内就开始了。没有 Persistent 的话,它只会等待下一个时间点。
意外在于 AccuracySec。systemd 默认把定时器合并到一分钟的窗口里以减少唤醒,所以我们的第一次运行出现在 07:42:18,而不是 07:42:00。设为 AccuracySec=1s 后,之后的运行精确到秒地落在 07:46:00 和 07:48:00。需要精确到分钟时设置它;夜间备份保持默认即可。
RandomizedDelaySec=10m 则有意反其道而行:它把启动时间分散到一个窗口内,避免十台服务器在同一秒冲击同一个备份存储。
一次性任务根本不需要文件:
sudo systemd-run --on-active=30m --unit=cleanup-once /usr/local/bin/cleanup.sh
它会创建一个临时定时器,运行一次后消失;在那之前,systemctl list-timers 会显示它。
3. 该用哪个
| cron | systemd 定时器 | |
|---|---|---|
| 配置 | 1 行 | 2 个文件 |
| 日志 | 只有重定向输出时才有 | journalctl -u name.service |
| 服务器关机期间错过的运行 | 丢失 | 配合 Persistent=true 在开机时运行 |
| 到下一个时间点任务还在运行 | 启动第二份 | 跳过,直到第一份结束 |
| 精确时间 | 精确到分钟 | 配合 AccuracySec=1s 精确到秒 |
| 状态 | 无 | list-timers、systemctl status |
一年后还能一眼看懂的简单一行,用 cron。凡是需要看到失败、不能重叠、或者错过重启后也必须运行的任务,用定时器:备份、证书续期、数据库清理。如果 cron 任务可能重叠,就把它包起来:flock -n /tmp/backup.lock /usr/local/bin/backup.sh,第二份会立即退出。
两种调度器都是启动任务后就结束。需要全天候运行、崩溃后能恢复的程序,要的是服务而不是计划;请参阅如何创建 systemd 服务,Windows 则参阅如何在 Windows Server 上 24/7 运行脚本。一个不错的第一个定时器来自我们的 restic 备份指南;自动安全更新本身就是 Ubuntu 自带的定时器 apt-daily-upgrade.timer,是新服务器上运行的 17 个定时器之一。
评论
暂无评论。来做第一个吧。