在容器化應用日益普及的時代,容器間的網路互連成為架構設計的關鍵要素。Podman 作為新一代容器管理工具,提供 CNI 與 Netavark 雙網路後端架構,賦予容器網路互連高度彈性與管理便利性。相較於 Docker 依賴複雜的 iptables 轉發規則,Podman 透過標準化介面與原生實作提供更簡潔高效的網路管理方案。本文將深入解析 Podman 容器網路互連的核心技術,透過實戰範例展現容器間的通訊機制。
CNI 標準介面的容器網路基礎架構
在 Podman 3 時代,CNI 作為容器網路功能的核心基礎,提供了一套標準化的介面與函式庫,讓容器管理工具能夠透過外掛程式架構管理容器網路。這種設計的優勢在於允許第三方廠商與社群開發自訂網路外掛程式,從而滿足各種不同的網路需求場景。
CNI 組態架構與外掛程式系統
CNI 的核心概念建立在使用 JSON 格式組態檔案來定義網路外掛程式及其參數設定。一個典型的 Podman CNI 組態檔案包含網路名稱、外掛程式鏈結清單、IP 位址管理方式等關鍵資訊。這個組態檔案定義了名為 podman 的預設網路,使用 bridge 外掛程式建立 Linux 橋接網路,並透過 host-local 外掛程式管理 IP 位址分配。
CNI 提供多種類型的外掛程式來實現不同的網路功能,介面建立外掛程式負責建立與管理容器的網路介面,包含 bridge、ipvlan 與 macvlan 等實作方式。IPAM 外掛程式專門管理容器的 IP 位址分配,支援 dhcp、host-local 與 static 等多種分配策略。Meta 外掛程式則提供額外的網路功能,例如連接埠對映、防火牆規則與網路效能調整等進階特性。
@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
package "CNI 外掛程式架構" {
rectangle "介面建立外掛程式" as Interface {
component "Bridge" as Bridge
component "IPVLAN" as IPVLAN
component "MACVLAN" as MACVLAN
}
rectangle "IPAM 外掛程式" as IPAM {
component "DHCP" as DHCP
component "Host-Local" as HostLocal
component "Static" as Static
}
rectangle "Meta 外掛程式" as Meta {
component "Portmap" as Portmap
component "Firewall" as Firewall
component "Tuning" as Tuning
}
}
rectangle "Podman 容器執行環境" as Container
Interface --> Container : 建立網路介面
IPAM --> Container : 分配 IP 位址
Meta --> Container : 套用進階功能
note right of Interface
介面建立職責:
建立 veth pair
設定橋接網路
連結容器命名空間
end note
note right of IPAM
位址管理職責:
分配子網路位址
管理位址租期
維護位址對映
end note
@enduml在預設的 Podman 組態中,容器網路分配的子網路為 10.88.0.0/16,橋接介面名稱為 cni-podman0,其 IP 位址 10.88.0.1 作為容器的預設閘道。所有來自容器的出站流量都會先導向橋接介面,再透過主機的網路堆疊轉發至外部網路。這個設定僅適用於以 root 權限執行的容器,對於 rootless 容器,Podman 使用不同的網路方法來克服使用者權限限制。
CNI 網路建立的完整流程分析
當建立新的容器時,主機上會發生一系列的網路組態事件。首先系統會建立一對 veth 虛擬乙太網路裝置,這是一對充當本地通道的虛擬介面,一端連接至容器的網路命名空間,另一端連接至主機的橋接介面。veth pair 的特性在於可以跨多個網路命名空間運作,傳送到該對一側的封包會立即在另一側接收。
# 執行 Nginx 容器並對映連接埠
# -d: 以背景模式執行容器,不會佔用終端機視窗
# -p 8080:80: 將主機的 8080 連接埠對映至容器內部的 80 連接埠
# --name: 指定容器的識別名稱,便於後續管理操作
podman run -d -p 8080:80 \
--name net_example \
docker.io/library/nginx
# 檢視主機網路介面組態,確認新建立的 veth 介面
# veth 介面以成對方式出現,一端在主機一端在容器內
ip addr show
# 列出所有網路命名空間,每個容器都擁有獨立的命名空間
# 網路命名空間提供網路資源的隔離機制
ip netns
# 在指定的網路命名空間中執行命令,檢視容器內的網路組態
# 這個命令展示容器內部看到的網路介面與 IP 位址
ip netns exec cni-df380fb0-b8a6-4f39-0d19-99a0535c2f2d ip addr show
# 檢視容器內的路由表設定
# 確認預設閘道指向橋接介面的 IP 位址
ip netns exec cni-df380fb0-b8a6-4f39-0d19-99a0535c2f2d ip route
# 檢視 NAT 表規則,觀察連接埠對映的實作機制
# DNAT 規則負責將主機連接埠的流量導向容器內部
iptables -L -t nat -n | grep -B4 10.88.0.3
# 檢視連接至橋接介面的所有 veth 裝置
# 確認容器的 veth pair 已正確連接至橋接網路
bridge link show cni-podman0
當容器啟動時,firewall 與 portmapper CNI 外掛程式會在主機的 filter 與 NAT 表中建立必要的防火牆規則。DNAT 規則位於自訂鏈中,負責將導向主機特定連接埠的 TCP 封包重新導向至容器內部的服務連接埠。這個機制對應到建立容器時指定的連接埠對映參數,確保外部流量能夠正確到達容器內的服務。
@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
participant "外部用戶端" as Client
participant "主機防火牆\nNAT 規則" as NAT
participant "Linux Bridge\ncni-podman0" as Bridge
participant "veth pair\n主機端" as VethHost
participant "veth pair\n容器端" as VethContainer
participant "容器服務\nNginx" as Container
Client -> NAT: 請求主機 8080 連接埠
activate NAT
NAT -> NAT: 執行 DNAT 轉換\n8080 → 10.88.0.3:80
NAT -> Bridge: 轉發至容器 IP
deactivate NAT
activate Bridge
Bridge -> VethHost: 透過橋接介面轉發
deactivate Bridge
activate VethHost
VethHost -> VethContainer: veth pair 傳輸封包
deactivate VethHost
activate VethContainer
VethContainer -> Container: 封包到達容器內部
deactivate VethContainer
activate Container
Container --> VethContainer: 回應封包
deactivate Container
VethContainer --> VethHost: 回傳路徑
VethHost --> Bridge: 封包返回橋接介面
Bridge --> NAT: 執行 SNAT 轉換
NAT --> Client: 回應到達用戶端
note right of NAT
防火牆規則功能:
DNAT 處理入站流量
SNAT 處理出站流量
維護連線追蹤狀態
end note
@endumlNetavark 原生網路實作的技術演進
隨著 Podman 專案的持續發展,社群意識到 CNI 在某些方面存在限制,包含對雙堆疊網路的支援不足,以及與 Podman 開發路線的整合程度有待加強。為了解決這些問題,Podman 4 引入了全新的網路堆疊 Netavark,這是一個完全使用 Rust 語言編寫的容器原生網路實作。
Netavark 的核心優勢與架構設計
Netavark 的主要優勢展現在多個層面,首先是更完善的雙堆疊網路支援,提供對 IPv4 與 IPv6 的原生支援能力,能夠更好地滿足現代網路環境的需求。其次是與 Podman 專案的緊密整合,Netavark 作為專為 Podman 設計的網路後端,能夠更靈活地適應專案的發展方向。Rust 語言的高效能特性使得 Netavark 能夠提供更快的網路速度與更低的資源消耗。
Netavark 的組態檔案與 CNI 保持相似的 JSON 格式,但結構更為簡潔明瞭。對於以 root 權限執行的容器,組態檔案儲存在 /etc/containers/networks 路徑下,而 rootless 容器的組態則存放在使用者家目錄的 .local/share/containers/storage/networks 路徑中。
{
"name": "netavark-example",
"id": "d98700453f78ea2fdfe4a1f77eae9e121f3cbf4b6160dab89edf9ce23cb924d7",
"driver": "bridge",
"network_interface": "podman1",
"created": "2022-02-17T21:37:59.873639361Z",
"subnets": [
{
"subnet": "10.89.4.0/24",
"gateway": "10.89.4.1"
}
],
"ipv6_enabled": false,
"internal": false,
"dns_enabled": true,
"ipam_options": {
"driver": "host-local"
}
}
組態檔案中的各個欄位具有明確的功能定義,name 欄位指定網路的識別名稱,id 提供唯一的網路識別碼,driver 定義網路驅動程式類型預設為 bridge 模式。network_interface 欄位指定與網路關聯的介面名稱,對於 bridge 驅動程式而言這將是 Linux 橋接介面的名稱。subnets 欄位包含子網路與閘道的設定清單,Netavark 允許在單一網路上管理多個子網路。ipv6_enabled 控制 IPv6 支援的啟用狀態,dns_enabled 則決定是否啟用由 aardvark-dns 守護程序提供的 DNS 解析服務。
Netavark 網路建立與管理機制
Netavark 管理容器網路命名空間與主機網路命名空間中的網路組態建立,包含 veth pair 的建立以及組態檔案中定義的 Linux 橋接介面。在預設 Podman 網路中建立第一個容器之前,系統不會預先建立任何橋接介面,僅有主機介面與迴路介面處於活動狀態。
# 執行 Nginx 容器並使用 Netavark 網路後端
# 容器啟動時系統會自動建立必要的網路資源
podman run -d -p 8080:80 \
--name nginx-netavark \
docker.io/library/nginx
# 檢視主機網路介面,確認 podman0 橋接介面已建立
# 橋接介面作為容器網路的中心節點
ip addr show
# 列出網路命名空間,Netavark 使用 netns- 前綴命名
# 命名空間的 UID 提供唯一識別
ip netns
# 在容器的網路命名空間中檢視網路組態
# 確認容器獲得正確的 IP 位址與路由設定
ip netns exec netns-61a5f9f9-9dff-7488-3922-165cdc6cd320 ip addr show
# 檢視橋接介面連接的 veth 裝置清單
# 每個容器都會有對應的 veth pair 連接
bridge link show podman0
Netavark 的橋接介面命名規則採用 podmanN 模式,其中 N 從 0 開始遞增。當容器啟動時,podman0 橋接介面與對應的 veth 介面會自動出現在主機的網路組態中。對於 rootless 容器,橋接與 veth pair 會在 rootless 網路命名空間中建立,可以使用 podman unshare –rootless-netns 命令進入該命名空間進行檢視。
容器網路互連的實戰應用場景
理解容器網路的基礎架構後,實際應用中需要處理各種容器間通訊的場景。Podman 提供完整的網路管理命令,讓使用者能夠靈活建立與管理容器網路,實現複雜的網路拓撲架構。
同網段容器的直接通訊機制
當多個容器連接至同一個網路時,它們處於相同的子網路範圍內,可以直接進行點對點通訊而無需額外的路由組態。這種設計類似於實體網路中處於同一區域網路的裝置,能夠透過交換器直接轉發封包。
# 建立第一個測試端點容器
# --cap-add 選項授予網路管理權限,允許執行網路診斷工具
# net_admin 權限允許修改網路組態
# net_raw 權限允許使用原始套接字進行網路診斷
podman run -d --name endpoint1 \
--cap-add=net_admin,net_raw \
busybox /bin/sleep 10000
# 建立第二個測試端點容器,使用相同的權限設定
podman run -d --name endpoint2 \
--cap-add=net_admin,net_raw \
busybox /bin/sleep 10000
# 在 endpoint1 容器中追蹤至 endpoint2 的網路路徑
# traceroute 顯示封包經過的所有躍點
# 同網段通訊應該只有一個躍點,直達目標容器
podman exec -it endpoint1 traceroute 10.88.0.14
# 使用 ping 命令測試容器間的連通性
# 驗證封包是否能成功到達並返回
podman exec -it endpoint1 ping -c 4 10.88.0.14
# 檢視容器的網路組態資訊
# 確認容器的 IP 位址與網路設定
podman inspect endpoint1 --format '{{.NetworkSettings.Networks}}'
追蹤路由的結果顯示封包直接在內部網路中傳輸,沒有經過額外的躍點。這證明了同網段容器之間的通訊完全在橋接網路內部完成,不需要經過複雜的路由轉發流程。橋接介面作為二層交換設備,能夠根據 MAC 位址表直接轉發封包至目標容器。
跨網段容器的路由轉發實作
當容器位於不同的網路時,通訊需要透過路由機制來實現。Podman 允許建立多個自訂網路,每個網路擁有獨立的子網路範圍與閘道設定。容器在傳送跨網段封包時,會先將封包傳送至本地閘道,再由主機的路由表決定轉發路徑。
# 建立自訂網路 net1,指定子網路與閘道設定
# --driver bridge: 使用橋接模式作為網路驅動
# --gateway: 指定網路閘道的 IP 位址
# --subnet: 定義網路的子網路範圍,使用 CIDR 表示法
podman network create \
--driver bridge \
--gateway "10.90.0.1" \
--subnet "10.90.0.0/16" \
net1
# 建立連接至 net1 網路的容器
# --network 參數指定容器使用的網路
podman run -d --name endpoint3 \
--network=net1 \
--cap-add=net_admin,net_raw \
busybox /bin/sleep 10000
# 從 endpoint1 追蹤至 endpoint3 的路由路徑
# 觀察封包如何經過閘道進行跨網段轉發
podman exec -it endpoint1 traceroute 10.90.0.2
# 檢視主機上的橋接介面清單
# 確認 net1 網路對應的橋接介面已建立
ip link show type bridge
# 檢視主機的路由表
# 觀察不同子網路的路由規則
ip route show
追蹤結果顯示封包首先到達 endpoint1 容器的預設閘道 10.88.0.1,這是 cni-podman0 橋接介面的 IP 位址。接著封包被路由至與 net1 網路關聯的橋接介面,最終到達 endpoint3 容器。整個過程展示了主機如何扮演路由器的角色,在不同的容器網路之間轉發流量。
DNS 解析簡化服務發現流程
在微服務架構中,服務發現是一個重要的課題。容器需要能夠動態找到彼此的網路位址才能建立通訊。Podman 的 DNS 解析功能大幅簡化了這個過程,容器可以直接使用容器名稱作為主機名稱來查詢 IP 位址。
# 建立 PostgreSQL 資料庫容器
# DNS 解析允許其他容器使用 'db' 名稱連接
# -e 參數設定環境變數,組態資料庫認證資訊
podman run -d \
--network net1 \
--name db \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=students \
postgres
# 等待資料庫完全啟動並準備接受連線
sleep 10
# 將測試資料匯入資料庫
# 使用 exec -i 參數將標準輸入導向容器內的 psql 命令
cat db.sql | podman exec -i db psql -U admin students
# 執行 Web 應用程式容器
# 應用程式使用 'db' 主機名稱連接資料庫
# DNS 解析會自動將 'db' 解析為資料庫容器的 IP
podman run -d \
--network net1 \
-p 8080:8080 \
--name webapp \
students \
students -host db -port 5432 \
-username admin -password password
# 在 Web 應用容器中測試 DNS 解析
# nslookup 命令查詢 'db' 主機名稱的 IP 位址
podman exec webapp nslookup db
# 驗證 Web 應用程式能否成功連接資料庫
# 透過 curl 命令測試應用程式的 API 端點
curl http://localhost:8080/api/health
預設情況下,Podman 的預設網路不提供 DNS 解析功能,但使用者建立的新網路則預設啟用 DNS 解析。這個設計允許容器透過名稱而非 IP 位址進行通訊,當容器重新啟動並獲得新的 IP 位址時,DNS 記錄會自動更新,其他容器無需修改組態即可繼續通訊。
CNI 與 Netavark 的 DNS 解析機制差異
雖然 CNI 與 Netavark 都提供 DNS 解析功能,但其實作機制存在顯著差異。理解這些差異有助於選擇適合的網路後端,並正確診斷網路問題。
CNI 網路後端的 dnsmasq 服務
CNI 網路後端使用 dnsname 外掛程式配合 dnsmasq 服務來提供 DNS 解析。當容器連接至啟用 DNS 的網路時,系統會啟動專屬於該網路的 dnsmasq 程序,負責將容器名稱解析為對應的 IP 位址。
# 檢視容器的 IP 位址,用於驗證 DNS 解析結果
# --format 參數使用 Go 模板語法提取特定欄位
podman inspect db --format '{{.NetworkSettings.Networks.net1.IPAddress}}'
podman inspect webapp --format '{{.NetworkSettings.Networks.net1.IPAddress}}'
# 尋找系統上執行的 dnsmasq 程序
# grep 過濾出與容器網路相關的 dnsmasq 實例
ps aux | grep dnsmasq
# 檢視 dnsmasq 的組態檔案內容
# 組態檔案定義 DNS 服務的各項參數
cat /run/containers/cni/dnsname/net1/dnsmasq.conf
# 檢視 DNS 主機名稱與 IP 的對映檔案
# addnhosts 檔案包含所有容器的 DNS 記錄
cat /run/containers/cni/dnsname/net1/addnhosts
# 測試 DNS 解析功能
# 在容器內部查詢其他容器的 IP 位址
podman exec webapp nslookup db
podman exec webapp ping -c 2 db
dnsmasq 組態檔案位於 /run/containers/cni/dnsname/ 目錄下,每個網路擁有獨立的子目錄。組態檔案指定 DNS 服務監聽的介面、使用的主機檔案以及快取設定等參數。addnhosts 檔案採用類似 /etc/hosts 的格式,記錄容器名稱與 IP 位址的對應關係。
Netavark 網路後端的 aardvark-dns 實作
Netavark 使用 aardvark-dns 來實現 DNS 解析功能,這是一個專為容器網路設計的輕量級 DNS 伺服器。aardvark-dns 以容器方式執行,為每個啟用 DNS 的網路提供解析服務。
# 檢視 aardvark-dns 容器的執行狀態
# Netavark 使用容器化的 DNS 服務
podman ps --filter name=aardvark
# 檢視 DNS 組態資訊
# Netavark 的 DNS 設定儲存在 JSON 格式檔案中
cat /run/containers/networks/aardvark-dns/net1
# 測試 DNS 解析功能
# 驗證容器名稱能正確解析為 IP 位址
podman exec webapp dig db +short
podman exec webapp host db
# 檢視容器的 /etc/resolv.conf 設定
# 確認 DNS 伺服器指向正確的位址
podman exec webapp cat /etc/resolv.conf
aardvark-dns 的優勢在於與 Netavark 的緊密整合,能夠更有效地處理容器生命週期事件。當容器啟動或停止時,DNS 記錄會自動更新,確保解析結果始終保持最新狀態。相較於 dnsmasq,aardvark-dns 針對容器環境進行了最佳化,提供更低的延遲與更高的可靠性。
進階網路管理與最佳實踐
掌握基礎網路操作後,進階的網路管理技巧能夠幫助建構更複雜且高效的容器網路架構。Podman 提供豐富的網路管理命令,支援動態調整容器的網路連接。
容器多網路連接策略
Podman 允許將執行中的容器連接至多個網路,無需停止或重新啟動容器。這個功能對於需要跨網路通訊的場景特別有用,例如反向代理伺服器需要同時連接前端網路與後端網路。
# 建立連接至預設網路的容器
podman run -d -p 8080:80 \
--name net_example \
docker.io/library/nginx
# 將容器動態連接至另一個網路
# 容器現在擁有兩個網路介面,分別連接不同網路
podman network connect example1 net_example
# 檢視容器的所有網路連接
# 確認容器已成功連接至兩個網路
podman inspect net_example --format '{{.NetworkSettings.Networks}}'
# 在容器內檢視所有網路介面
# 每個網路連接對應一個獨立的介面
podman exec net_example ip addr show
# 測試容器能否透過兩個網路介面通訊
# 驗證多網路連接的路由設定
podman exec net_example ip route show
多網路連接為容器提供了更大的網路靈活性,容器可以根據目標位址選擇合適的網路介面進行通訊。這種架構常見於多層應用程式,前端容器連接公開網路接收用戶請求,同時連接內部網路與後端服務通訊,提供網路隔離與安全防護。
網路管理命令完整指南
Podman 提供完整的網路管理命令集,涵蓋網路的建立、檢視、修改與刪除等所有操作。掌握這些命令能夠有效管理複雜的容器網路環境。
# 列出所有可用的網路
# 顯示網路名稱、ID 與驅動程式類型
podman network ls
# 檢視特定網路的詳細組態
# 輸出完整的網路設定資訊
podman network inspect net1
# 檢查網路是否存在
# 返回值指示網路的存在狀態
podman network exists net1 && echo "網路存在"
# 移除未使用的網路
# 清理沒有容器連接的網路資源
podman network prune -f
# 重新載入容器的防火牆規則
# 當防火牆規則需要更新時使用
podman network reload
# 斷開容器與網路的連接
# 容器會失去對該網路的存取能力
podman network disconnect example1 net_example
# 刪除指定的網路
# 確保沒有容器使用該網路後才能刪除
podman network rm net1
網路管理的最佳實踐包含使用描述性的網路名稱便於識別用途,為不同的應用程式環境建立獨立的網路實現隔離,定期檢視與清理未使用的網路資源,以及在生產環境中謹慎使用預設網路避免權限問題。
結論
Podman 透過 CNI 與 Netavark 雙網路後端提供了靈活且強大的容器網路管理能力。CNI 以其成熟的外掛程式生態系統與標準化介面,為容器網路提供了堅實的基礎。Netavark 作為新一代的原生實作,透過更好的雙堆疊支援與效能最佳化,代表了容器網路技術的發展方向。
DNS 解析功能大幅簡化了微服務架構中的服務發現流程,容器可以透過名稱而非 IP 位址進行通訊,提升了系統的可維護性與靈活性。透過理解容器網路的底層機制,包含 veth pair、橋接介面、網路命名空間與路由轉發等核心概念,能夠更有效地設計與管理容器化應用的網路架構。
無論選擇 CNI 或 Netavark 作為網路後端,Podman 都提供了完整的網路管理工具與靈活的組態選項。掌握這些技術與最佳實踐,能夠建構安全、高效且易於維護的容器網路環境,為現代化的雲端原生應用提供堅實的網路基礎。