標籤:Container

掛載 WSL2 的目錄到 Podman 中

TL;DR

cat <<-EOF | sudo tee /etc/systemd/system/mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
[Unit]
Description=WSL Instances

[Mount]
What=/
Where=/mnt/wsl/instances/${WSL_DISTRO_NAME}
Type=none
Options=defaults,bind,X-mount.mkdir

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
podman-remote run \
  -v /mnt/wsl/instances/${WSL_DISTRO_NAME}/`pwd`:/app \
  docker.io/library/busybox ls /app

前情提要

Podman[1] 是一個由 Redhat[2] 主推的 Container[3] 工具,如果有用過容器的讀者們應該對於與其類似的 Docker[4] 不陌生,Podman 就是一個這樣的容器管理工具。

與 Docker 不同的是,Podman 另外引入了 Pod[5] 的概念,以及具有預設支援 rootless 及去 daemon 化等特色。

Podman Desktop

Podman 團隊在各平臺上推出了桌面管理工具 —— Podman Desktop[6],類似於 Docker Desktop 提供視覺化的管理功能外,也能協助使用者安裝 Podman 至系統中並提供各種設定及擴充功能。

有興趣從 Docker 跳船的讀者可以參考 Podman Desktop 提供的跳船指南: https://podman-desktop.io/docs/migrating-from-docker

Windows 下的 Podman

在 Windows 上,Podman 會在 Windows 中安裝 podman.exe,協助你在 Host 的 Command line 中輕鬆存取 Podman,例如我們可以打開一個 PowerShell(或 CMD),執行 podman version,即可看到類似下方截圖的結果:

podman-on-windows.png

大家可以注意到,雖然我們可以在 Windows 下面執行 Podman 指令,但實際上的 Podman Engine 卻是執行在 Linux 上的,這是因為 Podman 目前只能管理 Linux Container,因此 Podman 決定將 Podman Engine 跑在 WSL2 中,如果有安裝 Windows Terminal 的話,可以從自動產生的下拉選單中發現一個新的 WSL2 的 Distro。

podman-machine-default.png

並且 Podman 會幫你做好與 Windows Host 的整合,例如可以直接掛載 Windows 的目錄或檔案進容器中,例如:

PS C:\Users\Davy\podman-test> echo "Hi Podman from Windows" > hello.txt
PS C:\Users\Davy\podman-test> podman run --rm -v .\hello.txt:/hello.txt docker.io/library/busybox cat /hello.txt
Trying to pull docker.io/library/busybox:latest...
Getting image source signatures
Copying blob sha256:ec562eabd705d25bfea8c8d79e4610775e375524af00552fe871d3338261563c
Copying config sha256:65ad0d468eb1c558bf7f4e64e790f586e9eda649ee9f130cd0e835b292bbc5ac
Writing manifest to image destination
��Hi Podman from Windows
PS C:\Users\Davy\podman-test>

(請忽略那兩個看不見的 BOM 符號……)

也就是說,我們可以隨意地編寫 PowerShell 腳本(或 CMD 批次檔)來操作 Podman!😏

WSL2 下的 Podman

那麼,通常會使用 Podman 的話,應該會對 WSL2[7] 也不陌生吧!

於是,我們可能會想,既然 Windows Host 都能如此順暢地與 WSL2 中的 Podman 整合了,那麼原本就在 WSL2 的其他 Distro 應該可以更好地整合吧……嗎?

很可惜,Podman Desktop 只丟了一個頁面[8]告訴你,你要自己整合。

整合方式大致就是在 WSL2 中安裝一個 Podman,並將 podman-machine-default 提供的 Podman socket 接上這個 Podman,方法如下:

安裝 Podman / Podman Remote

Podman Desktop 推薦安裝 podman-remote[9] 作為你在 WSL2 中使用的 Podman CLI,安裝方式也很簡單,到 containers/podman 的 Release 頁面中下載適合的 podman-remote 執行檔後放入 $PATH(例如 /usr/local/bin/usr/bin) 中,並將 podman alias 成 podman-remote 或直接 ln -s podman-remote podman

接下來就可以嘗試在 WSL2 中執行看看 podman(或 podman-remote)了!

$ podman-remote ps
Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM
Error: unable to connect to Podman socket: Get "http://d/v5.1.2/libpod/_ping": dial unix /mnt/wsl/podman/podman-machine-default.socket: connect: connection refused

