在容器中執行 systemd

我們長期以來一直關注在容器中使用 systemd 的話題。 早在 2014 年,我們的安全工程師 Daniel Walsh 就寫了一篇文章 在 Docker 容器中執行 systemd,幾年後 - 另一個,被稱為 在非特權容器中執行 systemd,他在其中表示情況並沒有太大改善。 他特別寫道,“不幸的是,即使是兩年後,如果你用谷歌搜尋“Docker 系統”,首先出現的還是他的同一篇舊文章。 所以是時候改變一些事情了。” 另外,我們已經討論過 Docker 與 systemd 開發人員之間的衝突.

在容器中執行 systemd

在本文中,我們將展示隨著時間的推移發生了什麼變化,以及 Podman 如何在這方面幫助我們。

在容器內執行 systemd 有許多原因,例如:

  1. 多服務容器 – 許多人希望將多服務應用程式從虛擬機器中拉出來並在容器中運行。 當然,最好將此類應用程式分解為微服務,但並不是每個人都知道如何做到這一點,或者只是沒有時間。 因此,將此類應用程式作為 systemd 從單元檔案啟動的服務來運行是非常有意義的。
  2. Systemd 單元文件 – 在容器內執行的大多數應用程式都是根據先前在虛擬機器或實體機上執行的程式碼建構的。 這些應用程式有一個為這些應用程式編寫的單元文件,並了解它們應該如何啟動。 因此,最好使用支援的方法啟動服務,而不是破解您自己的 init 服務。
  3. Systemd 是一個行程管理器。 它比任何其他工具都能更好地管理服務(關閉、重新啟動服務或殺死殭屍進程)。

也就是說,不在容器中運行 systemd 的原因有很多。 主要的一個是 systemd/journald 控制容器的輸出,以及諸如 Kubernetes開班 期望容器將日誌直接寫入 stdout 和 stderr。 因此,如果您打算透過上面提到的編排工具來管理容器,您應該認真考慮使用基於 systemd 的容器。 此外,Docker 和 Moby 開發人員經常強烈反對在容器中使用 systemd。

波德曼的到來

我們很高興地報告,情況終於有了進展。 紅帽負責運作容器的團隊決定開發 你自己的容器引擎。 他有一個名字 波德曼 並提供與 Docker 相同的命令列介面 (CLI)。 而幾乎所有的 Docker 指令都可以在 Podman 中以相同的方式使用。 我們經常舉辦研討會,現在稱為 將 Docker 更改為 Podman,第一張投影片要求編寫:alias docker=podman。

很多人都這麼做。

我和我的 Podman 絕不反對基於 systemd 的容器。 畢竟,Systemd 是最常用的 Linux init 子系統,不允許它在容器中正常工作意味著忽視成千上萬的人習慣運行容器。

Podman 知道如何讓 systemd 在容器中正常運作。 它需要在 /run 和 /tmp 上安裝 tmpfs 之類的東西。 她喜歡啟用「容器化」環境,並希望獲得對她所在的 cgroup 目錄和 /var/log/journald 資料夾的寫入權限。

當您啟動第一個指令是 init 或 systemd 的容器時,Podman 會自動設定 tmpfs 和 Cgroup,以確保 systemd 啟動沒有問題。 若要封鎖此自動啟動模式,請使用 --systemd=false 選項。 請注意,Podman 僅在需要執行 systemd 或 init 命令時才使用 systemd 模式。

以下是手冊的摘錄:

男子 podman 運行
...

–systemd=true|false

在 systemd 模式下運作容器。 預設啟用。

如果您在容器內執行 systemd 或 init 指令,Podman 會在下列目錄中設定 tmpfs 掛載點:

/run、/run/lock、/tmp、/sys/fs/cgroup/systemd、/var/lib/journal

預設停止訊號也是 SIGRTMIN+3。

所有這些都允許 systemd 無需任何修改即可在封閉的容器中運行。

注意:systemd 嘗試寫入 cgroup 檔案系統。 然而,SELinux 預設會阻止容器執行此操作。 若要啟用寫入,請啟用container_manage_cgroup布林參數:

setebool -P container_manage_cgroup true

現在來看看使用 Podman 在容器中執行 systemd 的 Dockerfile 是什麼樣子的:

# cat Dockerfile

FROM fedora

RUN dnf -y install httpd; dnf clean all; systemctl enable httpd

EXPOSE 80

CMD [ "/sbin/init" ]

這就是全部。

現在我們組裝容器:

# podman build -t systemd .

我們告訴 SELinux 允許 systemd 修改 Cgroups 設定:

# setsebool -P container_manage_cgroup true

