hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

コンテナーのネットワークについて



1 はじめに

podmanコマンドを使ってコンテナを生成した際のネットワークについて説明します。なお、podmanコマンドの基本的な使い方は、podmanコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

1.1 コンテナ生成前

コンテナの生成前の環境は以下です。サーバとクライアントの2台構成です。サーバは受信したパケットの宛先IPアドレスをキーにルーティングテーブルを検索します。検索した結果、自身宛ならサーバのプロセスに受信したパケットを転送します。

                 +---------------------- server ------------------------------------+
                 |                                                                  |
                 | +- servre's process -+                                           |
                 | | For example:       |                                           |
                 | |   httpd, chronyd   |                                           |
                 | +--------------------+                                           |
                 |           A                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 |           |                                                      |
                 | +--------------------+                                           |
                 | |       Routing      |                                           |
                 | +--------------------+                                           |
                 |    |              |                                              |
                 |    +----+    +----+                                              |
+--- client ---+ |         |    |                                                   |
|              | |         |    V                                                   |
|  +--------+  | |       +--------+                                                 |
+--| enp1s0 |--+ +-------| enp1s0 |-------------------------------------------------+
   +--------+            +--------+
       | .177                | .68
       |                     |
-------------------------------------------------------------------------------------
            192.168.122.0/24

1.2 コンテナ生成後

コンテナ(web1,web2)を生成すると、コンテナとホストを接続するブリッジが作成されます。サーバは受信したパケットの宛先IPアドレスをキーにルーティングテーブルを検索します。検索した結果、自身宛ならサーバのプロセスに受信したパケットを転送します。コンテナ宛てなら、受信したパケットをcni-podman0より送信します。なお、図中のデバイスの意味は以下のとおりです。

バイス 概要
cni-podman0 ブリッジです。宛先MACアドレスをキーにパケットの出力NICを決定するデバイスです
vetha0a16d8a,veth8b2242ec ブリッジに作成されたNICです
enp1s0 サーバ/クライアントのNICです
eth0 コンテナのNICです
                 +---------------------- server ------------------------------------+
                 |                                                                  |
                 | +- servre's process -+      +---- web1 ----+  +---- web2 ----+   |
                 |                      |      |              |  |              |   |
                 | | For example:       |      |    nginx     |  |    httpd     |   |
                 | |   httpd, chronyd   |      |              |  |              |   |
                 | +--------------------+      |    +----+    |  |    +----+    |   |
                 |           A                 +----|eth0|----+  +----|eth0|----+   |
                 |           |                      +----+            +----+        |
                 |           |                         |                 |          |
                 |           |                 +--------------+  +--------------+   |
                 |           |               +-| vetha0a16d8a |--| veth8b2242ec |-+ |
                 |           |               | +--------------+  +--------------+ | |
                 |           |               |                                    | |
                 |           |               |            cni-podman0             | |
                 |           |               |          (Bridge device)           | |
                 | +--------------------+    |                                    | |
                 | |       Routing      | -> |                                    | |
                 | +--------------------+    +------------------------------------+ |
                 |    |              |                                              |
                 |    +----+    +----+                                              |
+--- client ---+ |         |    |                                                   |
|              | |         |    V                                                   |
|  +--------+  | |       +--------+                                                 |
+--| enp1s0 |--+ +-------| enp1s0 |-------------------------------------------------+
   +--------+            +--------+
       | .177                | .68
       |                     |
-------------------------------------------------------------------------------------
            192.168.122.0/24

2 検証環境

クライアント、サーバのAlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

3 検証結果

サーバのNICを確認します。ループバックデバイス(lo)とサーバ外部との通信用のNIC(enp1s0)が確認できます。

[root@server ~]# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:98:95:eb brd ff:ff:ff:ff:ff:ff

コンテナのイメージを確認します。nginxとhttpdの最新イメージが存在することがわかります。なお、イメージのダウンロード方法は、podmanコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# podman images
REPOSITORY               TAG         IMAGE ID      CREATED      SIZE
docker.io/library/nginx  latest      670dcc86b69d  10 days ago  146 MB
docker.io/library/httpd  latest      444f7df01ce9  2 weeks ago  149 MB

web1という名前のコンテナを起動します。

[root@server ~]# podman run -d -p 8080:80 --name web1 670dcc86b69d
3881981d35e65364dd1df5b0fd2a2850714a9214cf5a09ae51b0f5f933a7c7b3

コンテナを起動すると、ブリッジ(cni-podman0)とNIC(vetha0a16d8a)が作成されたことがわかります。

[root@server ~]# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:98:95:eb brd ff:ff:ff:ff:ff:ff
3: cni-podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 72:24:37:26:77:17 brd ff:ff:ff:ff:ff:ff
4: vetha0a16d8a@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP mode DEFAULT group default
    link/ether 6a:f0:d2:db:20:0e brd ff:ff:ff:ff:ff:ff link-netns netns-db3d0237-4de3-5827-ee92-25e760337c1d

