要同時跑多個容器服務,我們通常會寫一個docker-compose.yml
,將每個服務的屬性都定義好,然後透過docker compose up
指令啟動。
在Podman,要達成這個作法,有兩種方式:
- podman-compose:相容docker-compose的指令,用法類似,讀取YAML檔啟動容器服務,幾乎是一條
podman compose up
就能轉換過來。注意不要跟podman play kube
搞混,後者需要使用Kubernetes的YAML格式。 - Podman Quadlets:在Podman 4.4加入的功能,將docker-compose的內容修改為符合Systemd服務檔,將Podman容器服務變成Systemd服務的概念。這已經跟原本的docke-compose的yaml是不同語言了,需要自行改寫成Systemd語法。
本文Ivon討論第二種,以Podman Quadlets來跑多個服務的作法,將docker-compose.yml轉換到Podman Quadlets。
據說Podman Quadlets的名字來源是將Kubernetes切成兩半,由8變成4,也就是扁平化後的Kubelet。意思是試圖用Systemd簡化管理容器的程序。
RedHat公司在官網部落格介紹了許多Podman Quadlets的好處,不過沒有很詳細的文件說明,因此Ivon決定研究一番。
Docker compose的另一種形式概念就是Podman Quadlets,好處為何?跳脫了從單一yaml啟動全部服務的作法,而非全部交由一個Docker的daemon去管,透過daemonless的特性迴避單點故障。Quadlets讓Podman容器與Linux系統融合,方便以systemctl指令管理,這樣能夠更靈活的使用Systemd的target,搭配PartOf=
、BindsTo=
處理一系列服務之間的依賴關係。還能設定開機自動在特定的target之後跟著啟動Podman容器服務。還可以自動讓Podman更新容器的映像檔。
依照Quadlets的概念,如果一個docker-compose.yml
檔案有多個容器服務,則他們會被拆分成多個Systemd的服務檔(service unit)。一個服務檔裡面定義該容器的環境變數、卷宗、網路設定、隸屬於哪一個Pod、自動更新策略、依賴哪個容器才能啟動的關係。
但是,YAML跟Systemd的格式根本不同,要將規則手動轉換感覺好麻煩喔?所幸我們有一個叫做「Podlet」的工具能夠自動轉換。它會讀取docker-compose.yml
,並生成Systemd服務檔。這對於遷移現有的docker-compose十分有幫助。
Podlet用於取代已經棄用的podman generate systemd
。
1. 安裝Podlet#
Fedora直接從套件庫安裝:sudo dnf install podlet
Ubuntu 24.04尚未收錄Podlet安裝套件,所以就直接從Github下載二進位檔,解壓縮放到/usr/bin/local/
wget https://github.com/containers/podlet/releases/download/v0.3.0/podlet-x86_64-unknown-linux-gnu.tar.xz
tar -xvf podlet-x86_64-unknown-linux-gnu.tar.xz
chmod +x podlet-x86_64-unknown-linux-gnu/podlet
sudo mv podlet-x86_64-unknown-linux-gnu/podlet /usr/bin/local/podlet
2. 將docker-compose.yml轉成Quadlets#
- 指令用法:
podlet [選項] [指令]
podlet允許根據映像檔、容器、容器網路,還有
docker-compose.yml
產生對應的Systemd service unit。這裡我們只使用轉換
docker-compose.yml
的指令。將一份docker-compose.yml
放置到目前的工作目錄:
mkdir -p ~/apache/html
vim docker-compose.yml
- 填入一個簡單的Apache容器服務範本
services:
web: # 這個名稱將會成為.container服務檔名稱
image: docker.io/library/httpd:latest
ports:
- "80:80"
volumes:
- /home/user/apache/html:/usr/local/apache2/htdocs/
- 開始轉換,Podlet會檢查不相容的部份並給出警告。如果只有一個容器,就只會建立一個
.container
檔案。多個服務就會自動拆成多個。
# 自動建立.container的服務檔,並允許開機自動啟動
podlet -u -i compose
# 只輸出服務檔寫法,不建立檔案
podlet compose
- Podman預設應該會將
.container
檔案放到~/.config/containers/systemd/
,所以要重新載入Systemd
systemctl --user daemon-reload
- 我們可以用quadlet指令檢查各個Systemd服務檔是否合法
/usr/libexec/podman/quadlet -dryrun -user
- 然後,以systemctl啟動Podman容器。Podman Quadlets沒辦法用
docker compose pull
拉取容器,所以得用podman pull
,或者讓它在啟動的時候自動拉取
systemctl --user start web
- 要設定開機自動啟動不是使用
systemctl --user enable
,而是看.container服務檔裡面有沒有以下內容:
[Install]
WantedBy=default.target
- 如果要讓Systemd自動更新Podman容器映像檔,在.container服務檔加入以下內容:
[Container]
AutoUpdate=registry
再使用以下指令啟用自動更新Podman映像檔的服務:
systemctl --user enable podman-auto-update.service
- 我們可以同時用podman以及systemctl的相關指令來操控這個容器
systemctl --user status web
podman ps
3. 一些Quadlets的注意事項#
在轉換
docker-comose.yml
前需要注意:使用的映像檔鍵值image:
要寫出完整網址。例如image: docker.io/library/httpd:latest
。不然Podman會不知道從哪裡拉取映像檔。關於Quadlets的服務檔,應該要放在系統目錄
/etc/containers/systemd/
還是使用者目錄~/.config/containers/systemd/
?Podlet預設是選擇後者,加強安全性,不過rootless容器可能會有一些權限問題,要修改一些SELinux設定。特權容器的話要在.container檔加入PodmanArgs=--privileged
。
如果還是遇到權限問題,就用rootful容器,將其放到/etc/containers/systemd/
。
- 使用者登出的時候,Podman容器可能會跟著被停止。要登出後仍繼續執行容器服務,使用這個指令:
loginctl enable-linger <使用者名稱>
- 對於rootless的Podman容器來說,需要使用
net.ipv4.ip_unprivileged_port_start
才可以存取1024以下的通訊埠:
echo "net.ipv4.ip_unprivileged_port_start=80" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Podman每個容器之間的網路是彼此隔離的,如果兩個容器之間依賴Docker內部的DNS通訊(例如需要存取資料庫),你可能要寫一個Pod,或者建立一個Podman Network,將兩個容器使用同一個Network Namespace,容器之間才能連線。使用Pod的好處是可以將多個容器組合在一起,停止一個Pod之後,相關的容器都會跟著停止,而不用手動寫Systemd的依賴關係。
有時候你會覺得與其依賴Podlet轉換,還不如自己根據docker-compose.yml的邏輯,自行重寫Systemd服務檔。
4. 多容器實際操作案例:將Immich以Quadlets部署#
目前自架相簿服務Immich官方尚未有關於Podman的安裝指示,只有docker-compose.yml。它定義了4個容器服務,這正好適合測試一下Quadlets。
以下內容參考自jbtrystram/immich-podman-systemd,測試當下版本是v137.3。注意我是手動轉的,不想知道原理的可以直接用這個儲存庫的版本。
- 放置.container的目錄可以針對每個Quadlets新增一個目錄,這樣就能放環境變數檔案。
mkdir -p ~/.config/containers/systemd/immich/
cd ~/.config/containers/systemd/immich/
- 取得Immich官方提供的docker-compose.yml,轉換為Quadlets,存在目前目錄,應該會得到四個.container檔案。因為Immich的docker-compose.yml使用了環境變數,所以會轉換失敗,要手動刪掉
$
開頭的環境變數的部份。
wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
podlet -f -i compose
- 新增環境變數檔案,在
immich.env
裡面填入偏好的設定
wget -O immich.env https://github.com/immich-app/immich/releases/latest/download/example.env
- 因為容器之間需要通訊,得共享網路,新增一個Network:
podman network create immich-net
環境變數是含有Immich主服務的容器要讀取的,故編輯
immich-server.container
,填入[Containers] EnvironmentFile=immich.env
。在所有的
.container
檔案裡面填入[Containers] Network=immich-net
代表它們使用同一個網路。確認各個Systemd服務的啟動順序,依照Immich的設計邏輯,應該是資料庫容器先啟動完成之後才啟動網頁伺服器容器,所以我只要啟動
immich-server
,它所依賴的服務就會跟著先啟動。且我希望停止immich-server
這個服務後,其他服務也要跟著自動停止。所以四個檔案的啟動邏輯關係如下:
# immich-server.container
[Unit]
Wants=immich-redis.service immich-database.service immich-machine-learning.service
After=immich-redis.service immich-database.service immich-machine-learning.service
# immich-redis.container
[Unit]
PartOf=immich-server.service
# immich-database.container
[Unit]
PartOf=immich-server.service
# immich-machine-learning.container
[Unit]
PartOf=immich-server.service
修正剩下.container檔案裡面的volume路徑。
用quadlet指令檢查各個Systemd服務檔是否合法
/usr/libexec/podman/quadlet -dryrun -user
- 透過Pod啟動全部的Immich服務,它應該會將依賴的服務一併啟動。
systemctl --user start immich-server
5. 用圖形界面管理Quadlets服務#
這樣講有點怪,應該沒有人想用圖形介面管理Linux的Systemd服務的吧?大部分都是用指令編輯,但就是有這種需求。桌面方案有Systemd Pilot,網頁有Cockpit的方案,OpenSUSE用戶可以嘗試YaST。
專門為Podman設計的Podman Desktop更是有專門的編輯選項。安裝完Quadlets的擴充套件後,就能列出Linux系統上的各個Pod狀況。
這樣一來,Podman Desktop圖形介面就能協助管理系統上的容器服務。只不過Quadlets的作法就變成更Linux-orientedㄌ,畢竟需要Systemd,對在其他作業系統跑容器服務的用戶來說可能會多一些學習成本。