systemd 編寫服務管理指令碼

sparkdev發表於2018-03-08

我們執行 linux 伺服器的主要目的是通過執行程式提供服務,比如 mysql、web server等。因此管理 linux 伺服器主要工作就是配置並管理上面執行的各種服務程式。在 linux 系統中服務程式的管理主要由 init 系統負責。如同筆者在《初識 systemd》一文中的介紹,linux 的 init 系統已經從最初的 sysvinit 進化到了如今的 systemd。本文主要介紹在 systemd 環境中如何編寫執行服務的配置檔案。

unit(單元)的配置檔案

Unit 是 systemd 進行任務管理的基本單位,我們在前文中已經介紹過,service 型別的 unit 代表一個後臺服務程式。接下來我們就詳細的介紹如何配置 service 型別的 unit。下面我們先來看一個簡單的服務配置:

[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/introduction/overview/
After=network.target

[Service]
User=prometheus
Restart=on-failure
WorkingDirectory=/usr/local/share/prometheus/
ExecStart=/usr/local/share/prometheus/prometheus \
          -config.file=/usr/local/share/prometheus/prometheus.yml

[Install]
WantedBy=multi-user.target

這是筆者主機上 prometheus 服務的配置檔案。把上面的內容儲存到檔案 /lib/systemd/system/prometheus.service 中,然後就可以使用 systemctl 命令管理 prometheus 服務了。注意,服務型別的配置檔名稱必須以 .service 結尾。
檢視上面配置資訊的詳細內容,我們會發現整個配置的內容分為三個部分:
[Unit] unit 本身的說明,以及與其它有依賴關係的服務的設定,包括在什麼服務之後才啟動此 unit 之類的設定。
[Service] 不同的 unit 型別就得要使用相對應的設定專案,比如 timer 型別的 unit 應該是 [Timer],socket 型別的 unit 應該是 [Socket]。服務型別的 unit 就是 [Service],這個專案內主要在規範服務啟動的指令碼、環境配置檔案檔名、重新啟動的方式等等。
[Install] 這個部分主要設定把該 unit 安裝到哪個 target 。

服務型別 unit 的詳細配置

配置檔案分為三個部分,每個部分中都可以提供詳細的配置資訊。為了精確的控制服務的執行方式,我們需要了解這些詳細的配置選項,並最終讓服務以我們期望的方式執行。

[Unit] 部分
Description    關於該 unit 的簡易說明。
Documentation    文件相關的內容,如 Documentation=https://prometheus.io/docs/introduction/overview/
                               Documentation=man:sshd(8)
                               Documentation=file:/etc/ssh/sshd_config
After    說明本 unit 是在哪個服務啟動之後才啟動的意思。僅是說明服務啟動的順序而已,並沒有強制要求 。
Before    與 After 的意義相反,在指定的服務啟動前最好啟動本個服務的意思。僅是說明服務啟動的順序而已,並沒有強制要求 。
Requires    本 unit 需要在哪個服務啟動後才能夠啟動!就是設定服務間的依賴性。如果在此項設定的前導服務沒有啟動成功,那麼本 unit 就不會被啟動!
Wants    與 Requires 剛好相反,規範的是這個 unit 之後還要啟動什麼服務,如果這 Wants 後面接的服務如果沒有啟動成功,其實不會影響到這個 unit 本身!
Conflicts    這個專案後面接的服務如果有啟動,那麼本 unit 就不能啟動!如果本 unit 啟動了,則指定的服務就不能啟動。

[Service] 部分
Type
說明這個服務的啟動方式,會影響到 ExecStart,主要有下面幾種型別:
simple:預設值,這個服務主要由 ExecStart 設定的程式來啟動,啟動後常駐於記憶體中。
forking:由 ExecStart 指定的啟動的程式通過 spawns 產生子程式提供服務,然後父程式退出。
oneshot:與 simple 類似,不過這個程式在工作完畢後就結束了,不會常駐在記憶體中。
dbus:與 simple 類似,但這個服務必須要在取得一個 D-Bus 的名稱後,才會繼續執行!因此設定這個專案時,通常也要設定 BusName= 才行。
idle:與 simple 類似,意思是,要執行這個服務必須要所有的工作都順利執行完畢後才會執行。這類的服務通常是開機到最後才執行即可的服務。
notify:與 simple 類似,但這個服務必須要收到一個 sd_notify() 函式傳送的訊息後,才會繼續執行。

ExecStart
就是實際執行此服務的程式。接受 "命令 引數 引數..." 的格式,不能接受 <, >, >>, |, & 等特殊字元,很多的 bash 語法也不支援。所以,要使用這些特殊的字元時,最好直接寫入到指令碼里面去!

ExecStartPreExecStartPost 分別在服務啟動前後,執行額外的命令。

ExecStop 用來實現 systemctl stop 命令,關閉服務。
ExecReload 用來實現 systemctl reload 命令,重新載入服務的配置資訊。

Restart 當設定為 Restart=1 時,如果服務終止,就會自動重啟此服務。
RestartSec 與 Restart 配合使用,在服務終止多長時間之後才重新啟動它。預設是 100ms。

KillMode
可以是 process, control-group, none 中的一種,如果是 process 則服務終止時,只會終止主要的程式(ExecStart接的後面那串指令),如果是 control-group 時,則由此 daemon 所產生的其他 control-group 的程式,也都會被關閉。如果是 none 的話,則沒有程式會被關閉。

TimeoutSec
若這個服務在啟動或者是關閉時,因為某些緣故導致無法順利 "正常啟動或正常結束" 的情況下,則我們要等多久才進入 "強制結束" 的狀態!

RemainAfterExit
當設定為 RemainAfterExit=1 時,則當這個服務所屬的所有程式都終止之後,此服務會再嘗試啟動。這對於 Type=oneshot 的服務很有幫助!

環境變數的設定對很多程式來說都是十分重要的,下面的配置則可以以不同的方式為服務程式設定環境變數:
Environment 用來設定環境變數,可以使用多次:

[Service]
# Client Env Vars
Environment=ETCD_CA_FILE=/path/to/CA.pem
Environment=ETCD_CERT_FILE=/path/to/server.crt

EnvironmentFile 通過檔案的方式設定環境變數,可以把下面的內容儲存到檔案 testenv 中:

AAA_IPV4_ANCHOR_0=X.X.X.X
BBB_IPV4_PRIVATE_0=X.X.X.X
CCC_HOSTNAME=test.example.com

然後這樣設定:

[Service]
EnvironmentFile=/testenv

接下來就可以在 ExecStart 配置中使用在檔案中設定的環境變數,如:

ExecStart=/xxx --abc=xx${AAA_IPV4_ANCHOR_0}yy

[Install] 部分
WantedBy    這個設定後面接的大部分是 *.target unit。意思是,這個 unit 本身是附掛在哪個 target unit 下面。
Also    當目前這個 unit 被 enable 時,Also 後面接的 unit 也要 enable 的意思。
Alias    當 systemctl enable 相關的服務時,則此服務會進行連結檔案的建立!

Timer 型別 unit 的詳細配置

Timer 型別的 unit 主要用來執行定時任務,並有可能取代 cron 服務。由於 timer 型別的 unit 經常與服務型別的 unit 一起使用,所以本文也附帶介紹一下 timer unit 的配置。與服務型別的 unit 不同,timer unit 配置檔案中的主要部分是 [Timer],下面是其主要的配置項:
OnActiveSec    當 timers.target 啟動後多久才執行這個 unit。
OnBootSec    當開機後多久才執行這個 unit。
OnStartupSec    當 systemd 第一次啟動後多久才執行這個 unit。
OnUnitActiveSec    這個 timer 配置檔案所管理的那個 unit 服務在最後一次啟動後,隔多久後再執行一次。
OnUnitInactiveSec    這個 timer 配置檔案所管理的那個 unit 服務在最後一次停止後,隔多久後再執行一次。
Unit    一般不需要設定,基本上我們設定都是 服務名稱.server + 服務名稱.timer。如果你的服務名稱和 timer 名稱不相同,就需要在 .timer 檔案中通過 Unit 項指定服務的名稱。
OnCalendar    使用實際時間(非迴圈時間)的方式來啟動服務。
Persistent    當使用 OnCalendar 的設定時,指定該功能要不要持續執行。

通過上面的介紹,相信大家對 systemd 服務型別和 timer 型別的 unit 配置已經有了基本的理解,下面讓就讓我們配置兩個實際的例子。

配置 redis 服務

在 ubuntu 上我們一般會手動編譯並安裝 redis。在安裝完成後需要把 redis 配置為 systemd 管理的服務,下面介紹具體的配置過程。
新增 redis 配置檔案
首先手動建立 /etc/redis 目錄並新增配置檔案:

$ sudo mkdir /etc/redis

並把程式碼目錄中的配置檔案 redis.conf 拷貝到 /etc/redis 目錄中:

$ sudo cp /tmp/redis-4.0.0/redis.conf /etc/redis/

然後修改配置檔案 /etc/redis/redis.conf 中的 supervised 為 systemd:
supervised systemd

接著繼續在配置檔案 /etc/redis/redis.conf 中配置工作目錄,把 dir ./ 修改為:
dir /var/lib/redis

配置由 systemd 管理 redis 服務
建立 /etc/systemd/system/redis.service 檔案

$ sudo vim /etc/systemd/system/redis.service

編輯其內容如下:

[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]
WantedBy=multi-user.target

啟動服務並配置為開機啟動:

$ sudo systemctl start redis
$ sudo systemctl enable redis
$ sudo systemctl status redis

通過指令碼定時備份檔案

備份檔案的 bash 指令碼:

#!/bin/bash
mydate()
{
        date "+%Y%m%d%H%M%S"
}
backupdate=$(mydate)
tar -zcf /tmp/backup.${backupdate}.tar.gz /home/nick/learn

把上面的程式碼儲存到檔案 /usr/local/bin/backupdir.sh,並新增可執行許可權:

$ sudo chmod +x /usr/local/bin/backupdir.sh

然後建立 service unit 配置檔案:

[Unit]
Description=nick backup learn dir service

[Service]
User=nick
Group=nick
Type=simple
ExecStart=/usr/local/bin/backupdir.sh

[Install]
WantedBy=multi-user.target

把上面的 unit 配置儲存到檔案 /etc/systemd/system/nickbak.service。
然後執行下面的命令測試服務的執行情況:

$ sudo systemctl daemon-reload
$ sudo systemctl start nickbak.service

這樣的備份任務只會在執行 sudo systemctl start nickbak.service 時執行一次。下面我們通過 timer unit 把它配置為定時執行。
建立 timer unit 配置檔案:

[Unit]
Description=nick backup learn dir timer

[Timer]
OnCalendar=*:0/15
Persistent=true
Unit=nickbak.service

[Install]
WantedBy=multi-user.target

把上面的 unit 配置儲存到檔案 /etc/systemd/system/nickbak.timer。配置中 OnCalendar=*:0/15 表示每 15 分鐘執行一次 nickbak.service 服務。

執行下面的命令把 nickbak.timer 設定為開機啟動,並啟動 nickbak.timer:

$ sudo systemctl daemon-reload
$ sudo systemctl enable nickbak.timer
$ sudo systemctl start nickbak.timer

現在來看看 nickbak.timer 的狀態:

$ sudo systemctl status nickbak.timer

從現在開始 nickbak.timer 會每隔 15 分鐘執行一次 nickbak.service 服務。

總結

systemd 提供了服務管理(其實是 unit 管理)的方方面面,我們需要做的就是寫好服務 unit 的配置檔案,然後利用 systemd 來管理我們的服務。這是一個看似簡單實則繁瑣的任務(很多的配置項其實需要我們在實踐中不斷的調整並優化)。希望本文對大家來說是個簡單的入門。

參考:
鳥哥的私房菜

相關文章