Systemd 定時器教程

阮一峰發表於2018-03-30

Systemd 作為 Linux 的系統啟動器,功能強大。

本文通過一個簡單例子,介紹 Systemd 如何設定定時任務。這不僅實用,而且可以作為 Systemd 的上手教程。

一、定時任務

所謂定時任務,就是未來的某個或多個時點,預定要執行的任務,比如每五分鐘收一次郵件、每天半夜兩點分析一下日誌等等。

Linux 系統通常都使用 cron 設定定時任務,但是 Systemd 也有這個功能,而且優點顯著。

  • 自動生成日誌,配合 Systemd 的日誌工具,很方便除錯
  • 可以設定記憶體和 CPU 的使用額度,比如最多使用50%的 CPU
  • 任務可以拆分,依賴其他 Systemd 單元,完成非常複雜的任務

下面,我就來演示一個 Systemd 定時任務:每小時傳送一封電子郵件。

二、郵件指令碼

先寫一個發郵件的指令碼mail.sh


#!/usr/bin/env bash

echo "This is the body" | /usr/bin/mail -s "Subject" someone@example.com

上面程式碼的someone@example.com,請替換成你的郵箱地址。

然後,執行這個指令碼。


$ bash mail.sh

執行後,你應該就會收到一封郵件,標題為Subject

如果你的 Linux 系統不能發郵件,建議安裝 ssmtp 或者 msmtp。另外,mail命令的用法,可以參考這裡

三、Systemd 單元

學習 Systemd 的第一步,就是搞懂"單元"(unit)是什麼。

簡單說,單元就是 Systemd 的最小功能單位,是單個程式的描述。一個個小的單元互相呼叫和依賴,組成一個龐大的任務管理系統,這就是 Systemd 的基本思想。

由於 Systemd 要做的事情太多,導致單元有很多不同的種類,大概一共有12種。舉例來說,Service 單元負責後臺服務,Timer 單元負責定時器,Slice 單元負責資源的分配。

每個單元都有一個單元描述檔案,它們分散在三個目錄。

  • /lib/systemd/system:系統預設的單元檔案
  • /etc/systemd/system:使用者安裝的軟體的單元檔案
  • /usr/lib/systemd/system:使用者自己定義的單元檔案

下面的命令可以檢視所有的單元檔案。


# 檢視所有單元
$ systemctl list-unit-files

# 檢視所有 Service 單元
$ systemctl list-unit-files --type service

# 檢視所有 Timer 單元
$ systemctl list-unit-files --type timer

四、單元的管理命令

下面是常用的單元管理命令。


# 啟動單元
$ systemctl start [UnitName]

# 關閉單元
$ systemctl stop [UnitName]

# 重啟單元
$ systemctl restart [UnitName]

# 殺死單元程式
$ systemctl kill [UnitName]

# 檢視單元狀態
$ systemctl status [UnitName]

# 開機自動執行該單元
$ systemctl enable [UnitName]

# 關閉開機自動執行
$ systemctl disable [UnitName]

五、Service 單元

前面說過,Service 單元就是所要執行的任務,比如傳送郵件就是一種 Service。

新建 Service 非常簡單,就是在/usr/lib/systemd/system目錄裡面新建一個檔案,比如mytimer.service檔案,你可以寫入下面的內容。


[Unit]
Description=MyTimer

[Service]
ExecStart=/bin/bash /path/to/mail.sh

可以看到,這個 Service 單元檔案分成兩個部分。

[Unit]部分介紹本單元的基本資訊(即後設資料),Description欄位給出這個單元的簡單介紹(名字叫做MyTimer)。

[Service]部分用來定製行為,Systemd 提供許多欄位。

  • ExecStartsystemctl start所要執行的命令
  • ExecStopsystemctl stop所要執行的命令
  • ExecReloadsystemctl reload所要執行的命令
  • ExecStartPreExecStart之前自動執行的命令
  • ExecStartPostExecStart之後自動執行的命令
  • ExecStopPostExecStop之後自動執行的命令

注意,定義的時候,所有路徑都要寫成絕對路徑,比如bash要寫成/bin/bash,否則 Systemd 會找不到。

