學會愛上 systemd

2020-05-13 08:51:00

systemd 是所有進程之母,負責將 Linux 主機啟動到可以做生產性任務的狀態。

systemd(是的,全小寫,即使在句子開頭也是小寫),是初始化程式(init)和 SystemV 初始化指令碼的現代替代者。此外,它還有更多功能。

當我想到 init 和 SystemV 初始化時,像大多數系統管理員一樣,我想到的是 Linux 的啟動和關閉,而不是真正意義上的管理服務,例如在服務啟動和執行後對其進行管理。像 init 一樣,systemd 是所有進程之母,它負責使 Linux 主機啟動到可以做生產性任務的狀態。systemd 設定的一些功能比老的初始化程式要廣泛得多,它要管理正在執行的 Linux 主機的許多方面,包括掛載檔案系統、管理硬體、處理定時器以及啟動和管理生產性主機所需的系統服務。

本系列文章是基於我的三期 Linux 培訓課程《使用和管理 Linux:從零開始進行學習系統管理》部分內容的摘錄,探討了 systemd 在啟動和啟動完成後的功能。

Linux 引導

Linux 主機從關機狀態到執行狀態的完整啟動過程很複雜,但它是開放的並且是可知的。在詳細介紹之前,我將簡要介紹一下從主機硬體被上電到系統準備好使用者登入的過程。大多數時候,“引導過程”被作為一個整體來討論,但這是不準確的。實際上,完整的引導和啟動過程包含三個主要部分:

  • 硬體引導:初始化系統硬體
  • Linux 引導boot:載入 Linux 核心和 systemd
  • Linux 啟動startup:systemd 為主機的生產性工作做準備

Linux 啟動階段始於核心載入了 init 或 systemd(取決於具體發行版使用的是舊的方式還是還是新的方式)之後。init 和 systemd 程式啟動並管理所有其它進程,它們在各自的系統上都被稱為“所有進程之母”。

將硬體引導與 Linux 引導及 Linux 啟動區分開,並明確定義它們之間的分界點是很重要的。理解它們的差異以及它們每一個在使 Linux 系統進入生產狀態所起的作用,才能夠管理這些進程,並更好地確定大部分人所謂的“啟動”問題出在哪裡。

啟動過程按照三步引導流程,使 Linux 計算機進入可進行生產工作的狀態。當核心將主機的控制權轉移到 systemd 時,啟動環節開始。

systemd 之爭

systemd 引起了系統管理員和其它負責維護 Linux 系統正常執行人員的廣泛爭議。在許多 Linux 系統中,systemd 接管了大量任務,這在某些開發者和sysadmins群體中引起了反對和不和諧。

SystemV 和 systemd 是執行 Linux 啟動環節的兩種不同的方法。SystemV 啟動指令碼和 init 程式是老的方法,而使用目標target的 systemd 是新方法。儘管大多數現代 Linux 發行版都使用較新的 systemd 進行啟動、關機和進程管理,但仍有一些發行版未採用。原因之一是某些發行版維護者和系統管理員喜歡老的 SystemV 方法,而不是新的 systemd。

我認為兩者都有其優勢。

為何我更喜歡 SystemV

我更喜歡 SystemV,因為它更開放。使用 Bash 指令碼來完成啟動。核心啟動 init 程式(這是一個編譯後的二進位制)後,init 啟動 rc.sysinit 指令碼,該指令碼執行許多系統初始化任務。rc.sysinit 執行完後,init 啟動 /etc/rc.d/rc 指令碼,該指令碼依次啟動 /etc/rc.d/rcX.d 中由 SystemV 啟動指令碼定義的各種服務。其中 X 是待啟動的執行級別號。

除了 init 程式本身之外,所有這些程式都是開放且易於理解的指令碼。可以通讀這些指令碼並確切了解整個啟動過程中發生的事情,但是我不認為有太多系統管理員真正做到這一點。每個啟動指令碼都被編了號,以便按特定順序啟動預期的服務。服務是序列啟動的,一次只能啟動一個服務。