咦?為什麼不會動呢? 啊,原來是忘記將 podman-remotepodman-machine-default 中的 Podman 連結在一起了,還需要下面的設定才行:

$ podman-remote system connection add --default \
  podman-machine-default-root \
  unix:///mnt/wsl/podman-sockets/podman-machine-default/podman-root.sock
# usermod --append --groups 10 $(whoami)

設定完成之後重新登入 Shell(或重新打開一個新的 Terminal),再執行一次 podman-remote 看看:

$ podman-remote ps
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES
$ podman-remote run --rm quay.io/podman/hello
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob sha256:81df7ff16254ed9756e27c8de9ceb02a9568228fccadbf080f41cc5eb5118a44
Copying config sha256:5dd467fce50b56951185da365b5feee75409968cbab5767b9b59e325fb2ecbc0
Writing manifest to image destination
!... Hello Podman World ...!

         .--"--.
       / -     - \
      / (O)   (O) \
   ~~~| -=(,Y,)=- |
    .---. /`  \   |~~
 ~/  o  o \~~~~.----. ~~
  | =(X)= |~  / (O (O) \
   ~~~~~~~  ~| =(Y_)=-  |
  ~~~~    ~~~|   U      |~~

Project:   https://github.com/containers/podman
Website:   https://podman.io
Desktop:   https://podman-desktop.io
Documents: https://docs.podman.io
YouTube:   https://youtube.com/@Podman
X/Twitter: @Podman_io
Mastodon:  @[email protected]
$

如此一來就可以在 WSL2 中操作與 Windows 摸到的同一個 Podman 了!

掛載 WSL2 中的路徑至 Podman

就算按照步驟將 WSL2 與 Podman 整合起來之後,你還會發現一些小小小問題 —— 例如掛載不到 WSL2 中的目錄……

~/podman-example$ echo "Hello from WSL2" > hello.txt
~/podman-example$ cat `pwd`/hello.txt
Hello from WSL2
~/podman-example$ podman-remote run --rm \
  -v `pwd`/hello.txt:/hello.txt \
  docker.io/library/busybox \
  cat /hello.txt
Error: statfs /home/davy/podman-example/hello.txt: no such file or directory
~/podman-example$

咦? 奇怪? 為什麼他會跟我說這個測試用的檔案不存在,明明直接 cat 會動的呀……

而且在 Docker 裡面如果這樣子做的話是會動的,為什麼在 Podman 中不行呢?

原來是在 Docker Desktop 中,對於掛載 WSL2 Distro 中的路徑時,Docker 會轉而向一個輔助工具求助,這個工具會協助 Docker 存取其他 WSL2 中的資料[10]。因為 WSL2 中各個 Distro 的資料是各自獨立的,所以 Docker/Podman 的 WSL2 預設沒有辦法直接存取其他 Distro 的資料,此時會導致掛載路徑時會只能在自己的 WSL2 中搜尋,我們可以來做個實驗:

我們嘗試將 /home 掛載到 Podman container 中,在此之前我們先來確認在兩個 WSL2 中的檔案狀態:

user@podman-machine-default:~ $ ls /home/
user

davy@Ubuntu:~ $ ls /home/
davy

接下來我們開始嘗試掛載:

davy@Ubuntu:~ $ podman-remote run --rm -v /home/:/the-home docker.io/library/busybox ls /the-home
user
davy@Ubuntu:~ $

可以看到,podman-remotepodman-machine-default 中的 /home 掛載到了容器中,而非我們期望的 WSL2 Distro 中的 /home

也就是說,--volume/-v 參數,不論在那個 WSL2 Distro,對應到的都是 podman-machine-default 中的路徑。

那麼,我們該如何掛載 WSL2 中其他 Distro 的路徑呢?

WSL2 的神奇共用目錄

在 WSL2 中,有一個神祕的路徑是全 WSL2 Distro 共享的,那就是 /mnt/wsl,Podman 也是藉由將 Podman Engine 的 socket 放在這個地方來讓其他 Distro 可以存取[11]。WSL2 將這個神奇目錄用在存放與全 Distro 共享的設定等資料,大家可以嘗試下面的指令:

$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 20 Jul 17 22:11 /etc/resolv.conf -> /mnt/wsl/resolv.conf
$ ls -l /mnt/wsl/resolv.conf
-rw-r--r-- 1 root root 213 Jul 13 09:12 /mnt/wsl/resolv.conf
$ cat /mnt/wsl/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 10.255.255.254
search local
$

可以看到其實所有 Distro 的 /etc/resolv.conf[12],都是指向 /mnt/wsl/resolv.conf,而這個檔案會被 WSL2 自動地更新,WSL2 藉由這個方式來讓所有的 Distro 共用相同的設定。

於是,我們可以透過這個神奇的小地方來把我們要分享的東西放在這裡,再讓 podman-machine-default 來這裡存取即可!

解決方案

既然知道了方法,那麼我們就往下來執行,最簡單的方式就是利用 mount 指令將 WSL2 各個 Distro 的根目錄分享到 /mnt/wsl 下面,如此一來 podman-machine-default 就可以存取到裡面的內容了![13]

$ echo $WSL_DISTRO_NAME
Ubuntu
# mount -t bind -o defaults,bind,X-mount.mkdir / /mnt/wsl/instances/${WSL_DISTRO_NAME}
$ ls /mnt/wsl/instances/
Ubuntu
$ ls /mnt/wsl/instances/Ubuntu/home/
davy
$ podman-remote run --rm \
  -v /mnt/wsl/instances/${WSL_DISTRO_NAME}`pwd`/hello.txt:/hello.txt \
  docker.io/library/busybox \
  cat /hello.txt
Hello from WSL2
$

終於成功啦!🎉

systemd.mount

目前主流版本的 WSL2 引擎都已經支援 systemd[14][15] 了,於是我們可以再多做一點事情 —— 自動掛載。

由於前面我們是手動使用 mount 指令來進行掛載,在每次 WSL2 重新啓動的時候都會復原,因此我們希望在 WSL2 啓動的時候就幫我們把這個 mountpoint 掛載上去,我們可以透過 systemd.mount 來幫我們完成這個任務。

/etc/systemd/system/ 下建立一個新的檔案 mnt-wsl-instances-${WSL_DISTRO_NAME}.mount,內容如下(此例中以 Ubuntu 取代 ${WSL_DISTRO_NAME},請讀者自行依照自己的環境作更換):

[Unit]
Description=WSL Instances

[Mount]
What=/
Where=/mnt/wsl/instances/${WSL_DISTRO_NAME}
Type=none
Options=defaults,bind,X-mount.mkdir

[Install]
WantedBy=multi-user.target

接下來執行下列指令來啓用這個 mount:

# umount /mnt/wsl/instances/${WSL_DISTRO_NAME}
$ ls /mnt/wsl/instances/${WSL_DISTRO_NAME}

# systemctl daemon-reload
# systemctl enable --now mnt-wsl-instances-${WSL_DISTRO_NAME}.mount
Created symlink /etc/systemd/system/multi-user.target.wants/mnt-wsl-instances-Ubuntu.mount → /etc/systemd/system/mnt-wsl-instances-Ubuntu.mount.
$ ls /mnt/wsl/instances/Ubuntu/home/
davy
$

這次終於是大功告成了!🎊

結語

其實原本不是要寫 Podman 相關的內容的…… 但剛好遇到要使用 Podman 的情景就想說順便把這件事情寫下來好了,原本是打算之後再跟大家分享的,現在提前先分享給大家 :)

Podman 在環境整合這方面真的還沒有做的 Docker 好,但還算在能搞懂原理的情況下自行 Hack,使用上或許沒有這麼方便,不過我個人還是覺得 Podman 還算是好用且方便的。


  1. 詳見官方網站: https://podman.io/ ↩︎

  2. 亦稱紅帽,主力開發及維護開源生態的軟體公司,其發行的 Redhat Enterprise Linux 常用於各種生產環境,詳見其官方網站: https://www.redhat.com/ ↩︎

  3. 此處專指 Linux Container,可參見維基百科《Containerization》條目 ↩︎

  4. 容器管理工具,詳見其官方網站: https://www.docker.com/ ↩︎

  5. 先出現與 Kubernetes 中的概念,為一組運算資源的最小管理單位,包含網路、掛載點等,詳見 K8s 的概念說明 ↩︎

  6. Podman 的桌面管理工具,詳見官方網站: https://podman-desktop.io/ ↩︎

  7. Windows Subsystem Linux 2,一個深度整合 Linux 執行環境於 Windows 中的解決方案,設計說明請參閱: https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2 ↩︎

  8. 請見: https://podman-desktop.io/docs/podman/accessing-podman-from-another-wsl-instance ↩︎

  9. 僅有遠端操作 Podman Engine 功能的閹割版 Podman CLI,使用說明詳見: https://docs.podman.io/en/latest/markdown/podman-remote.1.html ↩︎

  10. 原文節錄 "To make bind-mounts a seamless experience, we introduced a docker api proxy similar to the one we use for enabling bind mounts of Windows files that translates paths relative to the user distro into a path to the same file accessible from the LinuxKit container.",全文請見 https://www.docker.com/blog/new-docker-desktop-wsl2-backend/ ↩︎

  11. Podman 將 socket 放在 /mnt/wsl/podm an-sockets/podman-machine-default/podman-root.sock 來與其他 Distro 共用 ↩︎

  12. Linux 中用來設定 DNS 資訊的設定檔,詳見 man 5 resolv.conf 或這裡的存檔: https://man7.org/linux/man-pages/man5/resolv.conf.5.html ↩︎

  13. $WSL_DISTRO_NAME —— 此一環境變數會記錄當下環境的 WSL2 Distro 名稱 ↩︎

  14. 在 WSL v0.67.6+ 中可以啓用 systemd,詳見官方部落格: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/ ↩︎

  15. "systemd is a suite of basic building blocks for a Linux system. It provides a system and service manager that runs as PID 1 and starts the rest of the system.",節錄自官方網站: https://systemd.io/ ↩︎

使用 systemd 管理 Docker container

因為把部落格重建[1]的關係,順便把所有的服務都 container[2] 化了,這次使用 systemd[3] 來幫忙管理 container deamonlize 的部分,下面來介紹一下我用了什麼工具跟方法。

前言

其實管理 Docker container 的工具已經有很多了,最常見的就是 Docker Compose[4]或是廣義的來說用 K8s[5] 等工具也是可以管理容器,但為什麼我還要自己搞呢?明明就有現成的工具方便我管理了。

但,我必須得說,我就是沒那麼喜歡 Docker Compose,畢竟你還要為這些 docker-compose.yml 找一個家,雖然可以跟要掛載的 volume 放一起,但還是很亂,既然我都要管理這些 Container 了,那我是不是可以直接掛到 systemd 上面幫我管理呢?

systemd service unit

好,既然已經確定要用 systemd 了,那麼就先來寫個 Unit file[6] 來描述這個 Docker container 的啟動方式吧,現在很簡單的假設一下我們現在要啓動一個很簡單的服務吧,這裡使用 busybox:musl 這個 image 並在裡面跑一個簡單的腳本令它每分鐘輸出一句 hello world

那麼 Unit file 如下: docker-hello-world.service

[Unit]
Description=Docker container - hello world
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
ExecStart=/usr/bin/docker run --rm --name hello-world busybox:musl /bin/sh -c 'while true; do echo hello world; sleep 60; done'

[Install]
WantedBy=multi-user.target

如此一來我們就可以進行初步的管理了,例如使用 systemctl enable docker-hello-world.service 讓這個 container 可以開機自動執行,並且有加上 After=docker.serviceRequires=docker.service 的緣故,確保這個 service 會接在 docker.service 執行之後才開始

systemd-docker

在成功的啓動服務之後呢,還不要太心急。雖然現在 systemd 的確可以成功的幫我們把 container 開起來,但當我們想透過 systemd 觀察 process 的時候只能看到如下的內容:

$ sudo systemctl status docker-hello-world.service
● docker-hello-world.service - Docker container - hello world
   Loaded: loaded (/etc/systemd/system/docker-hello-world.service; disabled; vendor preset: enabled)
   Active: active (running) since Mon 2019-08-19 08:49:09 UTC; 16ms ago
 Main PID: 21293 (docker)
    Tasks: 1 (limit: 1152)
   CGroup: /system.slice/docker-hello-world.service
           └─21293 /usr/bin/docker run --rm --name hello-world busybox:musl /bin/sh -c while true; do echo hello world; sleep 60; done

Aug 19 08:49:09 did systemd[1]: Started Docker container - hello world.

這是為什麼呢?
原來 Docker 的設計分為 Server 跟 Client,我們平常呼叫的 docker 指令就(Docker CLI)是所謂的 Client,而 Server 又稱 Docker Engine[7],其任務則是負責處理 container 管理、網路管理等任務。當我們操作 Docker CLI 時,其實只是藉由 Client 去向 Server 溝通,實際處理請求的還是 Docker Engine。

也是因為這樣,所以 Container 的 Process 其實是從 Docker Engine 延伸出來[8]的,且 Docker 還會將 Container 的 Process 隔離進新的 CGroup[9],因此從 systemd 上面是無法從執行 docker 這個指令的 docker-hello-world.service unit 查詢到。

那怎麼辦呢?
所幸有人在理解背後的原理之後作出了一個工具 systemd-docker[10],這個工具的運作原理十分簡單,他會將吃到的參數直接轉送給 docker,如果這個操作將會建立 container 的話,systemd-docker 就會把建立好的 container process 的 cgroup 移動回 systemd 下面讓 systemd 可以管理,並將 stdout/stderr[11] 導向到 systemd-journal[12],而且支援 systemd-notify[13]

正所謂,有了 systemd-docker 考試都考一百分呢(不要瞎掰好嗎)!
那麼我們把 Unit file 改一下: docker-hello-world.service

[Unit]
Description=Docker container - hello world
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
Type=notify
NotifyAccess=all
ExecStart=/usr/local/bin/systemd-docker --cgroups name=systemd run --rm --name hello-world busybox:musl /bin/sh -c 'while true; do echo hello world; sleep 60; done'

[Install]
WantedBy=multi-user.target

可以注意到,我們同時加上了與 systemd-notify 有關的參數,並且在 systemd-docker 中指定 cgroup 到 systemd 上,如果想要指定其他 cgroup 也可以自行修改參數。

那麼就來看看結果吧:

$ systemctl daemon-reload
$ systemctl start docker-hello-world.service
$ systemctl status docker-hello-world.service
● docker-hello-world.service - Docker container - hello world
   Loaded: loaded (/etc/systemd/system/docker-hello-world.service; disabled; vendor preset: enabled)
   Active: active (running) since Mon 2019-08-26 07:30:48 UTC; 21s ago
 Main PID: 3742 (sh)
    Tasks: 4 (limit: 1152)
   CGroup: /system.slice/docker-hello-world.service
           └─3661 /usr/local/bin/systemd-docker --cgroups name=systemd run --rm --name hello-world busybox:musl /bin/sh -c while true; do echo hello world; sleep 60;
           ‣ 3742 /bin/sh -c while true; do echo hello world; sleep 60; done

Aug 26 07:30:48 did systemd[1]: Started Docker container - hello world.
Aug 26 07:30:48 did systemd-docker[3661]: hello world
Aug 26 07:31:48 did systemd-docker[3661]: hello world
Aug 26 07:32:48 did systemd-docker[3661]: hello world

要注意的是,由於我們移動了 cgroup,所以 docker run--cpuset/-m` 參數就會不起作用了(取而代之的是,我們可以從 systemd 來為這些 cgroup 設定限制)。