現在,啟動這個 Service。


$ sudo systemctl start mytimer.service

如果一切正常,你應該就會收到一封郵件。

六、Timer 單元

Service 單元只是定義瞭如何執行任務,要定時執行這個 Service,還必須定義 Timer 單元。

/usr/lib/systemd/system目錄裡面,新建一個mytimer.timer檔案,寫入下面的內容。


[Unit]
Description=Runs mytimer every hour

[Timer]
OnUnitActiveSec=1h
Unit=mytimer.service

[Install]
WantedBy=multi-user.target

這個 Timer 單元檔案分成幾個部分。

[Unit]部分定義後設資料。

[Timer]部分定製定時器。Systemd 提供以下一些欄位。

  • OnActiveSec:定時器生效後,多少時間開始執行任務
  • OnBootSec:系統啟動後,多少時間開始執行任務
  • OnStartupSec:Systemd 程式啟動後,多少時間開始執行任務
  • OnUnitActiveSec:該單元上次執行後,等多少時間再次執行
  • OnUnitInactiveSec: 定時器上次關閉後多少時間,再次執行
  • OnCalendar:基於絕對時間,而不是相對時間執行
  • AccuracySec:如果因為各種原因,任務必須推遲執行,推遲的最大秒數,預設是60秒
  • Unit:真正要執行的任務,預設是同名的帶有.service字尾的單元
  • Persistent:如果設定了該欄位,即使定時器到時沒有啟動,也會自動執行相應的單元
  • WakeSystem:如果系統休眠,是否自動喚醒系統

上面的指令碼里面,OnUnitActiveSec=1h表示一小時執行一次任務。其他的寫法還有OnUnitActiveSec=*-*-* 02:00:00表示每天凌晨兩點執行,OnUnitActiveSec=Mon *-*-* 02:00:00表示每週一凌晨兩點執行,具體請參考官方文件

七、[Install] 和 target

mytimer.timer檔案裡面,還有一個[Install]部分,定義開機自啟動(systemctl enable)和關閉開機自啟動(systemctl disable)這個單元時,所要執行的命令。

上面指令碼中,[Install]部分只寫了一個欄位,即WantedBy=multi-user.target。它的意思是,如果執行了systemctl enable mytimer.timer(只要開機,定時器自動生效),那麼該定時器歸屬於multi-user.target

所謂 Target 指的是一組相關程式,有點像 init 程式模式下面的啟動級別。啟動某個Target 的時候,屬於這個 Target 的所有程式都會全部啟動。

multi-user.target是一個最常用的 Target,意為多使用者模式。也就是說,當系統以多使用者模式啟動時,就會一起啟動mytimer.timer。它背後的操作其實很簡單,執行systemctl enable mytimer.timer命令時,就會在multi-user.target.wants目錄裡面建立一個符號連結,指向mytimer.timer

八、定時器的相關命令

下面,啟動剛剛新建的這個定時器。


$ sudo systemctl start mytimer.timer

你應該立刻就會收到郵件,然後每個小時都會收到同樣郵件。

檢視這個定時器的狀態。


$ systemctl status mytimer.timer

檢視所有正在執行的定時器。


$ systemctl list-timers

關閉這個定時器。


$ sudo systemctl stop myscript.timer

下次開機,自動執行這個定時器。


$ sudo systemctl enable myscript.timer

關閉定時器的開機自啟動。


$ sudo systemctl disable myscript.timer

九、日誌相關命令

如果發生問題,就需要檢視日誌。Systemd 的日誌功能很強,提供統一的命令。


# 檢視整個日誌
$ sudo journalctl

# 檢視 mytimer.timer 的日誌
$ sudo journalctl -u mytimer.timer

# 檢視 mytimer.timer 和 mytimer.service 的日誌
$ sudo journalctl -u mytimer

# 從結尾開始檢視最新日誌
$ sudo journalctl -f

# 從結尾開始檢視 mytimer.timer 的日誌
$ journalctl -f -u timer.timer

十、參考連結

(完)

相關文章