伺服器的價值在於你睡覺時它還在工作:備份、日誌輪替、憑證續期、九點的報表。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 個計時器之一。
評論
目前還沒有評論。歡迎成為第一位。