systemd 是由 Red Hat 的 Lennart Poettering 和 Kay Sievers 開發的,它是一個由大型的、編譯的二進位制可執行檔案構成的複雜系統,不存取其原始碼就無法理解。它是開源的,因此“存取其原始碼”並不難,只是不太方便。systemd 似乎表現出對 Linux 哲學多個原則的重大駁斥。作為二進位制檔案,systemd 無法被直接開啟供系統管理員檢視或進行簡單更改。systemd 試圖做所有事情,例如管理正在執行的服務,同時提供明顯比 SystemV 更多的狀態資訊。它還管理硬體、進程、行程群組、檔案系統掛載等。systemd 幾乎涉足於現代 Linux 主機的每個方面,使它成為系統管理的一站式工具。所有這些都明顯違反了“程式應該小,且每個程式都應該只做一件事並做好”的原則。

為何我更喜歡 systemd

我更喜歡用 systemd 作為啟動機制,因為它會根據啟動階段並行地啟動盡可能多的服務。這樣可以加快整個的啟動速度,使得主機系統比 SystemV 更快地到達登入螢幕。

systemd 幾乎可以管理正在執行的 Linux 系統的各個方面。它可以管理正在執行的服務,同時提供比SystemV 多得多的狀態資訊。它還管理硬體、進程和行程群組、檔案系統掛載等。systemd 幾乎涉足於現代 Linux 作業系統的每方面,使其成為系統管理的一站式工具。(聽起來熟悉吧?)

systemd 工具是編譯後的二進位制檔案,但該工具包是開放的,因為所有組態檔都是 ASCII 文字檔案。可以通過各種 GUI 和命令列工具來修改啟動設定,也可以新增或修改各種組態檔來滿足特定的本地計算環境的需求。

真正的問題

你認為我不能喜歡兩種啟動系統嗎?我能,我會用它們中的任何一個。

我認為,SystemV 和 systemd 之間大多數爭議的真正問題和根本原因在於,在系統管理層面沒有選擇權。使用 SystemV 還是 systemd 已經由各種發行版的開發人員、維護人員和打包人員選擇了(但有充分的理由)。由於 init 極端的侵入性,挖出並替換 init 系統會帶來很多影響,會帶來很多在發行版設計過程之外難以解決的後果。

儘管該選擇實際上是為我而選的,但我的Linux主機能不能開機、能不能工作,這是我平時最關心的。作為終端使用者,甚至是系統管理員,我主要關心的是我是否可以完成我的工作,例如寫我的書和這篇文章,安裝更新以及編寫指令碼來自動化所有事情。只要我能做我的工作,我就不會真正在意發行版中使用的啟動系統。

在啟動或服務管理出現問題時,我會在意。無論主機上使用哪種啟動系統,我都足夠了解如何沿著事件順序來查詢故障並進行修復。

替換SystemV

以前曾有過用更現代的東西替代 SystemV 的嘗試。大約在兩個版本中,Fedora 使用了一個叫作 Upstart 的東西來替換老化的 SystemV,但是它沒有取代 init,也沒有提供我所注意到的任何變化。由於 Upstart 並未對 SystemV 的問題進行任何顯著的改變,所以在這個方向上的努力很快就被放棄了,轉而使用 systemd。

儘管大部分 Linux 開發人員都認可替換舊的 SystemV 啟動系統是個好主意,但許多開發人員和系統管理員並不喜歡 systemd。與其重新討論人們在 systemd 中遇到的或曾經遇到過的所有所謂的問題,不如帶你去看兩篇好文章,儘管有些陳舊,但它們涵蓋了大多數內容。Linux 核心的建立者 Linus Torvalds 對 systemd 似乎不感興趣。在 2014 年 ZDNet 的一篇文章《Linus Torvalds 和其他人對 Linux 上的 systemd 的看法》中,Linus 清楚地表達了他的感受。

“實際上我對 systemd 本身沒有任何特別強烈的意見。我對一些核心開發人員有一些問題,我認為他們在對待錯誤和相容性方面過於輕率,而且我認為某些設計細節是瘋狂的(例如,我不喜歡二進位制紀錄檔),但這只是細節,不是大問題。”

如果你對 Linus 不太了解的話,我可以告訴你,如果他不喜歡某事,他是非常直言不諱的,很明確,而且相當明確的表示不喜歡。他解決自己對事物不滿的方式已經被社會更好地接受了。

