前言
隨著容器技術在現代軟體開發中的普及,Docker Compose 已成為管理多容器應用程式不可或缺的工具。透過簡潔的 YAML 配置檔,開發者能夠定義複雜的應用程式架構,包含多個服務之間的依賴關係、網路拓撲與資料持久化策略。然而,當我們決定從 Docker 遷移到 Podman 時,一個關鍵的問題隨之而來:既有的 Docker Compose 配置是否能夠在 Podman 環境中繼續使用?如何確保遷移過程的平滑與穩定?
Podman 在設計之初就考慮了與 Docker 生態系統的相容性,這不僅體現在命令列介面的高度相似性上,也延伸到對 Docker Compose 的支援。早期的 Podman 版本雖然在容器管理功能上已經相當完善,但對於 Docker Compose 的支援並不完整,這促使社群開發了 podman-compose 這個獨立專案來填補這個空缺。隨著 Podman 3.0 的發布,原生的 Docker Compose 支援終於被引入,讓使用者能夠直接使用標準的 docker-compose 命令來管理 Podman 容器,大幅簡化了遷移過程。
然而,僅僅能夠執行 Docker Compose 配置檔並不足以確保完整的功能對等。Podman 與 Docker 在架構設計上的差異,特別是 Podman 的無 Daemon 設計與對 Rootless 容器的支援,為容器的生命週期管理帶來了新的考量。例如,Docker Compose 中的 restart 策略在 Podman 環境中需要透過 Systemd 整合才能在系統重啟後持續生效。這種架構差異不是缺陷,而是 Podman 刻意選擇與系統原生服務管理工具深度整合的結果,帶來了更好的可靠性與可維護性。
除了技術層面的整合,理解 Docker Compose 配置檔的結構與運作原理同樣重要。YAML 配置檔透過宣告式的方式定義服務、網路與資料卷,讓複雜的多容器應用能夠以簡潔的形式描述。服務之間的依賴關係透過 depends_on 指令建立,環境變數透過 environment 區段注入,資料持久化透過具名資料卷實現。這些概念在遷移到 Podman 時依然適用,但需要理解 Podman 如何解釋與執行這些配置。
本文將深入探討 Podman 與 Docker Compose 的整合策略,從 Systemd 服務管理的基礎開始,逐步深入到 YAML 配置檔的結構解析、多容器應用的編排技巧,以及如何透過 podman-compose 實現 Rootless 容器編排。我們會透過實際的範例,包含 WordPress 與 MySQL 的經典組合,以及 Go REST API 與 Redis 的現代架構,展示如何在 Podman 環境中建構與管理複雜的多容器應用。透過這些知識,您將能夠充分利用 Podman 的優勢,同時保持與 Docker Compose 生態系統的相容性。
Podman 與 Systemd 的深度整合
在探討 Docker Compose 的整合之前,我們需要先理解 Podman 如何透過 Systemd 來實現容器的生命週期管理。這是 Podman 相較於 Docker 的一個重要差異,也是確保容器在系統重啟後能夠自動恢復的關鍵機制。
Docker 透過常駐的 Daemon 程序來管理容器的重啟策略。當我們在 docker run 命令中指定 –restart 參數時,Docker Daemon 會記錄這個策略,並在容器異常終止時自動重啟容器。這種機制在 Docker Daemon 持續運行的前提下運作良好,但 Podman 採用無 Daemon 架構,沒有常駐的背景程序來監控容器狀態,因此需要不同的解決方案。
Podman 選擇與 Linux 系統原生的服務管理工具 Systemd 深度整合。Systemd 是現代 Linux 發行版的標準初始化系統,負責管理系統服務的啟動、停止與監控。透過將容器轉換為 Systemd 服務單元,Podman 能夠利用 Systemd 的所有功能,包含自動重啟、日誌管理、依賴關係處理等,實現比 Docker 更強大且更可靠的容器生命週期管理。
Podman 提供了 generate systemd 命令,能夠自動產生對應容器的 Systemd 服務單元檔案。這個命令會分析容器的配置,包含映像檔、環境變數、埠號對應、資料卷掛載等所有參數,然後產生一個完整的 Systemd 單元檔案。讓我們透過一個實際的範例來展示這個過程。
首先,我們啟動一個簡單的 Nginx 容器:
# 啟動 Nginx 容器
# --name 參數指定容器名稱
# -d 參數讓容器在背景執行
# -p 參數將主機的 8080 埠對應到容器的 80 埠
podman run -d \
--name web-server \
-p 8080:80 \
docker.io/library/nginx:latest
容器啟動後,我們可以使用 generate systemd 命令產生對應的服務單元檔案:
# 產生 Systemd 服務單元檔案
# --new 參數表示在啟動服務時建立新容器,而非重用既有容器
# --files 參數將輸出寫入檔案而非標準輸出
# --name 參數指定容器名稱
podman generate systemd --new --files --name web-server
執行這個命令後,Podman 會在當前目錄產生一個名為 container-web-server.service 的檔案。讓我們檢視這個檔案的內容:
# container-web-server.service
# Podman 自動產生的 Systemd 服務單元檔案
[Unit]
Description=Podman container-web-server.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStartPre=/bin/rm -f %t/%n.ctr-id
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--sdnotify=conmon \
--replace \
-d \
--name web-server \
-p 8080:80 \
docker.io/library/nginx:latest
ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
這個服務單元檔案包含了完整的容器啟動與停止邏輯。Unit 區段定義了服務的基本資訊與依賴關係,確保網路服務已經就緒才啟動容器。Service 區段定義了實際的執行命令,包含容器的建立與刪除邏輯。特別注意 Restart=on-failure 這個設定,這正是我們需要的自動重啟策略,當容器異常終止時,Systemd 會自動重新啟動它。
要讓這個服務在系統啟動時自動執行,我們需要將服務單元檔案複製到 Systemd 的配置目錄,並啟用服務:
# 建立使用者層級的 Systemd 配置目錄
mkdir -p ~/.config/systemd/user/
# 複製服務單元檔案到配置目錄
cp container-web-server.service ~/.config/systemd/user/
# 重新載入 Systemd 配置
systemctl --user daemon-reload
# 啟用服務,讓它在系統啟動時自動執行
systemctl --user enable container-web-server.service
# 立即啟動服務
systemctl --user start container-web-server.service
完成這些步驟後,容器就成為了一個真正的系統服務。我們可以使用標準的 Systemd 命令來管理它:
# 檢視服務狀態
systemctl --user status container-web-server.service
# 停止服務
systemctl --user stop container-web-server.service
# 重啟服務
systemctl --user restart container-web-server.service
# 檢視服務日誌
journalctl --user -u container-web-server.service
這種整合方式帶來了幾個顯著的優勢。首先是可靠性,Systemd 是經過多年發展且高度穩定的服務管理工具,將容器託管給 Systemd 管理能夠確保其穩定運行。其次是一致性,所有系統服務都透過相同的介面管理,簡化了維運工作。最後是功能豐富,Systemd 提供了許多進階功能,如服務依賴管理、資源限制、日誌輪替等,這些都能直接應用到容器管理上。
以下的架構圖展示了 Podman 與 Systemd 整合的運作流程:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
actor "管理者" as ADMIN
package "Systemd 服務層" {
[Systemd Manager] as SYSTEMD
[服務單元檔案] as UNIT
}
package "Podman 容器層" {
[Podman CLI] as CLI
[容器程序] as CONTAINER
}
ADMIN --> CLI : 執行 podman run
CLI --> CONTAINER : 啟動容器
ADMIN --> CLI : 執行 generate systemd
CLI --> UNIT : 產生服務單元檔案
ADMIN --> SYSTEMD : 啟用服務
SYSTEMD --> UNIT : 讀取配置
note right of SYSTEMD
系統啟動時自動執行
容器異常時自動重啟
整合日誌與資源管理
end note
SYSTEMD --> CLI : 呼叫 podman run
CLI --> CONTAINER : 建立容器程序
@enduml這個架構圖清楚呈現了 Podman 如何透過 Systemd 來實現容器的持久化管理。管理者首先手動啟動容器,然後使用 generate systemd 產生服務單元檔案,最後將服務註冊到 Systemd。之後,容器的生命週期就完全由 Systemd 管理,在系統啟動時自動執行,在異常終止時自動重啟。
Docker Compose 基礎概念與 YAML 配置結構
在深入 Podman 與 Docker Compose 的整合之前,我們需要先理解 Docker Compose 的核心概念與配置檔案的結構。Docker Compose 是一個用於定義與執行多容器應用程式的工具,透過單一的 YAML 配置檔,我們能夠描述應用程式的完整架構,包含所有服務、網路與資料卷的配置。
Docker Compose 的設計理念是宣告式配置。相較於命令式的容器管理方式,宣告式配置讓我們專注於描述應用程式的目標狀態,而非執行的具體步驟。我們在 YAML 檔案中定義應用程式應該包含哪些服務、這些服務如何互動、需要哪些資源,然後 Docker Compose 會自動處理容器的建立、網路的配置與資料卷的掛載,確保應用程式達到我們描述的狀態。
最基本的 Docker Compose 配置檔包含三個主要區段:version、services 與 volumes。version 區段指定配置檔的格式版本,不同版本支援不同的功能特性。services 區段定義應用程式包含的所有服務,每個服務對應一個容器。volumes 區段定義具名資料卷,用於資料的持久化儲存。
讓我們從一個最簡單的範例開始,這個範例部署一個 Docker Registry 服務:
# Docker Registry 的基本配置
# 提供私有的容器映像檔倉庫服務
version: '3.9'
services:
# 定義 registry 服務
registry:
# 使用官方的 Docker Registry 映像檔
image: docker.io/library/registry:2
# 埠號對應配置
# 將主機的 5000 埠對應到容器的 5000 埠
# 格式為 "主機埠:容器埠"
ports:
- "5000:5000"
# 資料卷掛載配置
# 將具名資料卷 registry_volume 掛載到容器的 /var/lib/registry 目錄
# 這個目錄是 Registry 儲存映像檔的位置
volumes:
- registry_volume:/var/lib/registry
# 定義具名資料卷
volumes:
# 建立名為 registry_volume 的資料卷
# 空的大括號表示使用預設配置
registry_volume: {}
這個配置檔定義了一個非常簡單的單服務應用。registry 服務使用官方的 Docker Registry 映像檔,透過 ports 指令將服務暴露在主機的 5000 埠,並透過 volumes 指令掛載資料卷來持久化儲存映像檔資料。即使容器被刪除,儲存在 registry_volume 中的映像檔資料仍然保留,下次啟動容器時會自動載入。
Docker Compose 的真正價值在於管理多容器應用。讓我們看一個更實際的範例,部署一個包含 WordPress 與 MySQL 的完整網站應用:
# WordPress 與 MySQL 的完整應用配置
# 展示服務間依賴、環境變數注入與資料持久化
version: '3.9'
services:
# 資料庫服務定義
db:
# 使用官方的 MySQL 8.0 映像檔
image: docker.io/library/mysql:8.0
# 容器重啟策略
# always 表示容器無論何種原因終止都會自動重啟
restart: always
# 環境變數配置
# 這些環境變數會被注入到容器中,用於初始化 MySQL
environment:
# MySQL root 使用者的密碼
MYSQL_ROOT_PASSWORD: secure_root_password
# 要建立的資料庫名稱
MYSQL_DATABASE: wordpress
# 要建立的一般使用者名稱
MYSQL_USER: wordpress
# 一般使用者的密碼
MYSQL_PASSWORD: wordpress_password
# 資料卷掛載配置
# 將 MySQL 的資料目錄掛載到具名資料卷
# 確保資料庫內容在容器重啟後保留
volumes:
- db_data:/var/lib/mysql
# 暴露埠號配置
# expose 只在容器間網路中暴露埠號,不對外發布
# 這讓 WordPress 容器能夠連線到 MySQL,但外部無法直接存取
expose:
- 3306
- 33060
# WordPress 服務定義
wordpress:
# 使用官方的 WordPress 映像檔
image: docker.io/library/wordpress:latest
# 埠號對應配置
# 將主機的 8080 埠對應到容器的 80 埠
# 這讓我們能夠透過 http://localhost:8080 存取 WordPress
ports:
- "8080:80"
# 容器重啟策略
restart: always
# 環境變數配置
# 提供 WordPress 連線到 MySQL 資料庫所需的資訊
environment:
# 資料庫主機位址
# 使用服務名稱 db,Docker Compose 會自動解析為對應容器的 IP
WORDPRESS_DB_HOST: db
# 資料庫使用者名稱
WORDPRESS_DB_USER: wordpress
# 資料庫密碼
WORDPRESS_DB_PASSWORD: wordpress_password
# 資料庫名稱
WORDPRESS_DB_NAME: wordpress
# 服務依賴關係配置
# 確保 db 服務先於 wordpress 服務啟動
depends_on:
- db
# 定義具名資料卷
volumes:
# MySQL 資料卷
# 儲存資料庫的所有資料
db_data: {}
這個配置檔展示了 Docker Compose 的幾個核心功能。首先是服務間依賴,透過 depends_on 指令,我們確保 MySQL 資料庫會在 WordPress 之前啟動,避免 WordPress 啟動時找不到資料庫服務。其次是環境變數注入,WordPress 需要知道如何連線到 MySQL,我們透過環境變數提供這些資訊。特別注意 WORDPRESS_DB_HOST 的值是 db,這是 MySQL 服務的名稱,Docker Compose 會自動將服務名稱解析為對應容器的 IP 位址。
資料持久化是另一個重要概念。MySQL 的資料儲存在 /var/lib/mysql 目錄中,如果我們直接使用容器的檔案系統,當容器被刪除時,所有資料也會一併消失。透過將這個目錄掛載到具名資料卷 db_data,資料會被儲存在 Docker 管理的持久化儲存空間中,即使容器被刪除,資料仍然保留。
以下的架構圖展示了這個 WordPress 應用的完整結構:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
actor "使用者" as USER
package "Docker Compose 應用" {
package "WordPress 容器" {
[WordPress 程序] as WP
[環境變數\nDB_HOST=db] as WP_ENV
}
package "MySQL 容器" {
[MySQL 程序] as DB
[環境變數\nROOT_PASSWORD\nDATABASE] as DB_ENV
}
database "db_data\n資料卷" as VOLUME
}
USER --> WP : HTTP 請求\n(Port 8080)
WP --> WP_ENV : 讀取連線資訊
WP --> DB : MySQL 連線\n(Port 3306)
DB --> DB_ENV : 讀取配置
DB --> VOLUME : 讀寫資料
note right of WP
depends_on: db
確保資料庫先啟動
end note
note right of VOLUME
持久化儲存
容器刪除後資料保留
end note
@enduml這個架構圖清楚呈現了 WordPress 與 MySQL 之間的互動關係。使用者透過瀏覽器存取 WordPress,WordPress 根據環境變數中的配置連線到 MySQL 資料庫,MySQL 將資料儲存在持久化的資料卷中。整個應用的結構透過單一的 YAML 配置檔定義,Docker Compose 負責處理所有的容器建立、網路配置與資料卷掛載細節。
進階 Docker Compose 配置:建置映像檔與服務編排
除了使用既有的容器映像檔,Docker Compose 也支援在部署時建置自訂映像檔。這個功能在開發環境中特別有用,讓我們能夠在修改程式碼後立即重新建置映像檔並部署,大幅加速開發迭代的速度。
讓我們透過一個實際的範例來展示這個功能。這個範例包含一個使用 Go 語言開發的 REST API 服務,搭配 Redis 作為資料儲存後端:
# Go REST API 與 Redis 的應用配置
# 展示自訂映像檔建置與服務副本配置
version: '3.9'
services:
# Web API 服務定義
web:
# 建置配置
# 指定建置映像檔的相關參數
build:
# 建置上下文目錄
# 包含 Dockerfile 與所有建置所需的檔案
context: ./app
# Dockerfile 的路徑
# 如果不指定,預設使用 context 目錄下的 Dockerfile
dockerfile: Dockerfile
# 建置參數
# 這些參數會傳遞給 Dockerfile 中的 ARG 指令
args:
- GO_VERSION=1.21
# 容器標籤
# 用於標記容器的元資料,方便管理與查詢
labels:
- "com.example.description=Golang Redis REST API"
- "com.example.department=backend"
- "com.example.version=1.0.0"
# 埠號對應配置
# 將主機的 8080 埠對應到容器的 8080 埠
ports:
- "8080:8080"
# 環境變數配置
# 提供 API 服務連線到 Redis 所需的資訊
environment:
# Redis 服務的主機名稱
# 使用服務名稱,Docker Compose 會自動解析
- REDIS_HOST=redis
# Redis 連線埠號
- REDIS_PORT=6379
# API 服務的日誌等級
- LOG_LEVEL=info
# 服務依賴關係配置
# 確保 Redis 服務先於 API 服務啟動
depends_on:
- redis
# 容器重啟策略
restart: unless-stopped
# Redis 服務定義
redis:
# 使用官方的 Redis 映像檔
image: docker.io/library/redis:7-alpine
# 部署配置
# 定義服務的副本數量與資源限制
deploy:
# 服務副本數量
# 在 Docker Compose 中,這個設定主要用於 Swarm 模式
# 在單機模式下,通常設定為 1
replicas: 1
# 資源限制
resources:
limits:
# CPU 使用限制,0.5 表示最多使用半個 CPU 核心
cpus: '0.5'
# 記憶體使用限制
memory: 256M
reservations:
# 保留的 CPU 資源
cpus: '0.25'
# 保留的記憶體資源
memory: 128M
# 資料卷掛載配置
# 將 Redis 的資料目錄掛載到具名資料卷
volumes:
- redis_data:/data
# 容器重啟策略
restart: unless-stopped
# 定義具名資料卷
volumes:
# Redis 資料卷
# 儲存 Redis 的持久化資料
redis_data: {}
這個配置檔展示了幾個進階功能。首先是 build 區段,它定義了如何建置自訂映像檔。context 參數指定建置上下文目錄,這個目錄包含 Dockerfile 與所有建置所需的原始碼檔案。Docker Compose 會在這個目錄中尋找 Dockerfile,執行建置流程,然後使用產生的映像檔來啟動容器。
labels 區段提供了容器的元資料標記。這些標籤不會影響容器的運作,但對於管理與查詢容器非常有用。我們可以透過標籤來篩選容器、組織容器群組,或是記錄容器的版本與負責部門等資訊。
deploy 區段定義了服務的部署配置,包含副本數量與資源限制。在單機的 Docker Compose 環境中,副本數量通常設定為 1,因為 Docker Compose 主要用於開發與測試,不是生產環境的叢集編排工具。資源限制則確保單一容器不會耗盡整個系統的資源,在多個容器共存的環境中特別重要。
要使用這個配置檔部署應用,我們需要執行以下命令:
# 建置映像檔並啟動所有服務
# --build 參數強制重新建置映像檔
# -d 參數讓容器在背景執行
docker-compose up --build -d
執行這個命令後,Docker Compose 會依序執行以下步驟:首先讀取配置檔,解析所有服務的定義。接著檢查是否需要建置映像檔,對於 web 服務,會執行 Dockerfile 中定義的建置流程。建置完成後,依照服務的依賴關係順序啟動容器,先啟動 redis 服務,然後啟動 web 服務。最後建立所需的網路與資料卷,確保所有資源都正確配置。
我們可以使用多個 Docker Compose 命令來管理這個應用:
# 檢視所有服務的狀態
docker-compose ps
# 檢視服務的日誌輸出
# -f 參數持續追蹤新的日誌訊息
docker-compose logs -f web
# 只檢視特定服務的日誌
docker-compose logs redis
# 執行容器內的命令
# 例如進入 web 容器的 shell
docker-compose exec web /bin/sh
# 停止所有服務
docker-compose stop
# 停止並移除所有容器、網路與預設資料卷
docker-compose down
# 停止並移除所有資源,包含具名資料卷
docker-compose down -v
Docker Compose 的另一個實用功能是環境變數替換。我們可以在 YAML 配置檔中使用 ${VARIABLE} 語法來參照環境變數,讓配置更加靈活:
services:
web:
image: myapp:${APP_VERSION}
environment:
- DATABASE_URL=${DATABASE_URL}
- API_KEY=${API_KEY}
這些環境變數可以從多個來源讀取,包含作業系統的環境變數、.env 檔案,或是在執行 docker-compose 命令時透過 -e 參數指定。這種機制讓我們能夠在不同環境中使用相同的配置檔,只需要調整環境變數的值即可。
Podman 與 Docker Compose 的整合配置
現在我們理解了 Docker Compose 的基礎概念後,讓我們探討如何在 Podman 環境中使用 Docker Compose。Podman 對 Docker Compose 的支援需要一些額外的配置,主要是因為 Docker Compose 預期與 Docker Daemon 透過 Unix Socket 通訊,而 Podman 需要模擬這個介面。
Podman 提供了一個 REST API 服務,能夠模擬 Docker 的 API 介面。這個服務同時支援 Docker 相容的 API 與 Podman 原生的 Libpod API,讓既有的 Docker 工具能夠透過這個介面與 Podman 互動。要啟用這個服務,我們需要透過 Systemd 來管理。
在 Fedora 或 Red Hat 系列的作業系統中,首先需要安裝必要的套件:
# 安裝 Docker Compose 套件
# 這會安裝 docker-compose 命令列工具
sudo dnf install -y docker-compose
# 安裝 podman-docker 套件
# 這個套件提供 docker 命令的別名,指向 podman
# 同時也建立 Docker Socket 的符號連結
sudo dnf install -y podman-docker
安裝完成後,我們需要啟用 Podman 的 Socket 服務。這個服務會建立一個 Unix Socket,監聽來自 Docker Compose 的 API 請求:
# 啟用並立即啟動 Podman Socket 服務
# --now 參數表示立即啟動,而不只是設定開機啟動
sudo systemctl enable --now podman.socket
# 檢視 Socket 服務的狀態
sudo systemctl status podman.socket
# 檢視 Socket 檔案的位置與權限
ls -l /run/podman/podman.sock
執行這些命令後,Podman 會建立一個 Socket 檔案在 /run/podman/podman.sock,這個 Socket 會監聽來自 Docker Compose 的連線請求。然而,Docker Compose 預設會尋找 /var/run/docker.sock 這個路徑,因此 podman-docker 套件會建立一個符號連結,將 Docker 的預設 Socket 路徑指向 Podman 的 Socket:
# 檢視符號連結
ls -l /var/run/docker.sock
# 輸出範例:
# lrwxrwxrwx. 1 root root 23 Nov 24 10:30 /var/run/docker.sock -> /run/podman/podman.sock
這個符號連結確保 Docker Compose 能夠找到 Podman 的 Socket,實現無縫整合。需要注意的是,預設情況下,Podman Socket 只能由 root 使用者存取。這意味著使用 docker-compose 命令時需要 root 權限,或是需要調整 Socket 的權限設定。
現在我們可以使用 Docker Compose 來管理 Podman 容器。讓我們使用之前的 WordPress 範例:
# 切換到包含 docker-compose.yaml 的目錄
cd wordpress-app
# 使用 Docker Compose 啟動應用
# Podman 會透過 Socket 處理所有容器操作
sudo docker-compose up -d
# 檢視執行中的容器
sudo docker-compose ps
# 檢視容器日誌
sudo docker-compose logs -f wordpress
執行 docker-compose up 命令後,Docker Compose 會透過 Socket 向 Podman 發送 API 請求,Podman 接收這些請求並建立對應的容器、網路與資料卷。從使用者的角度來看,整個過程與使用 Docker 完全相同,但底層實際上是由 Podman 來管理容器。
以下的架構圖展示了 Docker Compose 與 Podman 的整合方式:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
actor "開發者" as DEV
package "Docker Compose 層" {
[docker-compose\n命令列工具] as COMPOSE
}
package "Podman 服務層" {
[Podman REST API\nSocket 服務] as SOCKET
[Podman CLI] as CLI
}
package "容器執行層" {
[WordPress\n容器] as WP
[MySQL\n容器] as DB
}
DEV --> COMPOSE : 執行 docker-compose up
COMPOSE --> SOCKET : API 請求\n(透過 Unix Socket)
SOCKET --> CLI : 轉換為 Podman 指令
CLI --> WP : 建立容器
CLI --> DB : 建立容器
note right of SOCKET
監聽 /run/podman/podman.sock
提供 Docker 相容 API
符號連結到 /var/run/docker.sock
end note
@enduml這個架構圖清楚呈現了整合的運作方式。開發者執行 docker-compose 命令,Docker Compose 透過 Unix Socket 向 Podman 發送 API 請求,Podman 的 Socket 服務接收請求並轉換為對應的 Podman 操作,最終建立與管理容器。整個過程對於 Docker Compose 來說是透明的,它認為自己正在與 Docker Daemon 通訊,實際上卻是 Podman 在處理所有操作。
Rootless 容器編排:podman-compose 的應用
雖然透過 Docker Compose 與 Podman Socket 的整合能夠運作,但這種方式需要 root 權限,無法充分發揮 Podman 的 Rootless 容器優勢。為了在 Rootless 模式下使用 Compose 功能,社群開發了 podman-compose 這個工具,它是一個完全使用 Python 編寫的 Docker Compose 替代實作,專為 Podman 設計。
podman-compose 的核心優勢在於完整支援 Rootless 容器。與需要 root 權限的 Docker Socket 整合方式不同,podman-compose 直接調用 Podman CLI,而 Podman CLI 本身就支援以一般使用者身份執行。這意味著我們可以在完全不需要 root 權限的情況下,使用 Compose 配置檔來編排多容器應用。
安裝 podman-compose 有兩種方式。第一種是透過系統套件管理工具:
# 在 Fedora 或 RHEL 系統上安裝
sudo dnf install -y podman-compose
# 驗證安裝
podman-compose --version
第二種是透過 Python 的 pip 工具安裝:
# 使用 pip3 安裝到使用者目錄
# --user 參數確保安裝在使用者的家目錄,不需要 root 權限
pip3 install --user podman-compose
# 確保 Python 使用者腳本目錄在 PATH 中
export PATH=$PATH:~/.local/bin
# 將這行加入到 shell 配置檔中,讓設定永久生效
echo 'export PATH=$PATH:~/.local/bin' >> ~/.bashrc
# 驗證安裝
podman-compose --version
安裝完成後,podman-compose 的使用方式與 docker-compose 高度相似。讓我們使用之前的 WordPress 範例,但這次以 Rootless 方式執行:
# 切換到包含 docker-compose.yaml 的目錄
cd wordpress-app
# 使用 podman-compose 啟動應用
# 不需要 sudo,以一般使用者身份執行
podman-compose up -d
# 檢視執行中的容器
podman-compose ps
# 檢視特定服務的日誌
podman-compose logs wordpress
# 進入容器執行命令
podman-compose exec wordpress /bin/bash
# 停止並移除所有容器
podman-compose down
podman-compose 支援大多數 docker-compose 的常用命令,包含:
# 建置映像檔(如果配置檔中定義了 build 區段)
podman-compose build
# 拉取所有服務使用的映像檔
podman-compose pull
# 啟動服務
podman-compose up
# 在背景啟動服務
podman-compose up -d
# 停止服務
podman-compose stop
# 啟動已停止的服務
podman-compose start
# 重啟服務
podman-compose restart
# 停止並移除容器
podman-compose down
# 停止並移除容器與資料卷
podman-compose down -v
# 檢視服務狀態
podman-compose ps
# 檢視服務日誌
podman-compose logs
# 執行容器內的命令
podman-compose exec <service> <command>
# 在服務中執行一次性命令
podman-compose run <service> <command>
需要注意的是,podman-compose 目前仍在積極開發中,某些進階功能可能尚未完全實作。例如,某些特定的網路模式或資料卷驅動可能不受支援。在生產環境使用前,建議詳細測試所有需要的功能,確保符合專案需求。
然而,對於大多數常見的使用場景,podman-compose 已經提供了足夠的功能。特別是在開發環境或需要 Rootless 容器的場景中,podman-compose 是一個優秀的選擇。它讓我們能夠享受 Docker Compose 帶來的便利性,同時充分利用 Podman 的安全優勢。
以下的對比圖展示了三種 Compose 整合方式的差異:
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 120
package "Docker + docker-compose" as DOCKER {
[docker-compose] as DC1
[Docker Daemon\n(需要 root)] as DAEMON
[容器] as C1
DC1 --> DAEMON
DAEMON --> C1
}
package "Podman + docker-compose" as PODMAN_DC {
[docker-compose] as DC2
[Podman Socket\n(需要 root)] as SOCKET
[Podman CLI] as CLI1
[容器] as C2
DC2 --> SOCKET
SOCKET --> CLI1
CLI1 --> C2
}
package "Podman + podman-compose" as PODMAN_PC {
[podman-compose\n(Rootless)] as PC
[Podman CLI] as CLI2
[容器] as C3
PC --> CLI2
CLI2 --> C3
}
note right of DAEMON
集中式管理
需要 root 權限
完整功能支援
end note
note right of SOCKET
API 相容層
需要 root 權限
相容 docker-compose
end note
note right of PC
原生 Podman 整合
支援 Rootless
持續開發中
end note
@enduml這個對比圖清楚呈現了三種方案的特點。傳統的 Docker 與 docker-compose 組合提供最完整的功能支援,但需要 root 權限。Podman 與 docker-compose 的整合透過 Socket 提供相容性,同樣需要 root 權限。而 podman-compose 則是唯一支援 Rootless 容器的方案,讓我們能夠在不需要 root 權限的情況下編排多容器應用。
實務建議與最佳實踐
在使用 Podman 與 Docker Compose 整合時,有幾個實務建議能夠幫助我們更順利地部署與管理應用。
首先是選擇合適的整合方式。如果應用需要完整的 Docker Compose 功能支援,且能夠接受使用 root 權限,那麼透過 Podman Socket 與 docker-compose 的整合是最穩定的選擇。如果追求 Rootless 容器的安全優勢,且應用的 Compose 配置相對簡單,那麼 podman-compose 是更好的選擇。在做出決定前,建議先在測試環境中驗證所有需要的功能。
其次是善用 Systemd 整合。對於需要長期運行的服務,建議將容器轉換為 Systemd 服務單元,而不僅僅依賴 Compose 的 restart 策略。這能夠確保容器在系統重啟後自動恢復,並享有 Systemd 提供的日誌管理、資源限制等進階功能。
第三是注意資料持久化。在配置 Compose 檔案時,確保所有需要持久化的資料都掛載到具名資料卷,而非使用容器的暫時性檔案系統。定期備份這些資料卷的內容,避免因為意外操作導致資料遺失。
第四是善用環境變數。將敏感資訊如密碼、API 金鑰等透過環境變數注入,而不是硬編碼在 Compose 配置檔中。使用 .env 檔案來管理這些環境變數,並確保 .env 檔案不會被提交到版本控制系統中。
最後是定期更新與清理。保持 Podman、Docker Compose 與 podman-compose 的版本更新,享受新功能與安全性修復。定期清理未使用的容器、映像檔與資料卷,避免佔用過多磁碟空間:
# 清理停止的容器
podman container prune
# 清理未使用的映像檔
podman image prune -a
# 清理未使用的資料卷
podman volume prune
# 一次清理所有未使用的資源
podman system prune -a --volumes
結論
Podman 與 Docker Compose 的整合為容器化應用的部署提供了強大且靈活的解決方案。透過 Systemd 的深度整合,Podman 能夠提供比 Docker 更可靠的容器生命週期管理。透過 Socket 服務與 docker-compose 的相容,我們能夠直接使用既有的 Compose 配置檔。透過 podman-compose 的支援,我們能夠在 Rootless 模式下享受 Compose 編排的便利性。
Docker Compose 的宣告式配置讓多容器應用的管理變得簡單直觀。透過 YAML 配置檔,我們能夠清楚描述應用程式的架構,包含服務、網路與資料卷的完整配置。服務間的依賴關係、環境變數的注入、資料的持久化,這些在傳統容器管理中需要大量手動操作的任務,都能透過 Compose 配置自動處理。
Podman 的優勢在於其無 Daemon 架構與 Rootless 容器支援,這些特性在安全性與可靠性方面提供了顯著的價值。結合 Docker Compose 的編排能力,我們能夠建構既安全又易於管理的容器化環境。無論是選擇 docker-compose 還是 podman-compose,理解其運作原理與限制都能幫助我們做出最適合的技術決策。
希望本文提供的知識與實務經驗,能夠協助您在 Podman 與 Docker Compose 的整合應用上,建構出符合需求且穩定可靠的容器化解決方案。