Systemd 是 Linux 系統工具,用來啟動守護程序,已成為大多數發行版的标準配置。
本文介紹它的基本用法,分為上下兩篇。今天介紹它的主要指令,下一篇介紹如何用于實戰。

一、由來
曆史上,Linux 的啟動一直采用init程序。
下面的指令用來啟動服務。
$ sudo /etc/init.d/apache2 start
# 或者
$ service apache2 start
這種方法有兩個缺點。
一是啟動時間長。
init
程序是串行啟動,隻有前一個程序啟動完,才會啟動下一個程序。
二是啟動腳本複雜。
init
程序隻是執行啟動腳本,不管其他事情。腳本需要自己處理各種情況,這往往使得腳本變得很長。
二、Systemd 概述
Systemd 就是為了解決這些問題而誕生的。它的設計目标是,為系統的啟動和管理提供一套完整的解決方案。
根據 Linux 慣例,字母
d
是守護程序(daemon)的縮寫。 Systemd 這個名字的含義,就是它要守護整個系統。
(上圖為 Systemd 作者 Lennart Poettering)
使用了 Systemd,就不需要再用
init
了。Systemd 取代了
initd
,成為系統的第一個程序(PID 等于 1),其他程序都是它的子程序。
$ systemctl --version
上面的指令檢視 Systemd 的版本。
Systemd 的優點是功能強大,使用友善,缺點是體系龐大,非常複雜。事實上,現在還有很多人反對使用 Systemd,理由就是它過于複雜,與作業系統的其他部分強耦合,違反"keep simple, keep stupid"的Unix 哲學。
(上圖為 Systemd 架構圖)
三、系統管理
Systemd 并不是一個指令,而是一組指令,涉及到系統管理的方方面面。
3.1 systemctl
systemctl
是 Systemd 的主指令,用于管理系統。
# 重新開機系統
$ sudo systemctl reboot
# 關閉系統,切斷電源
$ sudo systemctl poweroff
# CPU停止工作
$ sudo systemctl halt
# 暫停系統
$ sudo systemctl suspend
# 讓系統進入冬眠狀态
$ sudo systemctl hibernate
# 讓系統進入互動式休眠狀态
$ sudo systemctl hybrid-sleep
# 啟動進入救援狀态(單使用者狀态)
$ sudo systemctl rescue
3.2 systemd-analyze
systemd-analyze
指令用于檢視啟動耗時。
# 檢視啟動耗時
$ systemd-analyze
# 檢視每個服務的啟動耗時
$ systemd-analyze blame
# 顯示瀑布狀的啟動過程流
$ systemd-analyze critical-chain
# 顯示指定服務的啟動流
$ systemd-analyze critical-chain atd.service
3.3 hostnamectl
hostnamectl
指令用于檢視目前主機的資訊。
# 顯示目前主機的資訊
$ hostnamectl
# 設定主機名。
$ sudo hostnamectl set-hostname rhel7
3.4 localectl
localectl
指令用于檢視本地化設定。
# 檢視本地化設定
$ localectl
# 設定本地化參數。
$ sudo localectl set-locale LANG=en_GB.utf8
$ sudo localectl set-keymap en_GB
3.5 timedatectl
timedatectl
指令用于檢視目前時區設定。
# 檢視目前時區設定
$ timedatectl
# 顯示所有可用的時區
$ timedatectl list-timezones
# 設定目前時區
$ sudo timedatectl set-timezone America/New_York
$ sudo timedatectl set-time YYYY-MM-DD
$ sudo timedatectl set-time HH:MM:SS
3.6 loginctl
loginctl
指令用于檢視目前登入的使用者。
# 列出目前session
$ loginctl list-sessions
# 列出目前登入使用者
$ loginctl list-users
# 列出顯示指定使用者的資訊
$ loginctl show-user ruanyf
四、Unit
4.1 含義
Systemd 可以管理所有系統資源。不同的資源統稱為 Unit(機關)。
Unit 一共分成12種。
- Service unit:系統服務
- Target unit:多個 Unit 構成的一個組
- Device Unit:硬體裝置
- Mount Unit:檔案系統的挂載點
- Automount Unit:自動挂載點
- Path Unit:檔案或路徑
- Scope Unit:不是由 Systemd 啟動的外部程序
- Slice Unit:程序組
- Snapshot Unit:Systemd 快照,可以切回某個快照
- Socket Unit:程序間通信的 socket
- Swap Unit:swap 檔案
- Timer Unit:定時器
systemctl list-units
指令可以檢視目前系統的所有 Unit 。
# 列出正在運作的 Unit
$ systemctl list-units
# 列出所有Unit,包括沒有找到配置檔案的或者啟動失敗的
$ systemctl list-units --all
# 列出所有沒有運作的 Unit
$ systemctl list-units --all --state=inactive
# 列出所有加載失敗的 Unit
$ systemctl list-units --failed
# 列出所有正在運作的、類型為 service 的 Unit
$ systemctl list-units --type=service
4.2 Unit 的狀态
systemctl status
指令用于檢視系統狀态和單個 Unit 的狀态。
# 顯示系統狀态
$ systemctl status
# 顯示單個 Unit 的狀态
$ sysystemctl status bluetooth.service
# 顯示遠端主機的某個 Unit 的狀态
$ systemctl -H [email protected] status httpd.service
除了
status
指令,
systemctl
還提供了三個查詢狀态的簡單方法,主要供腳本内部的判斷語句使用。
# 顯示某個 Unit 是否正在運作
$ systemctl is-active application.service
# 顯示某個 Unit 是否處于啟動失敗狀态
$ systemctl is-failed application.service
# 顯示某個 Unit 服務是否建立了啟動連結
$ systemctl is-enabled application.service
4.3 Unit 管理
對于使用者來說,最常用的是下面這些指令,用于啟動和停止 Unit(主要是 service)。
# 立即啟動一個服務
$ sudo systemctl start apache.service
# 立即停止一個服務
$ sudo systemctl stop apache.service
# 重新開機一個服務
$ sudo systemctl restart apache.service
# 殺死一個服務的所有子程序
$ sudo systemctl kill apache.service
# 重新加載一個服務的配置檔案
$ sudo systemctl reload apache.service
# 重載所有修改過的配置檔案
$ sudo systemctl daemon-reload
# 顯示某個 Unit 的所有底層參數
$ systemctl show httpd.service
# 顯示某個 Unit 的指定屬性的值
$ systemctl show -p CPUShares httpd.service
# 設定某個 Unit 的指定屬性
$ sudo systemctl set-property httpd.service CPUShares=500
4.4 依賴關系
Unit 之間存在依賴關系:A 依賴于 B,就意味着 Systemd 在啟動 A 的時候,同時會去啟動 B。
systemctl list-dependencies
指令列出一個 Unit 的所有依賴。
$ systemctl list-dependencies nginx.service
上面指令的輸出結果之中,有些依賴是 Target 類型(詳見下文),預設不會展開顯示。如果要展開 Target,就需要使用
--all
參數。
$ systemctl list-dependencies --all nginx.service
五、Unit 的配置檔案
5.1 概述
每一個 Unit 都有一個配置檔案,告訴 Systemd 怎麼啟動這個 Unit 。
Systemd 預設從目錄
/etc/systemd/system/
讀取配置檔案。但是,裡面存放的大部分檔案都是符号連結,指向目錄
/usr/lib/systemd/system/
,真正的配置檔案存放在那個目錄。
systemctl enable
指令用于在上面兩個目錄之間,建立符号連結關系。
$ sudo systemctl enable [email protected]
# 等同于
$ sudo ln -s '/usr/lib/systemd/system/[email protected]' '/etc/systemd/system/multi-user.target.wants/[email protected]'
如果配置檔案裡面設定了開機啟動,
systemctl enable
指令相當于激活開機啟動。
與之對應的,
systemctl disable
指令用于在兩個目錄之間,撤銷符号連結關系,相當于撤銷開機啟動。
$ sudo systemctl disable [email protected]
配置檔案的字尾名,就是該 Unit 的種類,比如
sshd.socket
。如果省略,Systemd 預設字尾名為
.service
,是以
sshd
會被了解成
sshd.service
。
5.2 配置檔案的狀态
systemctl list-unit-files
指令用于列出所有配置檔案。
# 列出所有配置檔案
$ systemctl list-unit-files
# 列出指定類型的配置檔案
$ systemctl list-unit-files --type=service
這個指令會輸出一個清單。
$ systemctl list-unit-files
UNIT FILE STATE
chronyd.service enabled
[email protected] static
[email protected] disabled
這個清單顯示每個配置檔案的狀态,一共有四種。
- enabled:已建立啟動連結
- disabled:沒建立啟動連結
- static:該配置檔案沒有
部分(無法執行),隻能作為其他配置檔案的依賴
[Install]
- masked:該配置檔案被禁止建立啟動連結
注意,從配置檔案的狀态無法看出,該 Unit 是否正在運作。這必須執行前面提到的
systemctl status
指令。
$ systemctl status bluetooth.service
一旦修改配置檔案,就要讓 SystemD 重新加載配置檔案,然後重新啟動,否則修改不會生效。
$ sudo systemctl daemon-reload
$ sudo systemctl restart httpd.service
5.3 配置檔案的格式
配置檔案就是普通的文本檔案,可以用文本編輯器打開。
systemctl cat
指令可以檢視配置檔案的内容。
$ systemctl cat atd.service
[Unit]
Description=ATD daemon
[Service]
Type=forking
ExecStart=/usr/bin/atd
[Install]
WantedBy=multi-user.target
從上面的輸出可以看到,配置檔案分成幾個區塊。每個區塊的第一行,是用方括号表示的差別名,比如
[Unit]
。注意,配置檔案的區塊名和字段名,都是大小寫敏感的。
每個區塊内部是一些等号連接配接的鍵值對。
[Section]
Directive1=value
Directive2=value
. . .
注意,鍵值對的等号兩側不能有空格。
5.4 配置檔案的區塊
[Unit]
區塊通常是配置檔案的第一個區塊,用來定義 Unit 的中繼資料,以及配置與其他 Unit 的關系。它的主要字段如下。
:簡短描述
Description
:文檔位址
Documentation
:目前 Unit 依賴的其他 Unit,如果它們沒有運作,目前 Unit 會啟動失敗
Requires
:與目前 Unit 配合的其他 Unit,如果它們沒有運作,目前 Unit 不會啟動失敗
Wants
:與
BindsTo
類似,它指定的 Unit 如果退出,會導緻目前 Unit 停止運作
Requires
:如果該字段指定的 Unit 也要啟動,那麼必須在目前 Unit 之後啟動
Before
:如果該字段指定的 Unit 也要啟動,那麼必須在目前 Unit 之前啟動
After
:這裡指定的 Unit 不能與目前 Unit 同時運作
Conflicts
:目前 Unit 運作必須滿足的條件,否則不會運作
Condition...
:目前 Unit 運作必須滿足的條件,否則會報啟動失敗
Assert...
[Install]
通常是配置檔案的最後一個區塊,用來定義如何啟動,以及是否開機啟動。它的主要字段如下。
:它的值是一個或多個 Target,目前 Unit 激活時(enable)符号連結會放入
WantedBy
目錄下面以 Target 名 +
/etc/systemd/system
字尾構成的子目錄中
.wants
:它的值是一個或多個 Target,目前 Unit 激活時,符号連結會放入
RequiredBy
/etc/systemd/system
.required
:目前 Unit 可用于啟動的别名
Alias
:目前 Unit 激活(enable)時,會被同時激活的其他 Unit
Also
[Service]
區塊用來 Service 的配置,隻有 Service 類型的 Unit 才有這個區塊。它的主要字段如下。
:定義啟動時的程序行為。它有以下幾種值。
Type
:預設值,執行
Type=simple
指定的指令,啟動主程序
ExecStart
:以 fork 方式從父程序建立子程序,建立後父程序會立即退出
Type=forking
:一次性程序,Systemd 會等目前服務退出,再繼續往下執行
Type=oneshot
:目前服務通過D-Bus啟動
Type=dbus
:目前服務啟動完畢,會通知
Type=notify
,再繼續往下執行
Systemd
:若有其他任務執行完畢,目前服務才會運作
Type=idle
:啟動目前服務的指令
ExecStart
:啟動目前服務之前執行的指令
ExecStartPre
:啟動目前服務之後執行的指令
ExecStartPost
:重新開機目前服務時執行的指令
ExecReload
:停止目前服務時執行的指令
ExecStop
:停止當其服務之後執行的指令
ExecStopPost
:自動重新開機目前服務間隔的秒數
RestartSec
:定義何種情況 Systemd 會自動重新開機目前服務,可能的值包括
Restart
(總是重新開機)、
always
、
on-success
on-failure
on-abnormal
on-abort
on-watchdog
:定義 Systemd 停止目前服務之前等待的秒數
TimeoutSec
:指定環境變量
Environment
Unit 配置檔案的完整字段清單,請參考官方文檔。
六、Target
啟動計算機的時候,需要啟動大量的 Unit。如果每一次啟動,都要一一寫明本次啟動需要哪些 Unit,顯然非常不友善。Systemd 的解決方案就是 Target。
簡單說,Target 就是一個 Unit 組,包含許多相關的 Unit 。啟動某個 Target 的時候,Systemd 就會啟動裡面所有的 Unit。從這個意義上說,Target 這個概念類似于"狀态點",啟動某個 Target 就好比啟動到某種狀态。
傳統的
init
啟動模式裡面,有 RunLevel 的概念,跟 Target 的作用很類似。不同的是,RunLevel 是互斥的,不可能多個 RunLevel 同時啟動,但是多個 Target 可以同時啟動。
# 檢視目前系統的所有 Target
$ systemctl list-unit-files --type=target
# 檢視一個 Target 包含的所有 Unit
$ systemctl list-dependencies multi-user.target
# 檢視啟動時的預設 Target
$ systemctl get-default
# 設定啟動時的預設 Target
$ sudo systemctl set-default multi-user.target
# 切換 Target 時,預設不關閉前一個 Target 啟動的程序,
# systemctl isolate 指令改變這種行為,
# 關閉前一個 Target 裡面所有不屬于後一個 Target 的程序
$ sudo systemctl isolate multi-user.target
Target 與 傳統 RunLevel 的對應關系如下。
Traditional runlevel New target name Symbolically linked to...
Runlevel 0 | runlevel0.target -> poweroff.target
Runlevel 1 | runlevel1.target -> rescue.target
Runlevel 2 | runlevel2.target -> multi-user.target
Runlevel 3 | runlevel3.target -> multi-user.target
Runlevel 4 | runlevel4.target -> multi-user.target
Runlevel 5 | runlevel5.target -> graphical.target
Runlevel 6 | runlevel6.target -> reboot.target
它與
init
程序的主要差别如下。
(1)預設的 RunLevel(在檔案設定)現在被預設的 Target 取代,位置是
/etc/inittab
,通常符号連結到
/etc/systemd/system/default.target
(圖形界面)或者
graphical.target
multi-user.target
(多使用者指令行)。
(2)啟動腳本的位置,以前是
目錄,符号連結到不同的 RunLevel 目錄 (比如
/etc/init.d
/etc/rc3.d
等),現在則存放在
/etc/rc5.d
和
/lib/systemd/system
/etc/systemd/system
目錄。
(3)配置檔案的位置,以前
程序的配置檔案是
init
,各種服務的配置檔案存放在
/etc/inittab
目錄。現在的配置檔案主要存放在
/etc/sysconfig
目錄,在
/lib/systemd
目錄裡面的修改可以覆寫原始設定。
/etc/systemd
七、日志管理
Systemd 統一管理所有 Unit 的啟動日志。帶來的好處就是,可以隻用
journalctl
一個指令,檢視所有日志(核心日志和應用日志)。日志的配置檔案是
/etc/systemd/journald.conf
journalctl
功能強大,用法非常多。
# 檢視所有日志(預設情況下 ,隻儲存本次啟動的日志)
$ sudo journalctl
# 檢視核心日志(不顯示應用日志)
$ sudo journalctl -k
# 檢視系統本次啟動的日志
$ sudo journalctl -b
$ sudo journalctl -b -0
# 檢視上一次啟動的日志(需更改設定)
$ sudo journalctl -b -1
# 檢視指定時間的日志
$ sudo journalctl --since="2012-10-30 18:17:16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"
# 顯示尾部的最新10行日志
$ sudo journalctl -n
# 顯示尾部指定行數的日志
$ sudo journalctl -n 20
# 實時滾動顯示最新日志
$ sudo journalctl -f
# 檢視指定服務的日志
$ sudo journalctl /usr/lib/systemd/systemd
# 檢視指定程序的日志
$ sudo journalctl _PID=1
# 檢視某個路徑的腳本的日志
$ sudo journalctl /usr/bin/bash
# 檢視指定使用者的日志
$ sudo journalctl _UID=33 --since today
# 檢視某個 Unit 的日志
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today
# 實時滾動顯示某個 Unit 的最新日志
$ sudo journalctl -u nginx.service -f
# 合并顯示多個 Unit 的日志
$ journalctl -u nginx.service -u php-fpm.service --since today
# 檢視指定優先級(及其以上級别)的日志,共有8級
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b
# 日志預設分頁輸出,--no-pager 改為正常的标準輸出
$ sudo journalctl --no-pager
# 以 JSON 格式(單行)輸出
$ sudo journalctl -b -u nginx.service -o json
# 以 JSON 格式(多行)輸出,可讀性更好
$ sudo journalctl -b -u nginx.serviceqq
-o json-pretty
# 顯示日志占據的硬碟空間
$ sudo journalctl --disk-usage
# 指定日志檔案占據的最大空間
$ sudo journalctl --vacuum-size=1G
# 指定日志檔案儲存多久
$ sudo journalctl --vacuum-time=1years
(完)