2013 年,Poettering 寫了一篇很長的部落格,他在文章駁斥了關於 systemd 的迷思,同時對建立 systemd 的一些原因進行了深入的剖析。這是一分很好的讀物,我強烈建議你閱讀。

systemd 任務

根據編譯過程中使用的選項(不在本系列中介紹),systemd 可以有多達 69 個二進位制可執行檔案執行以下任務,其中包括:

  • systemd 程式以 1 號進程(PID 1)執行,並提供使盡可能多服務並行啟動的系統啟動能力,它額外加快了總體啟動時間。它還管理關機順序。
  • systemctl 程式提供了服務管理的使用者介面。
  • 支援 SystemV 和 LSB 啟動指令碼,以便向後相容。
  • 服務管理和報告提供了比 SystemV 更多的服務狀態資料。
  • 提供基本的系統設定工具,例如主機名、日期、語言環境、已登入使用者的列表,正在執行的容器和虛擬機器、系統帳戶、執行時目錄及設定,用於簡易網路設定、網路時間同步、紀錄檔轉發和名稱解析的守護行程。
  • 提供通訊端管理。
  • systemd 定時器提供類似 cron 的高階功能,包括在相對於系統啟動、systemd 啟動時間、定時器上次啟動時間的某個時間點執行指令碼。
  • 它提供了一個工具來分析定時器規範中使用的日期和時間。
  • 能感知分層的檔案系統掛載和解除安裝功能可以更安全地級聯掛載的檔案系統。
  • 允許主動的建立和管理臨時檔案,包括刪除。
  • D-Bus 的介面提供了在插入或移除裝置時執行指令碼的能力。這允許將所有裝置(無論是否可插拔)都被視為隨插即用,從而大大簡化了裝置的處理。
  • 分析啟動環節的工具可用於查詢耗時最多的服務。
  • 它包括用於儲存系統訊息的紀錄檔以及管理紀錄檔的工具。

架構

這些以及更多的任務通過許多守護程式、控制程式和組態檔來支援。圖 1 顯示了許多屬於 systemd 的元件。這是一個簡化的圖,旨在提供概要描述,因此它並不包括所有獨立的程式或檔案。它也不提供資料流的視角,資料流是如此複雜,因此在本系列文章的背景下沒用。

系統架構

圖 1:systemd 的架構,作者 Shmuel Csaba Otto Traian (CC BY-SA 3.0)

如果要完整地講解 systemd 就需要一本書。你不需要了解圖 1 中的 systemd 元件是如何組合在一起的細節。只需瞭解支援各種 Linux 服務管理以及紀錄檔檔案和紀錄檔處理的程式和元件就夠了。但是很明顯, systemd 並不是某些批評者所宣稱的那樣,它是一個單一的怪物。

作為 1 號進程的 systemd

systemd 是 1 號進程(PID 1)。它的一些功能,比老的 SystemV3 init 要廣泛得多,用於管理正在執行的 Linux 主機的許多方面,包括掛載檔案系統以及啟動和管理 Linux 生產主機所需的系統服務。與啟動環節無關的任何 systemd 任務都不在本文討論範圍之內(但本系列後面的一些文章將探討其中的一些任務)。

首先,systemd 掛載 /etc/fstab 所定義的檔案系統,包括所有交換檔案或分割區。此時,它可以存取位於 /etc 中的組態檔,包括它自己的組態檔。它使用其設定連結 /etc/systemd/system/default.target 來確定將主機引導至哪個狀態或目標。default.target 檔案是指向真實目標檔案的符號連結。對於桌面工作站,通常是 graphical.target,它相當於 SystemV 中的執行級別 5。對於伺服器,預設值更可能是 multi-user.target,相當於 SystemV 中的執行級別 3。emergency.target 類似於單使用者模式。目標target服務service是 systemd 的單元unit

下表(圖 2)將 systemd 目標與老的 SystemV 啟動執行級別進行了比較。systemd 提供 systemd 目標別名以便向後相容。目標別名允許指令碼(以及許多系統管理員)使用 SystemV 命令(如 init 3)更改執行級別。當然,SystemV 命令被轉發給 systemd 進行解釋和執行。

