[Backend]無痛轉換 Podman

Kuan Yu Chen
8 min readJan 9, 2023

--

相信會點入這篇文章的人,不外乎是要跳槽或尋找一個適當的虛擬機器,而前者為本篇重點。身為長期使用 docker 開發的人,如何無痛地跳到 podman 為本篇重點。

開始之前,不免俗地,要先了解 podman 為何物,為什麼可以取代 docker?

  1. daemonless:Docker 為人詬病的就是他的 daemon,任何機器都需要在 daemon 上執行,若 daemon 發生錯誤,就會造成所有機器停機的狀況。因此,podman 拿掉了 daemon,將每一個機器視為獨立的個體,舉例來說,db 機器壞了,並不會影響前端機器的運行。更甚,如果要將互相有關聯的機器同時呼叫並建立關係,可以利用 pod 建立叢集。
  2. rootless:與上述觀點類似,但這點更在乎權限問題。在 Docker 中,root 這個身份可以控管 deamon,也就是說,就算 deamon 沒壞,只要 root 去關掉 deamon,那麼其他機器也會瞬間停機。為了避免所有重責大任操之於一人的危險行為,podman 使用了 rootless 及 rootful 兩種模式來進行控管。(詳細內容請見此,他詳細介紹在 podman 的 rootless 及 rootful)
  3. SELinux:更適合在 SELinux 環境上運行。

Podman on Mac

由於建立容器(container),本身就是一件很 linux 的事情,所以要在mac系統執行容器,就必須在虛擬機器上。要透過 Podman 建立各樣容器,就必須先喚起虛擬機器 (virtual machine)。

podman machine init # 初始化 VM
podman machine start

以上是最基本的概念,但是你我都知道,開發環境之所以困難,就是因為有很多例外。接下來,就是這陣子我的踩坑心得。

首先,初始化一台虛擬機器需要掛載 volume,也就是可以儲存資料的倉儲。我遇到的問題是,如果要建立 Dockerfile 自行編譯的 image 會有找不到該資料夾的問題,我並不能直接用 podman machine init 指令來初始化,一定要將整個HOME都掛載進去(參考資料)。
Error: no container with name or ID "frontend-container" found: no such container
exit code: 125

podman machine init -v $HOME:$HOME

第二,若是遇到要建立數個虛擬機器的狀況,記得要定義名稱,像是”myvm”。

podman machine init -v $HOME:$HOME myvm

要在當前的虛擬機器下建立容器,就必須將該虛擬機器定為預設,否則會有連線錯誤。
error: failed to connect: dial tcp [::1]:59797: connect: connection refused

podman system connection default myvm

解決以上問題後,就可以開啟虛擬機器,利用 podman 拉取鏡像檔(image)建立容器(container)。

Podman with podman-compose

延續上段落的邏輯,一開始要初始化虛擬機器。這時候,就要先把整個專案的結構搞清楚,就是幾台容器、幾個 volume,他們各自是否有關連。不複雜化,我們先以前端容器(frontend)為例。

上圖中,左邊是我們本地端所看到的專案架構,在 vue3-test 這個資料夾中,包括了容器配置的 docker-compose.yml 以及前端內容的 frontend 資料夾。(在複雜的專案中,vue3-test 也會包含 backend 的資料夾,並另起一個 container。)透過一開始的虛擬器初始指令,可以將本地的 volume 掛載到 podman 的volume,如下。

# 初始化稱為 vue3 的虛擬機器
podman machine init -v "$(pwd):/opt" vue3
# 啟動 vue3
podman machine start vue3
# 將 vue3 當作預設虛擬機器(因為此時我的本機還有其他虛擬機器)
podman system connection default vue3
# 進入虛擬機器
podman machine ssh vue3

接下來,是回到熟悉的 docker-compose 概念。

上圖中,左邊是 podman volume,我只要將 frontend 資料夾的東西掛在到 frontend container 的 volume 中,避免將太多沒用到的資料,像是 backend掛載進去。

version: '3.7'

services:
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
container_name: frontend-container
image: localhost/frontend-image
volumes:
- /opt/frontend:/frontend-code # 這裡是重點
- /opt/frontend/node_modules:/frontend-code/node_modules # 這裡是重點
ports:
- 9000:9000

(至於 node_modules 的掛載又是另一門學問了,這個坑很深,可能要在其他章節解釋。)

回到本篇文章的主題,我們要無痛轉換到 podman,因此原本使用 docker-compose.yml 來喚起多個機器這個方法,理應保留。Podman-compose 即是為了達成此目的,他可以執行資料夾內的 docker-compose.yml,如同我們隻前執行 docker-compose 一樣。

# 安裝 podman-compose
pip3 install https://github.com/containers/podman-compose/archive/devel.tar.gz
# 建立各樣容器
podman-compose build
# 運行各樣容器
podman-compose up

有沒有發現?上面呼叫容器的指令只是把 docker 改成 podman,同理其他指令,下面列舉一些常用的指令:

# 停止所有 container
podman-compose stop
# 移除所有 container
podman-compose down
# 進入到 container 中
podman-compose exec frontend bash
# 查看各個 container 狀態
podman-compose ps
# 查看所有映像檔
podman image ls
# 移除特定映像檔
podman rmi {image_id}

惱人的權限問題

一個專案裡,db 是不可或缺的一部分,如果 db 也要在同一份 docker-compose.yml 中同時建立,那他的 volume 配置會和前述的 frontend 例子不同。以 psql 為例,儲存 psql 設定的資料,若無特別指定,會在此路徑 /var/lib/postgresql/data。因此,如果按照往常的方式掛載 volume,路徑會是這樣:

db:
container_name: db-container
image: postgres:12-alpine
volumes:
- /opt/pgdata:/var/lib/postgresql/data

然而,我遇到了權限問題:
chown: /var/lib/postgresql/data: Operation not permitted

這是因為 uid/gid 對應錯誤,詳細解釋請見此文

為了解決這個問題,我們要另外建立一個 volume 去對應 psql 的資料,除此之外,也要重新定義 psql 資料的儲存位置,這樣大家的權限才會是一樣。

services:
db:
container_name: db-container
image: postgres:12-alpine
restart: always
environment:
POSTGRES_USER: ...
POSTGRES_DB: ...
POSTGRES_HOST_AUTH_METHOD: trust
PGDATA: /podman-pgdata # 指定psql資料的儲存位置
ports:
- 5432:5432
volumes:
- db-volume:/podman-pgdata # 使用特設的 volume 來掛載psql資料
volumes:
db-volume: # 建立一個獨立的 volume

目前我也還是 podman 新手,一邊踩坑一邊紀錄,如果有任何問題或是我理解錯誤的地方,再麻煩留言指教了。

--

--

Kuan Yu Chen

Taiwanese but work in Japan. Passionate about thinking and solving problem.