systemd template unit

在經過一番忙碌之後,我們終於把 Docker container 與 systemd 結合了,但要是每次建立新 container 都要複製貼上一個 unit file,豈不太搞剛(太麻煩)?

於是我們應該要將我們的 service unit 改良成一個 template unit,如此一來我們就可以不需要重複撰寫有關於 Docker container service 的重疊部分,把焦點放置在差異的部分就好了。

首先,我們來把 docker-hello-world.service 改一下名字以及內容,取作 [email protected],注意名稱中的 @ 是必要的,systemd 會把 @ 結尾的 unit file 視為一個 template unit,內容如下:

[Unit]
Description=Docker container - %i
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
Type=notify
NotifyAccess=all
EnvironmentFile=/etc/systemd/docker/%i.conf
ExecStart=/usr/local/bin/systemd-docker --cgroups name=systemd run --rm --name systemd-%i $ARGS $IMAGE $CMD

[Install]
WantedBy=multi-user.target

我們在這裡定義了一個 template file,其執行時會讀取 /etc/systemd/docker/%i.conf 的檔案來做 unit 的環境變數設定,然後我們再將環境變數中的 $IMAGE $ARGS $CMD 丟給 systemd-docker 執行,其中 %i[14] 指的就是這個 template unit 的 instance name。

