systemd/定时器

出自 Arch Linux 中文维基

Timers 是以 .timer 為後綴名的 systemd 單元文件,用於控制 .service 文件或事件。Timers 可用來替換 cron(閱讀 #替代 cron)。Timers 內置了實時定時事件和單調定時事件的支持,並可以異步執行這些事件。

定時器單元[編輯 | 編輯原始碼]

Timers 是以 .timer 為後綴的 systemd 單元文件。Timers 和其他單元配置文件是類似的,它通過相同的路徑加載,不同的是包含了 [Timer] 部分。 [Timer] 部分定義了何時以及如何激活定時事件。Timers 可以被定義成以下兩種類型:

  • 實時定時器(亦稱「掛鍾定時器」)通過日曆事件激活(類似於 cronjobs)定時任務。使用 OnCalendar= 來定義實時定時器。
  • 單調定時器即在一個時間點經過一段時間後激活定時任務。所有的單調計時器都遵循如下形式: OnTypeSec=OnBootSecOnActiveSec 是常用的單調定時器。

要查閱完整的定時器選項,參見 systemd.timer(5)。關於日曆事件和時間段的定義參見 systemd.time(7)

服務單元[編輯 | 編輯原始碼]

每個 .timer 文件所在目錄都得有一個對應的 .service 文件(如 foo.timerfoo.service)。.timer 文件激活並控制 .service 文件。對應的 .service 文件中不需要包含 [Install] 部分,因為這由 timer 單元接管。必要時可以通過在定時器的 [Timer] 部分指定 Unit= 選項來控制一個與定時器不同名的服務單元。

管理[編輯 | 編輯原始碼]

使用 timer 單元只需像其他單元一樣 enablestart 即可(別忘了添加 .timer 後綴)。要查看所有已啟用的定時器,運行:

$ systemctl list-timers
NEXT                          LEFT        LAST                          PASSED     UNIT                         ACTIVATES
Thu 2014-07-10 19:37:03 CEST  11h left    Wed 2014-07-09 19:37:03 CEST  12h ago    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Fri 2014-07-11 00:00:00 CEST  15h left    Thu 2014-07-10 00:00:13 CEST  8h ago     logrotate.timer              logrotate.service
注意:
  • 列出所有定時器(包括非活動的),使用下列命令: systemctl list-timers --all
  • 一個由定時器啟動的服務的狀態,如果不是正好被觸發的話,通常是未激活的。
  • 若一個定時器不再同步,它可能會刪除它在 /var/lib/systemd/timers 下的 stamp-* 文件。這些空文件只用於表示每個定時器上次運行的時間。刪除後,他們將在下次定時器運行時自動重建。

示例[編輯 | 編輯原始碼]

通過定時器預定執行的服務文件一般無需任何修改。以下示例將預定執行 foo.service,因此它的定時器應該被命名為 foo.timer

單調定時器[編輯 | 編輯原始碼]

定義一個在系統啟動 15 分鐘後執行,且之後每周都執行一次的定時器:

/etc/systemd/system/foo.timer
[Unit]
Description=Run foo weekly and on boot

[Timer]
OnBootSec=15min
OnUnitActiveSec=1w 

[Install]
WantedBy=timers.target

實時定時器[編輯 | 編輯原始碼]

定義一個每周執行一次(具體來講,指周一凌晨零點)的定時器。如果上次未執行(比如說系統當時沒有開機,這個行為由 Persistent=true 定義)就立即執行服務。

/etc/systemd/system/foo.timer
[Unit]
Description=Run foo weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

更精確的時間可以通過 OnCalendar 參數以下列方式指定:

星期 年-月-日 時:分:秒

可以用星號來表示任意值,用逗號分列可能值。以 .. 分隔的兩個值指兩個值間的連續序列。

這是在每個月的前四天晚上中午 12 點,但僅當這一天是周一或周二時運行的例子:

OnCalendar=Mon,Tue *-*-01..04 12:00:00

在每個月的第一個星期六運行:

 OnCalendar=Sat *-*-1..7 18:00:00

如果不需要以星期幾來界定,比如說在每天四點運行,請去掉星期項:

 OnCalendar=*-*-* 4:00:00

在不同的時間運行一個服務可以以多次指定 OnCalendar 的方式表示。在下面的例子中,這個服務在周內的 22:30 和周末的 20:00 運行:

 OnCalendar=Mon..Fri 22:30
 OnCalendar=Sat,Sun 20:00

更多信息請參閱 systemd.time(7)

提示:
  • 可以用 systemd-analyze 工具檢測 OnCalendar 項是否填寫正確,並計算下一次這個條件成立的時間。比如說可以用 systemd-analyze calendar weeklysystemd-analyze calendar "Mon,Tue *-*-01..04 12:00:00"
  • libfaketime 包提供的 faketime 命令在測試各種情況時非常有用。
  • 特殊的事件表達式如 dailyweekly 表示 特定的啟動時間,因此任何共享該日曆事件的定時器將同時啟動。如果該定時器的服務會競爭系統資源,那麼共享該日曆事件的定時器可能會引起系統性能下降。使用 [Timer] 部分中的 RandomizedDelaySec 選項可以通過隨機推遲定時器啟動來避免這個問題。參見 systemd.timer(5)
  • [Timer] 部分添加 AccuracySec=1us 選項可以避免原有默認的精確值 1 分鐘造成的誤差。參見 systemd.timer(5)

瞬態 .timer 單元[編輯 | 編輯原始碼]

可以使用 systemd-run 創建一個瞬態 .timer 單元。即可以在不創建服務文件的情況下設置定時運行某個命令。比如說下面的命令可以在 30 秒鐘後創建一個文件:

# systemd-run --on-active=30 /bin/touch /tmp/foo

也可以建立指定一個已經存在但沒有定義 timer 的服務文件。比如說,下面的命令會在 12 小時 30 分鐘後啟動名為 someunit.service 的服務:

# systemd-run --on-active="12h 30m" --unit someunit.service

更多信息和例子參見 systemd-run(1)

替代 cron[編輯 | 編輯原始碼]

儘管 cron 毋庸置疑是最有名的計劃任務管理器,systemd 定時器仍可以作為一個替代品。

優勢[編輯 | 編輯原始碼]

使用定時器的最主要的優勢在於每個任務都有它自己的 systemd 服務。這樣做的好處包括:

  • 任務可以簡單地獨立於他們的定時器啟動,簡化調試。
  • 每個任務可配置運行於特定的環境中(參見 systemd.exec(5))。
  • 任務可以使用 cgroups 特性。
  • 任務可以配置依賴於其他 systemd 單元。
  • 任務會被記錄於 systemd 日誌,便於調試。

注意事項[編輯 | 編輯原始碼]

有些可以用 cron 輕易做到的事情僅僅使用 timer 組件會比較難辦:

  • 創建過程:相比於在 crontab 中只需添加一行任務,使用 systemd 配置計劃任務需要創建兩個文件並運行好幾次 systemctl 命令。
  • 郵件:目前還沒有內置與 cron MAILTO 類似的任務失敗時發送郵件的功能。可以在每個服務文件中配置 OnFailure= 來實現同樣的功能。

另外, systemd用戶服務定時器默認只會在用戶登錄時進行。但是,lingering 允許在啟動,且用戶沒有活動登錄會話時激活。

  • 隨機延時:目前沒還有內置與 cron 類似的 RANDOM_DELAY 功能來指定一個數字用於定時器延時執行。(參見 bug report)。 你不想同時執行的服務必須手動設置它們的定時器。
注意: AccuracySec 選項對於隨機錯開定時器執行時間是 沒有 作用的,因為它"會在所有本地定時器單元間同步" (systemd.timer(5))。換句話說, AccuracySec 會以相同的量改變所有定時器激活時間。例如,所有 OnCalendar=daily 的定時器單元,指定 AccuracySec=15m 將同時在 00:00 到 00:15 觸發相關的服務。

發送郵件[編輯 | 編輯原始碼]

可以配置 systemd 在單元失敗時發送電子郵件。Cron 的 MAILTO 會在任務向標準輸出或標準錯誤輸出時發送郵件,許多任務只在發生錯誤時輸出。首先需要兩個文件:一個用來發送郵件的可執行文件和一個用於啟動這個可執行文件的 .service 文件。在本例中,可執行文件只是一個由提供 smtp-forwarder 的包中給出的調用 sendmail 的shell 腳本:

/usr/local/bin/systemd-email
#!/bin/sh

/usr/bin/sendmail -t <<ERRMAIL
To: $1
From: systemd <root@$HOSTNAME>
Subject: $2
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8

$(systemctl status --full "$2")
ERRMAIL

不論你用什麼可執行文件,應該都會像這個腳本一樣接收至少兩個參數:目標郵箱地址和用來獲取狀態的單元文件名。我們創建的 .service 文件會傳遞這些參數:

/etc/systemd/system/status_email_user@.service
[Unit]
Description=status email for %i to user

[Service]
Type=oneshot
ExecStart=/usr/local/bin/systemd-email address %i
User=nobody
Group=systemd-journal

其中,user 是被通知的用戶名,address 是該用戶的郵件地址。儘管這個地址被硬編碼在代碼裡,但是報告的單元文件會在實例參數中指定,因此可以作為許多其他單元的郵件服務端。此時你可以 start status_email_user@dbus.service 服務來檢查你能否收信。

成功後,僅需 編輯 你需要發送失敗郵件的服務,向 [Unit] 部分添加 OnFailure=status_email_user@%n.service%n 會向模板傳遞單元的名字。

注意:
  • 如果你根據 sSMTP#Security 配置了 sSMTP 安全,nobody 用戶會沒有訪問 /etc/ssmtp/ssmtp.conf 文件的權限,systemctl start status_email_user@dbus.service 命令就會失敗。一個解決辦法是使用 root 作為 status_email_user@.service 單元的執行者。
  • 如果你希望在你的郵件腳本中使用 mail -s somelogs addressmail 會以 fork 的形式啟動,systemd 會把在你的腳本退出時一併將這個進程結束掉。可以用 mail -Ssendwait -s somelogs address 來讓 mail 不以 fork 的形式啟動。

使用 crontab[編輯 | 編輯原始碼]

有些注意事項中的部分可以通過安裝一些將傳統 crontab 項目翻譯成 timers 配置的包來完成,例如AUR 中的 systemd-crontab-generatorAUR[損壞的連結:package not found]systemd-cronAUR 包。他們也可以提供缺失的 MAILTO 特性。

同樣,與 crontab 一樣,可以通過 systemctl 來獲取一個統一的計劃任務視圖。參見 #管理章節。

參見[編輯 | 編輯原始碼]

https://github.com/systemd-cron/systemd-cron-next || systemd-cron-nextAUR
  • systemd-cron — 提供 systemd 單元運行 cron 腳本;使用 systemd-crontab-generator 轉換 crontabs
https://github.com/systemd-cron/systemd-cron || systemd-cronAUR