返回文章列表

無根容器網路組態與遷移技巧

本文探討無根容器的網路組態,著重於 `slirp4netns` 與自定義網路的應用,並比較 Podman 和 Docker 在網路管理上的差異,以及 Docker 映像遷移至 Podman 的技巧與注意事項,包含命令別名、網路命令比較和容器間通訊設定。

容器技術 網路管理

無根容器利用 slirp4netns 行程建立獨立網路名稱空間和 tap0 虛擬網路裝置,預設組態 10.0.2.0/24 子網路,但容器間網路隔離,彼此無法直接通訊。為解決此問題,可將容器置於同一 Pod 或自定義網路,例如使用 Podman 建立 rootless-net 網路,使容器透過 veth pair 連線至橋接器,實作容器間通訊,避免 IP 衝突。此外,也可使用連線埠對映發布必要連線埠,供容器間通訊。相較於 Docker,Podman 的無根容器網路管理更為彈性,但遷移 Docker 容器至 Podman 時,需重新建立容器並掛載磁碟區,或使用 docker exportpodman import 匯入映像。Podman 提供與 Docker 相容的 CLI,可使用別名簡化遷移,但部分命令列為存在差異,例如 podman volume createpodman run -v 的處理方式。

實作容器網路概念

在每個新的無根容器(rootless container)中,系統會在主機上執行一個新的 slirp4netns 行程。此行程為容器建立網路名稱空間,並建立一個 tap0 網路裝置,且組態 10.0.2.100/24 位址(來自預設的 slirp4netns 子網路 10.0.2.0/24)。這使得兩個容器無法直接在相同網路上相互通訊,因為會發生 IP 位址衝突。

以下範例展示了一個無根 busybox 容器的網路行為:

$ podman run -i busybox sh -c 'ip addr show tap0'
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 2a:c7:86:66:e9:20 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fd00::28c7:86ff:fe66:e920/64 scope global dynamic mngtmpaddr
       valid_lft 86117sec preferred_lft 14117sec
    inet6 fe80::28c7:86ff:fe66:e920/64 scope link
       valid_lft forever preferred_lft forever

可以檢查無根網路名稱空間並找到對應的 tap0 網路裝置:

$ podman unshare --rootless-netns ip addr show tap0
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 1a:eb:82:6a:82:8d brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever
    inet6 fd00::18eb:82ff:fe6a:828d/64 scope global dynamic mngtmpaddr
       valid_lft 86311sec preferred_lft 14311sec
    inet6 fe80::18eb:82ff:fe6a:828d/64 scope link
       valid_lft forever preferred_lft forever

內容解密:

此範例展示了無根容器的網路組態,包含 tap0 網路裝置的 IP 位址和相關設定。

無根容器網路行為

由於無根容器不具備獨立的 IP 位址,我們有兩種方法可以讓多個容器相互通訊:

  • 將所有容器放在同一個 Pod 中,使它們可以使用 localhost 介面進行通訊,而無需開啟任何連線埠。
  • 將容器連線到自定義網路,並在無根網路名稱空間中管理其介面。
  • 如果要保持所有容器的獨立性,可以使用連線埠對映技術來發布所有必要的連線埠,然後使用這些連線埠讓容器相互通訊。

使用 Podman 4 網路後端,讓我們快速關注第二種場景,即兩個 Pod 連線到無根網路。首先,需要建立網路並連線幾個測試容器:

$ podman network create rootless-net
$ podman run -d --net rootless-net --name endpoint1 --cap-add=net_admin,net_raw busybox /bin/sleep 10000
$ podman run -d --net rootless-net --name endpoint2 --cap-add=net_admin,net_raw busybox /bin/sleep 10000

內容解密:

此步驟建立了一個名為 rootless-net 的自定義網路,並啟動了兩個容器 endpoint1endpoint2,將它們連線到該網路。

驗證容器間通訊

讓我們嘗試從 endpoint1 容器 ping endpoint2 容器:

$ podman exec -it endpoint1 ping -c1 endpoint2
PING endpoint2 (10.89.1.3): 56 data bytes
64 bytes from 10.89.1.3: seq=0 ttl=64 time=0.145 ms

---
 endpoint2 ping statistics 
---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.145/0.145/0.145 ms

這兩個容器可以在共同的網路上相互通訊,並且具有不同的 IPv4 位址。為了證明這一點,可以檢查 aardvark-dns 組態的內容:

$ cat /run/user/1000/containers/networks/aardvark-dns/rootless-net
10.89.1.1
fe27f8d653384fc191d5c580d18d874d480a7e8ef74c2626ae21b118eedbf1e6 10.89.1.2 endpoint1,fe27f8d65338
19a4307516ce1ece32ce58753e70da5e5abf9cf70feea7b981917ae399ef934d 10.89.1.3 endpoint2,19a4307516ce

內容解密:

此組態顯示了容器的 IP 位址和名稱,證明它們可以在自定義網路上相互通訊。

自定義網路與 tap0 介面的區別

最後,讓我們演示自定義網路如何繞過 tap0 介面,並允許在無根網路名稱空間中建立專用的 veth 對和橋接器。以下命令將顯示一個 Linux 橋接器,用於 rootless-net 網路和兩個連線的 veth 對:

$ podman unshare --rootless-netns ip link | grep 'podman'
3: podman2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
4: vethdca7cdc6@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master podman2 state UP mode DEFAULT group default qlen 1000
5: veth912bd229@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master podman2 state UP mode DEFAULT group default qlen 1000

內容解密:

此輸出顯示了自定義網路中的橋接器和 veth 對,證明瞭自定義網路的獨立性。

Docker 遷移技巧與竅門

每項技術都有其開創性的公司、專案和產品,一旦被創造和宣佈,就會成為真正的遊戲規則改變者,讓其基礎概念得以普及。對於容器技術來說,這個角色由 Docker 擔當。

正如我們在第一章《容器技術介紹》中所學到的,Docker 提供了一種新的方法和偉大的理念,利用現有的技術並創造全新的技術。幾年後,它成為了容器技術中最被廣泛使用的技術。

但正如通常發生在開源專案上的情況,社群和企業開始尋求改進、新的架構和不同的實作。這就是 Podman 找到成長和利用 Open Container Initiative(OCI)所提供的標準化的地方。

Docker 是(仍然是)最被廣泛使用的容器技術。因此,在本章中,我們將提供一些關於處理遷移過程的技巧和竅門。我們將涵蓋以下主題:

  • 遷移現有的映像並使用命令別名
  • Podman 命令與 Docker 命令的比較
  • 使用 Docker Compose 與 Podman

技術需求

在繼續本章的講解和範例之前,您需要一台具有正常運作的 Podman 安裝的機器。正如我們在第三章《執行第一個容器》中提到的,本文中的所有範例都是在 Fedora 34 系統或更高版本上執行的,但可以在您選擇的作業系統(OS)上重現。

對第四章《管理執行中的容器》、第五章《為容器的資料實作儲存》以及第九章《將映像推播到容器登入檔》中所涵蓋的主題有很好的理解,將有助於您掌握本章中關於容器的概念。

遷移現有的映像並使用命令別名

Podman 有一個很棒的功能,讓任何之前的 Docker 使用者能夠輕鬆地適應和切換到它——與 Docker 完全相容的命令列介面(CLI)。

讓我們透過為 docker 命令建立一個 shell 命令別名來展示這種 CLI 相容性:

# alias docker=podman
# docker
Error: missing command 'podman COMMAND'
Try 'podman --help' for more information.

如您所見,我們建立了一個命令別名,將 podman 命令繫結到 docker 命令。如果我們在設定別名後嘗試執行 docker 命令,則輸出將由 podman 命令傳回。

讓我們在新建立的別名上執行一個容器來測試一下:

# docker run --rm -it docker.io/wernight/funbox nyancat

我們應該會看到一些非常有趣的東西——一個正在執行的貓,類別似於下圖所示:

此圖示

讓我們測試一些更有趣的事情。Docker,例如,提供了一個根據容器映像的教學,暴露了一個網頁伺服器:

# docker run -dp 80:80 docker.io/docker/getting-started
Trying to pull docker.io/docker/getting-started:latest...
Getting image source signatures
Copying blob 97518928ae5f done
Copying blob e0bae2ade5ec done
Copying blob a2402c2da473 done
Copying blob e362c27513c3 done
Copying blob a4e156412037 done
Copying blob 3f3577460f48 done
Copying blob 69465e074227 done
Copying blob eb65930377cd done
Copying config 26d80cd96d done
Writing manifest to image destination
Storing signatures
d44a2df41d76b3322e56971d45e92e75f4679e8b620198228fbd9cc00fe9578f

在這裡,我們繼續使用 docker 別名命令,並使用 -d 選項以守護程式模式執行它,並使用 -p 選項繫結 HTTP 連線埠。

程式碼解析:

docker run -dp 80:80 docker.io/docker/getting-started

此命令的作用是:

  1. 使用 docker run 命令執行一個容器。
  2. -d 選項使容器在背景執行。
  3. -p 80:80 選項將主機的 80 連線埠對映到容器的 80 連線埠。
  4. docker.io/docker/getting-started 是要執行的容器映像名稱。

如果一切正常,那麼我們可以將瀏覽器指向 http://localhost

此圖示

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 無根容器網路組態與遷移技巧

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

Dockerlabs 的第一頁《Getting Started》指定了剛才執行的命令。從頁面的左欄,我們可以繼續進行教學。

讓我們繼續進行教學,並再次檢查別名是否能在每個階段正常運作。

教學步驟非常簡單,它們可以幫助您總結前面章節中分享的知識,從建置容器到使用多個容器應用程式建立專用網路。請在《使用 Docker Compose》一節之前停止,因為我們將很快更詳細地討論這一點。

不要忘記,我們正在使用別名,在底層,Podman 正在積極地讓我們的容器按預期工作,確保 Docker CLI 相容性。

但是,對於用 Podman 取代 Docker 的情況,容器遷移又該如何處理呢?

嗯,沒有直接的方法可以將現有的容器從 Docker 遷移到 Podman。建議您使用各自的容器映像重新建立容器,並使用 Podman 重新掛載任何磁碟區。

容器映像可以使用 docker export 命令匯出,這將建立一個 TAR 封存檔案,可以透過 podman import 命令匯入到 Podman 中。如果您正在使用容器映像登入檔,則可以跳過此步驟。

Podman 命令與 Docker 命令的比較

正如我們在前一節中看到的,以及在第二章《比較 Podman 和 Docker》中所提到的,Podman CLI 是根據 Docker CLI 的。然而,由於 Podman 不需要執行時守護程式才能工作,因此某些 Docker 命令可能無法直接使用,或者需要一些變通方法。

命令列表非常長,因此下表僅指定了幾個:

命令描述
podman volume create建立一個新的磁碟區
podman run -v /tmp/noexist:/tmp將主機上的 /tmp/noexist 目錄掛載到容器的 /tmp 目錄

Podman 與 Docker 的行為差異

以下命令被 Podman 開發團隊有意地以不同的方式實作:

  • podman volume create:如果磁碟區已經存在,此命令將失敗。在 Docker 中,此命令是冪等的,這意味著如果具有相同名稱的磁碟區已經存在,則 Docker 將跳過此指令。Docker 的實際行為與其他命令的實作不符。
  • podman run -v /tmp/noexist:/tmp:如果來源磁碟區路徑不存在,此命令將失敗。相反,Docker 將建立該資料夾(如果它不存在)。同樣,Podman 開發團隊認為這是一個錯誤並對其進行了更改。