覺得太過複雜的話,可以換個角度這樣理解,當 systemd 要執行一個 template service unit 的時候,會要求以 [email protected] 來執行,systemd 會去讀取 [email protected] 這個 template unit,並且把 %i 取代為 @ 後方的內容(此例為 instance-name)。以上面的例子來說,如果我們執行 [email protected],我們可以想成是在執行如下的 unit file:

[Unit]
Description=Docker container - hello-world
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
Type=notify
NotifyAccess=all
EnvironmentFile=/etc/systemd/docker/hello-world.conf
ExecStart=/usr/local/bin/systemd-docker --cgroups name=systemd run --rm --name systemd-hello-world $ARGS $IMAGE $CMD

[Install]
WantedBy=multi-user.target

如此一來就清晰多了,關鍵的問題是 /etc/systemd/docker/hello-world.conf 的內容是什麼呢?其實也很簡單,內容如下:

IMAGE=busybox:musl
ARGS=
CMD=sh -c 'while true; do echo hello world; sleep 60; done'

而 systemd 在讀取這個檔案之後會把裡面的內容設成環境變數,並填入 ExecStart 中,整個 unit file 最後就會變成這樣:

[Unit]
Description=Docker container - hello-world
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
Restart=always
Type=notify
NotifyAccess=all
EnvironmentFile=/etc/systemd/docker/hello-world.conf
ExecStart=/usr/local/bin/systemd-docker --cgroups name=systemd run --rm --name systemd-hello-world busybox:musl sh -c 'while true; do echo hello world; sleep 60; done'

