由於網路伺服器可能會因各種天災人禍或是因為需要進行系統更新而必須重新開機,因此在部署網路相關的服務時,我們應該要確保網路服務可以在伺服器重新開機之後也跟著自動啟動。再來,網路伺服器可能會遭遇到各種意外狀況,而導致程式執行出錯,造成程式崩潰(crash),一旦提供服務的應用程式崩潰了,就無法再繼續處理後續進來的請求,因此有個能夠保證服務能夠在崩潰或是被關閉之後自動重啟的機制也是很重要的。在Linux作業系統上,我們可以直接透過Linux作業系統內建的init系統,來將任意應用程式變成開機可自動執行,且保證能夠在崩潰或是被關閉之後自動重啟的服務。



Linux內建的init系統,從過去主流的SysV-init,到過渡的Upstart,再到成為新主流的Systemd,都可以用來處理上述問題。本篇文章只會介紹Systemd的作法。

Systemd Unit

Systemd Unit是Systemd設定檔的類型,常用的類型有處理服務的.service、處理runlevel的.target、處理檔案系統掛載的.mount

Debian或是其衍生的Linux發行版會有三個來存放不同優先等級的Systemd Unit。第一個是優先權最低的系統目錄/lib/systemd/system(已安裝套件通常會將Systemd Unit檔案放置在此目錄,如果是紅帽系的Linux發行版的話,此目錄的路徑是/usr/lib/systemd/system),第二個是優先權中等的執行階段目錄/run/systemd/system,第三個是優先權最高的管理員自訂目錄/etc/systemd/system。如果要查看Systemd Unit是否有發生覆寫,可以在終端機使用systemd-delta指令,有興趣者可以自行試試,這篇文章就不細談這部份了。

撰寫Systemd Service Unit的設定檔

若我們想要利用Systemd Service Unit來啟動某個與作業系統本身運作沒什麼關聯的應用程式,可以將Systemd Service Unit的設定檔寫在管理員自訂目錄/etc/systemd/system下。

直接在/etc/systemd/system下建立一個副檔名為.service的檔案,並先使用以下文字作為檔案內容:

[Unit]
Description=A description for this service unit.
After=network.target
 
[Service]
# User=user
# Group=group
# WorkingDirectory=/path/to/folder
ExecStart=/path/to/program [arguments]
Restart=always
RestartSec=3s

# Environment="VARIABLE_1=VALUE_1" "VARIABLE_2=VALUE_2"
# Environment="VARIABLE_3=VALUE_3" "VARIABLE_4=VALUE_4"
 
[Install]
WantedBy=multi-user.target

[Unit]區塊中,我們撰寫了兩個項目。Description用來設定這個Systemd Service Unit設定檔案的描述,可以讓管理員在日後查看時還能想起這個Systemd Service Unit的目的是什麼。另一個After項目可以用來指定一個Systemd Target Unit,可以確保目前正在設定的這個Unit,會在Systemd Target Unit運行完後再運行。如果是用來提供Web服務的應用程式,那麼通常就必須要在作業系統環境的網路功能被啟用之後才能夠正常啟動,所以After項目設定為network.target可以確保該程式在第一次執行時候就順利啟動。

[Service]區塊中有五個使用井字號#註解掉的項目,這些先不要管它,稍候會再提到。ExecStart項目可以用來設定當這個Systemd Service Unit項目啟動時要執行的應用程式(需使用完整絕對路徑),並且如果需要的話,可以利用空白字元來分隔多個不同的引數。若是這個程式需要搭配shell的命令列功能(例如變數、管線等)來執行,那就使用如以下的方式先執行shell之後,再運行shell的命令吧!

ExecStart=/bin/bash -c 'shell命令'

Restart項目可以設定ExecStart所執行的程式要在什麼樣的情況被關閉時,以相同的指令再重新執行一次。有效的設定值如下:

  • no:不重啟。(預設值)
  • always:不管什麼原因都會重啟。
  • on-success:只在成功運行結束後(也就是行程回傳的Exit Status為0的時候)才進行重啟。(這蠻少用)
  • on-failure:在運行失敗後(行程回傳的Exit Status非為0、被使用Kill信號強制關閉、逾時或是餵狗沒反應時)才進行重啟。
  • on-abnormal:在意外地運行失敗後(被使用Kill信號強制關閉、逾時或是餵狗沒反應時)才進行重啟。
  • on-abort:被使用Kill信號強制關閉時才進行重啟。
  • on-watchdog:餵狗沒反應時才進行重啟。

上述提到的「逾時」,可以在[Service]區塊中用TimeoutSec項目來設定程式在執行多久後算是逾時,一般我們是用不到這個機制啦!而「餵狗」則是使用Systemd提供的看門狗(Watchdog)機制,使程式在固定時間內(可以在[Service]區塊中用WatchdogSec項目來設定),必須主動呼叫Systemd函式庫的sd_notify函數,以利Systemd判斷該程式是否還是正常運作的狀態,一般我們也是用不到這個機制的啦!