systemd 目標SystemV 執行級別目標別名描述
default.target  此目標總是通過符號連線的方式成為 multi-user.targetgraphical.target 的別名。systemd 始終使用 default.target 來啟動系統。default.target 絕不應該設為 halt.targetpoweroff.targetreboot.target 的別名。
graphic.target5runlevel5.target帶有 GUI 的 multi-user.target
 4runlevel4.target未用。在 SystemV 中執行級別 4 與執行級別 3 相同。可以建立並自定義此目標以啟動本地服務,而無需更改預設的 multi-user.target
multi-user.target3runlevel3.target所有服務在執行,但僅有命令列介面(CLI)。
 2runlevel2.target多使用者,沒有 NFS,其它所有非 GUI 服務在執行。
rescue.target1runlevel1.target基本系統,包括掛載檔案系統,執行最基本的服務和主控制台的恢復 shell。
emergency.targetS 單使用者模式:沒有服務執行;不掛載檔案系統。這是最基本的工作級別,只有主控制台上執行的一個緊急 Shell 供使用者與系統互動。
halt.target  停止系統而不關閉電源。
reboot.target6runlevel6.target重新啟動。
poweroff.target0runlevel0.target停止系統並關閉電源。

圖 2:SystemV 執行級別與 systemd 目標和一些目標別名的比較

每個目標在其組態檔中都描述了一個依賴集。systemd 啟動必須的依賴項,這些依賴項是執行 Linux 主機到特定功能級別所需的服務。當目標組態檔中列出的所有依賴項被載入並執行後,系統就在該目標級別執行了。在圖 2 中,功能最多的目標位於錶的頂部,從頂向下,功能逐步遞減。

systemd 還會檢查老的 SystemV init 目錄,以確認是否存在任何啟動檔案。如果有,systemd 會將它們作為組態檔以啟動它們描述的服務。網路服務是一個很好的例子,在 Fedora 中它仍然使用 SystemV 啟動檔案。

圖 3(如下)是直接從啟動手冊頁複製來的。它顯示了 systemd 啟動期間一般的事件環節以及確保成功啟動的基本順序要求。

                                        cryptsetup-pre.target                                                   | (various low-level                                v     API VFS mounts:                 (various cryptsetup devices...)  mqueue, configfs,                                |    |  debugfs, ...)                                    v    |  |                                  cryptsetup.target  |  |  (various swap                                 |    |    remote-fs-pre.target  |   devices...)                                  |    |     |        |  |    |                                           |    |     |        v  |    v                       local-fs-pre.target |    |     |  (network file systems)  |  swap.target                       |           |    v     v                 |  |    |                               v           |  remote-cryptsetup.target  |  |    |  (various low-level  (various mounts and  |             |              |  |    |   services: udevd,    fsck services...)   |             |    remote-fs.target  |    |   tmpfiles, random            |           |             |             /  |    |   seed, sysctl, ...)          v           |             |            /  |    |      |                 local-fs.target    |             |           /  |    |      |                        |           |             |          /  \____|______|_______________   ______|___________/             |         /                              \ /                                |        /                               v                                 |       /                        sysinit.target                           |      /                               |                                 |     /        ______________________/|\_____________________           |    /       /              |        |      |               \          |   /       |              |        |      |               |          |  /       v              v        |      v               |          | /  (various       (various      |  (various            |          |/   timers...)      paths...)   |   sockets...)        |          |       |              |        |      |               |          |       v              v        |      v               |          | timers.target  paths.target   |  sockets.target      |          |       |              |        |      |               v          |       v              \_______ | _____/         rescue.service   |                              \|/                     |          |                               v                      v          |                           basic.target         rescue.target    |                               |                                 |                       ________v____________________             |                      /              |              \            |                      |              |              |            |                      v              v              v            |                  display-    (various system   (various system  |              manager.service     services        services)      |                      |         required for        |            |                      |        graphical UIs)       v            v                      |              |            multi-user.target emergency.service    |              |              |         |            \_____________ | _____________/         v                          \|/ emergency.target                    v                              graphical.target

圖 3: systemd 啟動圖

sysinit.targetbasic.target 目標可以看作啟動過程中的檢查點。儘管 systemd 的設計目標之一是並行啟動系統服務,但是某些服務和功能目標必須先啟動,然後才能啟動其它服務和目標。直到該檢查點所需的所有服務和目標被滿足後才能通過這些檢查點。