[Install]
WantedBy=multi-user.target

這樣子就跟原本的一樣了,那麼我們如果要建立新的 container service 的時候,我們只需要進行下列的步驟:

  1. 決定一個 instance name,例如 nginx
  2. /etc/systemd/docker/ 下面建立 nginx.conf,並填入 IMAGE, ARGS, CMD
  3. 執行 systemctl start [email protected]
  4. (可選) systemctl enable [email protected] 設為開機自動執行

後記

經過了上面的研究之後,也去看了 systemd-docker 的原始碼瞭解這個工具實際上做的事情以及 Docker 與 systemd 背後做的事情有哪些,雖然還只是冰山一角,但至少又更理解自己平時使用的工具到底在幹嘛,而不是單純一味的使用,知其然而不知所以然,希望大家也有學到一些技巧。


  1. 有關重建部落格的資訊,請見《部落格復活》一文 ↩︎

  2. 這邊專指 Linux container,是一種作業系統層級的虛擬化技術,參見 Docker 的《What is a Container?》一文或維基百科上的《OS-level virtualisation》條目 ↩︎

  3. systemd 是一套用於 Linux 的管理框架,其實作包含 Daemon、Library 及眾多 Application,並通常用來取代 System V 作為 init,並使用 cgroup 來追從 process,請見維基百科《systemd》條目及 ArchWiki 《systemd》條目 ↩︎

  4. Docker Compose 是 Docker 所推薦的一種同時管理多個容器的方式,相關用法可以參見官網說明 ↩︎

  5. Kubernetes,是一個用於自動部署、擴展、容器化管理的工具,並不是專為 Docker 而設計,但支援使用 Docker 作為容器化的引擎,相關資訊可見其網站介紹 ↩︎

  6. systemd 裡面所有與 service、socket、device、mountpoint、等有關的描述都被稱為 Unit,有關 Unit 的說明可以參考 man systemd.unit (link)或 ArchWiki 〈[systemd - 編寫單元檔案(https://wiki.archlinux.org/index.php/Systemd_(正體中文)#編寫單元檔案)]〉一節 ↩︎

  7. Docker Engine 的介紹可見《About Docker Engine》一文 ↩︎

  8. 在預設的環境中,其實 Docker Engine 只是 containerd 的前端界面,實際建立 Process 的其實是 containerd,有關 containerd 的介紹請見網站說明 ↩︎

  9. Control Groups,是 Linux 核心的一個功能,用來限制、控制與分離一個行程群組的資源,常被用來做 Container 的基礎,systemd 也以此為基礎來管理 service。 ↩︎

  10. 提供 Systemd 與 Docker 互動的界面,其原始版本只支援舊 Docker API,筆者這裡使用的是由 @DonTseTse fork 出來的版本: github.com/DonTseTse/systemd-docker ↩︎

  11. stdout 表示為標準輸出、stderr 表示為標準錯誤輸出,兩者預設皆會直接輸出至終端 ↩︎

  12. Systemd 中提供的日誌系統,用以管理系統 log 等記錄,更多資訊可見ArchWiki《Systemd/Journal》條目 ↩︎

  13. Systemd-notify 可向 systemd 發送一個提示以表示 service 的狀態(例如:執行中),以便 systemd 處理 unit 之間的相依性 ↩︎

  14. %i 等變數我們稱為 specifier,其他可用的 specifier 可見 man systemd.unit 中的 〈specifiers〉一節 ↩︎