至於RestartSec這個項目,就是設定程式在被關閉後,到下次自動重啟的間隔時間,預設是100ms(毫秒)。

最後的[Install]區塊中,只有WantedBy這一個設定項目,它可以用來指定一個Systemd Target Unit,讓目前正在設定的這個Unit,會在運行該Systemd Target Unit時,也跟著被運行。

接著來談談被註解掉的項目,首先是UserGroup這兩個項目,它們可以分別用來設定要運行ExecStart項目所指定的程式時所使用的使用者名稱和群組名稱。另外如果程式的運行必須仰賴工作目錄的支援的話,就要使用WorkingDirectory項目來手動指定程式工作目錄的路徑。

而如果要替執行的程式設定環境變數的話,可以透過一個或是數個Environment項目來設定,一個Environment項目可以設定多個環境變數。

套用Systemd Unit的設定檔變更

在新增、或是修改Systemd Unit的設定檔後,要在終端機執行以下指令才會重新載入:

sudo systemctl daemon-reload

這個指令並不會導致正常運行中的服務被重啟。(換句話說,正在運行中的服務還是會使用原來的Systemd Unit設定檔,除非重啟手動它。)

啟動、重新啟動、停止指定的Systemd Service Unit

systemctl指令除了可以讓Systemd重新載入新的Systemd Unit的設定檔外,當然也可以用來進行其它的管理操作。

例如要啟動magiclen.org.service這個Systemd Service Unit,可以在終端機執行以下指令:

sudo systemctl start magiclen.org.service

Systemd Unit檔案的副檔名可以省略掉,所以以上的指令也可以簡寫成:

sudo systemctl start magiclen.org

如果是要「重新啟動」正在運行的服務,就把指令中的start換成restart即可。

如果是要「停止」正在運行的服務,就把指令中的start換成stop即可。

查看指定Systemd Service Unit的狀態

使用systemctl start指令來啟動服務,是屬於非阻塞(non-blocking)的操作,我們無法直接得知服務到底有沒有執行成功。因此在手動使用systemctl start指令執行服務之後,通常還會再使用systemctl status指令來查看指定的Systemd Service Unit的狀態。

如果是想要查看Systemd Service Unit所執行的程式所輸出的Log,可以使用journalctl指令,基本用法如下:

journalctl -u 要查看Systemd Service Unit名稱

如果要同時查看多個Systemd Service Unit,可以再使用多個-u參數進行串接。

在預設情況下journalctl會把所有時間以及所有層級(如Debug、Warning、Error)的Log通通顯示出來,如果要進行過濾的話,可以將指令寫成:

journalctl -u 要查看Systemd Service Unit名稱 -p 要查看的Log最高層級 --since 起始時間點 --until 中止時間點

Log的層級列表如下:

  • 0: emerg
  • 1: alert
  • 2: crit
  • 3: err
  • 4: warning
  • 5: notice
  • 6: info
  • 7: debug

層級數字或是層級名稱都可以與-p參數搭配使用。

時間點的寫法最基本的就是使用以下格式:

YYYY-MM-DD[ HH:MM[:SS]]

例如:2019-06-192019-06-19 12:002019-06-19 12:00:00

或是:

HH:MM[:SS]

例如:12:00表示今天正中午。

另外也可以用yesterdaytodaytomorrow來分別表示昨天、今天和明天(未來日記?)。

也可以用1 hour ago1 hours ago2 week ago2 weeks ago等來表示距離現在時間多久前的時間點。

開機自動啟動服務

如果要將Systemd Service Unit在開機時自動運行的話,首先要確定設定檔中的[Install]區塊有撰寫正確,接著要在終端機上執行以下指令:

sudo systemctl enable 要自動啟動的Systemd Service Unit名稱

如果要關閉自動執行的功能,則在終端機上執行以下指令:

sudo systemctl disable 要自動啟動的Systemd Service Unit名稱

牛刀小試

利用這篇文章中所介紹的mprober來架設出可開機自動執行,且保證能夠在崩潰或是被關閉之後自動重啟的系統狀態監看之Web服務。該Web服務必須監聽TCP的9958連接埠。

參考答案

按照mprober文章的方法將mprober安裝在/usr/local/bin目錄中。然後新增一個/etc/systemd/system/mprober.service檔案,內容如下:

[Unit]
Description=M Prober
After=network.target

[Service]
ExecStart=/usr/local/bin/mprober web -p 9958
Restart=always
RestartSec=3s

[Install]
WantedBy=multi-user.target

在終端機執行以下指令:

sudo systemctl daemon-reload
sudo systemctl start mprober
sudo systemctl status mprober

mprober有成功運行,再執行以下指令:

sudo systemctl enable mprober