なお、brctlコマンドを実行すると、cni-podman0はブリッジ、vetha0a16d8aはブリッジに作成されたNICであることがわかります。

[root@server ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
cni-podman0             8000.722437267717       no      vetha0a16d8a

次に、web2という名前のコンテナを起動します。

[root@server ~]# podman run -d -p 9090:80 --name web2 444f7df01ce9
311285f82dbae08a17323bab1e6e6d560b21bbfd6716551aa3d0f8911edb704f

コンテナを起動すると、NICがさらに1つ(veth8b2242ec)作成されたことがわかります。

[root@server ~]# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:98:95:eb brd ff:ff:ff:ff:ff:ff
3: cni-podman0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 72:24:37:26:77:17 brd ff:ff:ff:ff:ff:ff
4: vetha0a16d8a@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP mode DEFAULT group default
    link/ether 6a:f0:d2:db:20:0e brd ff:ff:ff:ff:ff:ff link-netns netns-db3d0237-4de3-5827-ee92-25e760337c1d
5: veth8b2242ec@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman0 state UP mode DEFAULT group default
    link/ether e6:8c:c8:4d:81:6e brd ff:ff:ff:ff:ff:ff link-netns netns-d229252d-f395-e935-1c2a-0e2cc4080cfa

なお、brctlコマンドを実行すると、ブリッジにNIC(veth8b2242ec)が追加されたことがわかります。

[root@server ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
cni-podman0             8000.722437267717       no      vetha0a16d8a
                                                        veth8b2242ec

次にpsコマンドを実行します。コンテナが2つ(web1,web2)動作していることがわかります。

[root@server ~]# podman ps
CONTAINER ID  IMAGE                           COMMAND               CREATED             STATUS                 PORTS                 NAMES
3881981d35e6  docker.io/library/nginx:latest  nginx -g daemon o...  2 minutes ago       Up 2 minutes ago       0.0.0.0:8080->80/tcp  web1
311285f82dba  docker.io/library/httpd:latest  httpd-foreground      About a minute ago  Up About a minute ago  0.0.0.0:9090->80/tcp  web2

コンテナweb1で動作しているプロセスを確認します。nginxプロセスが動作していることがわかります。

[root@server ~]# podman top web1
USER        PID         PPID        %CPU        ELAPSED          TTY         TIME        COMMAND
root        1           0           0.000       2m30.808444783s  ?           0s          nginx: master process nginx -g daemon off;
nginx       32          1           0.000       2m30.808765576s  ?           0s          nginx: worker process
nginx       33          1           0.000       2m30.808847992s  ?           0s          nginx: worker process
nginx       34          1           0.000       2m30.808919483s  ?           0s          nginx: worker process
nginx       35          1           0.000       2m30.808999017s  ?           0s          nginx: worker process

コンテナweb2で動作しているプロセスを確認します。www-data(httpd)プロセスが動作していることがわかります。

[root@server ~]# podman top web2
USER        PID         PPID        %CPU        ELAPSED          TTY         TIME        COMMAND
root        1           0           0.000       1m35.137631946s  ?           0s          httpd -DFOREGROUND
www-data    9           1           0.000       1m35.138004623s  ?           0s          httpd -DFOREGROUND
www-data    10          1           0.000       1m35.138121226s  ?           0s          httpd -DFOREGROUND
www-data    11          1           0.000       1m35.138194124s  ?           0s          httpd -DFOREGROUND

次にiptablesのDNATターゲットを確認します。コンテナを起動すると、下記DNATターゲットが作成されることがわかります。作成されるDNATターゲットのルールは以下の意味になります。受信したTCPパケットを以下のように変換します。
・宛先ポート番号が8080→宛先IPアドレスを10.88.0.2、宛先ポート番号を80に変換
・宛先ポート番号が8090→宛先IPアドレスを10.88.0.3、宛先ポート番号を80に変換

[root@server ~]# iptables -t nat -nvL |grep DNAT
 pkts bytes target     prot opt in     out     source               destination
Chain CNI-HOSTPORT-DNAT (2 references)
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:10.88.0.2:80
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9090 to:10.88.0.3:80

DNAT変換をしたあと、ルーティングテーブルの検索処理が実行されます。ルーティングテーブルを確認すると、以下のようになっています。宛先が10.88.0.0/16のパケットはcni-podman0 デバイスから、宛先が192.168.122.0/24のパケットはenp1s0から送信することがわかります。つまり、コンテナ宛てのパケットはcni-podman0デバイスから送信されることがわかります。

[root@server ~]# ip r
default via 192.168.122.1 dev enp1s0 proto dhcp metric 100
10.88.0.0/16 dev cni-podman0 proto kernel scope link src 10.88.0.1
192.168.122.0/24 dev enp1s0 proto kernel scope link src 192.168.122.68 metric 100

クライアントからweb1コンテナのnginxに対してhttpアクセスします。なお、curlコマンドの使い方は、curlコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@client ~]# curl -I http://192.168.122.68:8080
HTTP/1.1 200 OK
Server: nginx/1.23.1
Date: Mon, 01 Aug 2022 10:25:52 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 19 Jul 2022 14:05:27 GMT
Connection: keep-alive
ETag: "62d6ba27-267"
Accept-Ranges: bytes

DNATターゲットを確認します。DNATで処理したパケット数が1増加したことがわかります。

[root@server ~]#  iptables -t nat -nvL |grep DNAT
 pkts bytes target     prot opt in     out     source               destination
Chain CNI-HOSTPORT-DNAT (2 references)
    1    60 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:10.88.0.2:80
    0     0 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9090 to:10.88.0.3:80

次に、クライアントからweb2コンテナのhttpdに対してhttpアクセスします。

[root@client ~]# curl -I http://192.168.122.68:9090
HTTP/1.1 200 OK
Date: Mon, 01 Aug 2022 10:35:51 GMT
Server: Apache/2.4.54 (Unix)
Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT
ETag: "2d-432a5e4a73a80"
Accept-Ranges: bytes
Content-Length: 45
Content-Type: text/html

DNATターゲットを確認します。DNATで処理したweb2宛てのhttpパケット数が1増加したことがわかります。

[root@server ~]# iptables -t nat -nvL |grep DNAT
 pkts bytes target     prot opt in     out     source               destination
Chain CNI-HOSTPORT-DNAT (2 references)
    1    60 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:10.88.0.2:80
    1    60 DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:9090 to:10.88.0.3:80

次に、tsharkを使って、NICを通過するhttpパケットを確認してみます。なお、tsharkコマンドのインストール方法、使い方は、tsharkコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。まず、tsharkでパケットキャプチャが可能なインタフェース一覧を表示してみます。ブリッジデバイス、コンテナと接続するNICが、キャプチャ可能なインタフェースとして表示されています。

[root@server ~]# tshark -D
Running as user "root" and group "root". This could be dangerous.
1. veth8b2242ec
2. enp1s0
3. cni-podman0
4. vetha0a16d8a
-snip-

ブリッジとweb1コンテナを接続するNICでパケットをキャプチャしてみます。

[root@server ~]# tshark -i vetha0a16d8a -nn
Running as user "root" and group "root". This could be dangerous.
Capturing on 'vetha0a16d8a'

クライアントからweb1コンテナのnginxに対してhttpアクセスします。

[root@client ~]# curl -I http://192.168.122.68:8080

web1コンテナのnginxに対してhttpアクセスすると、下記HTTPパケットのやり取りが確認できます。1,2,3番のパケットを確認すると、クライアントからサーバのweb1コンテナのnginxに対してSYNパケットを送信することで、3 way Handshakeを実行して、TCPコネクションを確立していることがわかります。そして、8番のパケットで、クライアントからサーバのweb1コンテナのnginxに対してFINパケットを送信して、TCPコネクションの開放を開始しています。なお、8,9,10のパケットをやり取りすることでTCPコネクションを開放します。なお、TCPコネクションの確立、解放の様子は、TCPコネクションの確立、解放シーケンス - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tshark -i vetha0a16d8a -nn
Running as user "root" and group "root". This could be dangerous.
Capturing on 'vetha0a16d8a'
    1 0.000000000 192.168.122.177 → 10.88.0.2    TCP 74 57960 → 80 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=372010414 TSecr=0 WS=128
    2 0.000116548    10.88.0.2 → 192.168.122.177 TCP 74 80 → 57960 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=2845155008 TSecr=372010414 WS=128
    3 0.001329368 192.168.122.177 → 10.88.0.2    TCP 66 57960 → 80 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=372010416 TSecr=2845155008
    4 0.001448952 192.168.122.177 → 10.88.0.2    HTTP 150 HEAD / HTTP/1.1
    5 0.001659216    10.88.0.2 → 192.168.122.177 TCP 66 80 → 57960 [ACK] Seq=1 Ack=85 Win=29056 Len=0 TSval=2845155010 TSecr=372010416
    6 0.003723081    10.88.0.2 → 192.168.122.177 HTTP 304 HTTP/1.1 200 OK
    7 0.004638309 192.168.122.177 → 10.88.0.2    TCP 66 57960 → 80 [ACK] Seq=85 Ack=239 Win=30336 Len=0 TSval=372010420 TSecr=2845155012
    8 0.005427533 192.168.122.177 → 10.88.0.2    TCP 66 57960 → 80 [FIN, ACK] Seq=85 Ack=239 Win=30336 Len=0 TSval=372010420 TSecr=2845155012
    9 0.005777478    10.88.0.2 → 192.168.122.177 TCP 66 80 → 57960 [FIN, ACK] Seq=239 Ack=86 Win=29056 Len=0 TSval=2845155014 TSecr=372010420
   10 0.006914414 192.168.122.177 → 10.88.0.2    TCP 66 57960 → 80 [ACK] Seq=86 Ack=240 Win=30336 Len=0 TSval=372010422 TSecr=2845155014

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