順便說一句,很多人都忘了這一步。 幸運的是,這只需要完成一次,並且在重新啟動系統後會保存設定。

現在我們啟動容器:

# podman run -ti -p 80:80 systemd

systemd 239 running in system mode. (+PAM +AUDIT +SELINUX +IMA -APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN2 -IDN +PCRE2 default-hierarchy=hybrid)

Detected virtualization container-other.

Detected architecture x86-64.

Welcome to Fedora 29 (Container Image)!

Set hostname to <1b51b684bc99>.

Failed to install release agent, ignoring: Read-only file system

File /usr/lib/systemd/system/systemd-journald.service:26 configures an IP firewall (IPAddressDeny=any), but the local system does not support BPF/cgroup based firewalling.

Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)

[  OK ] Listening on initctl Compatibility Named Pipe.

[  OK ] Listening on Journal Socket (/dev/log).

[  OK ] Started Forward Password Requests to Wall Directory Watch.

[  OK ] Started Dispatch Password Requests to Console Directory Watch.

[  OK ] Reached target Slices.

…

[  OK ] Started The Apache HTTP Server.

就這樣,服務已啟動並運行:

$ curl localhost

<html  xml_lang="en" lang="en">

…

</html>

注意:不要在 Docker 上嘗試這個! 在那裡,您仍然需要用手鼓跳舞才能透過守護進程啟動這些類型的容器。 (需要額外的欄位和套件才能使這一切在 Docker 中無縫工作,或需要在特權容器中運行。有關詳細信息,請參閱 文章.)

關於 Podman 和 systemd 的一些更酷的事情

Podman 在 systemd 單元檔案中比 Docker 工作得更好

如果需要在系統啟動時啟動容器,那麼您只需將適當的 Podman 命令插入到 systemd 單元檔案中,這將啟動服務並對其進行監控。 Podman 使用標準的 fork-exec 模型。 換句話說,容器進程是 Podman 進程的子進程,因此 systemd 可以輕鬆監控它們。

Docker採用客戶端-伺服器模型,Docker CLI指令也可以直接放在單元檔案中。 然而,一旦 Docker 用戶端連線到 Docker 守護程式,它(客戶端)就變成了另一個處理 stdin 和 stdout 的進程。 反過來,systemd 不知道 Docker 用戶端和在 Docker 守護程式控制下運行的容器之間的連接,因此,在這個模型中,systemd 根本無法監控服務。

透過套接字激活systemd

Podman 正確處理透過套接字的啟動。 由於 Podman 使用 fork-exec 模型,因此它可以將套接字轉送到其子容器進程。 Docker 無法做到這一點,因為它使用客戶端-伺服器模型。

Podman 用於與遠端客戶端與容器進行通訊的 varlink 服務實際上是透過套接字啟動的。 cockpit-podman 套件是用 Node.js 編寫的,是 cockpit 專案的一部分,讓人們透過 Web 介面與 Podman 容器進行互動。 執行 cockpit-podman 的 Web 守護程式將訊息傳送到 systemd 偵聽的 varlink 套接字。 然後 Systemd 啟動 Podman 程式來接收訊息並開始管理容器。 透過套接字啟動 systemd 可以消除在實現遠端 API 時持續運行的守護程序的需要。

此外,我們正在開發另一個名為 podman-remote 的 Podman 用戶端,它實作相同的 Podman CLI,但呼叫 varlink 來運行容器。 Podman-remote 可以在 SSH 會話之上運行,讓您可以安全地與不同電腦上的容器互動。 隨著時間的推移,我們計劃讓 podman-remote 支援 MacOS 和 Windows 以及 Linux,以便這些平台上的開發人員可以運行運行 Podman varlink 的 Linux 虛擬機,並獲得容器在本地電腦上運行的完整體驗。

SD_NOTIFY

Systemd 可讓您延遲輔助服務的啟動,直到它們需要的容器化服務啟動為止。 Podman 可以將 SD_NOTIFY 套接字轉發到容器化服務,以便該服務通知 systemd 它已準備好運作。 再說一遍,使用客戶端-伺服器模型的 Docker 無法做到這一點。

計劃中

我們計劃新增指令 podmangeneratesystemdCONTAINERID,該指令將產生一個systemd單元檔案來管理指定的特定容器。 對於非特權容器,這應該在根模式和無根模式下都有效。 我們甚至看到了對 OCI 相容的 systemd-nspawn 運行時的請求。

結論

在容器中執行 systemd 是一種可以理解的需求。 多虧了 Podman,我們終於擁有了一個不與 systemd 衝突、而且易於使用的容​​器運行時。

來源: www.habr.com

添加評論