sysinit.target 所依賴的所有單元都完成時,就會到達 sysinit.target。所有這些單元,包括掛載檔案系統、設定交換檔案、啟動 Udev、設定亂數生成器種子、啟動低層服務以及設定安全服務(如果一個或多個檔案系統是加密的)都必須被完成,但在 sysinit.target 中,這些任務可以並行執行。

sysinit.target 啟動了系統接近正常執行所需的所有低層服務和單元,它們也是進入 basic.target 所需的。

在完成 sysinit.target 之後,systemd 會啟動實現下一個目標所需的所有單元。basic.target 通過啟動所有下一目標所需的單元來提供一些額外功能。包括設定為各種可執行程式目錄的路徑、設定通訊通訊端和計時器之類。

最後,使用者級目標 multi-user.targetgraphical.target 被初始化。要滿足 graphical.target 的依賴必須先達到 multi-user.target。圖 3 中帶下劃線的目標是通常的啟動目標。當達到這些目標之一時,啟動就完成了。如果 multi-user.target 是預設設定,那麼你應該在控制台上看到文字模式的登入介面。如果 graphical.target 是預設設定,那麼你應該看到圖形的登入介面。你看到的具體的 GUI 登入介面取決於你的預設顯示管理器。

引導手冊頁還描述並提供了引導到初始化 RAM 磁碟和 systemd 關機過程的圖。

systemd 還提供了一個工具,該工具列出了完整的啟動過程或指定單元的依賴項。單元是一個可控的 systemd 資源實體,其範圍可以從特定服務(例如 httpd 或 sshd)到計時器、掛載、通訊端等。嘗試以下命令並捲動檢視結果。

systemctl list-dependencies graphical.target

注意,這會完全展開使系統進入 graphical.target 執行模式所需的頂層目標單元列表。也可以使用 --all 選項來展開所有其它單元。

systemctl list-dependencies --all graphical.target

你可以使用 less 命令來搜尋諸如 targetslicesocket 之類的字串。

現在嘗試下面的方法。

systemctl list-dependencies multi-user.target

systemctl list-dependencies rescue.target

systemctl list-dependencies local-fs.target

systemctl list-dependencies dbus.service

這個工具幫助我視覺化我正用的主機的啟動依賴細節。繼續花一些時間探索一個或多個 Linux 主機的啟動樹。但是要小心,因為 systemctl 手冊頁包含以下注釋:

“請注意,此命令僅列出當前被服務管理器載入到記憶體的單元。尤其是,此命令根本不適合用於獲取特定單元的全部反向依賴關係列表,因為它不會列出被單元宣告了但是未載入的依賴項。”

結尾語

即使在沒有深入研究 systemd 之前,很明顯能看出它既強大又複雜。顯然,systemd 不是單一、龐大、獨體且不可知的二進位制檔案。相反,它是由許多較小的元件和旨在執行特定任務的子命令組成。

本系列的下一篇文章將更詳細地探討 systemd 的啟動,以及 systemd 的組態檔,更改預設的目標以及如何建立簡單服務單元。

資源

網際網路上有大量關於 systemd 的資訊,但是很多都很簡短、晦澀甚至是帶有誤導。除了本文提到的資源外,以下網頁還提供了有關 systemd 啟動的更詳細和可靠的資訊。

  • Fedora 專案有一個很好的實用的 systemd 指南。它有你需要知道的通過 systemd 來設定、管理和維護 Fedora 主機所需的幾乎所有知識。
  • Fedora 專案還有一個不錯的速記表,將老的 SystemV 命令與對比的 systemd 命令相互關聯。
  • 有關 systemd 的詳細技術資訊及建立它的原因,請檢視 Freedesktop.orgsystemd 描述
  • Linux.com 的“systemd 的更多樂趣”提供了更高階的 systemd 資訊和技巧

還有針對 Linux 系統管理員的一系列技術性很強的文章,作者是 systemd 的設計師和主要開發者 Lennart Poettering。這些文章是在 2010 年 4 月至 2011 年 9 月之間撰寫的,但它們現在和那時一樣有用。關於 systemd 及其生態的其它許多好文都基於這些論文。