EQVPS
控制台開始使用

如何在 VPS 上用 cron 和 systemd 計時器排程工作

依排程執行備份、清理和指令碼。五分鐘上手 cron,以及三個會悄悄弄壞工作的陷阱;接著是 systemd 計時器:日誌、停機後補跑和精確時間。下面的每種行為都在一台全新的 Ubuntu 24.04 伺服器上驗證過。

伺服器的價值在於你睡覺時它還在工作:備份、日誌輪替、憑證續期、九點的報表。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. 該用哪個

cronsystemd 計時器
設定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 個計時器之一。

常見問題

為什麼工作在終端機裡能跑,在 cron 裡卻不行?

常見原因有三個。cron 用 sh 而不是 bash 執行工作,所以 bash 專有的語法會失敗。安裝在家目錄裡的工具,例如 nvm、pipx 或 cargo,不在 cron 的 PATH 裡,所以要用完整路徑。另外命令列中的任何 % 都會讓指令在那裡結束:在我們的伺服器上 date +%F 變成了 date +。請寫成 \% 或把指令放進指令碼。

cron 的輸出跑去哪了?

預設寄到電子郵件。我們的映像沒有郵件伺服器,所以 cron 記錄 'No MTA installed, discarding output',輸出就遺失了。替工作加上 >> /var/log/myjob.log 2>&1,或改用 systemd 計時器,它的輸出會自動進入 journal。

systemd 計時器比 cron 好在哪?

日誌在 journalctl 裡;仍在執行的工作不會再啟動第二份;搭配 Persistent=true 能補跑伺服器關機期間錯過的執行;還有一行狀態顯示下一次和上一次執行。cron 勝在簡潔:一行而不是兩個檔案。

我的計時器晚了 18 秒才執行,為什麼?

systemd 預設會把計時器合併到一分鐘的視窗內,以減少喚醒系統的次數,這就是 AccuracySec 設定。我們每兩分鐘一次的計時器在 07:42:18 而不是 07:42:00 執行。設為 AccuracySec=1s 後,它恰好在 07:46:00 執行。需要精確到分鐘時就設定它。

cron 和計時器用哪個時區?

用伺服器的時區,在我們的映像上是 UTC。用 timedatectl 設定一次(參見我們的主機名稱和時區指南),cron 和計時器都會跟隨。計時器也可以帶自己的時區:OnCalendar=*-*-* 03:00 Europe/Kyiv 在我們的測試中於 01:00 UTC 執行。

評論

目前還沒有評論。歡迎成為第一位。

發表評論

評論在顯示前會經過審核。