🫠

Dockerコンテナのネットワーク

2024/05/02に公開

こちらでProxmoxの上にLXCコンテナを作った場合のネットワークを調べた。
https://zenn.dev/takai404/articles/9b23447387ae04

さらにDockerコンテナを作った時のネットワークについて調べる。

pve1=Proxmoxホスト
docker01=Proxmox pve1上のLXCコンテナ=Dockerホスト
apline01=LXCコンテナdocker01上のDockerコンテナ

親:NICの数はDockerをインストールする前と変わらない

親pve1
root@pve1:~# ip link
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 pfifo_fast master vmbr0 state UP mode DEFAULT group default qlen 1000
    link/ether b0:41:6f:0b:a4:47 brd ff:ff:ff:ff:ff:ff
3: wlo1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether f4:6d:3f:c2:1e:6f brd ff:ff:ff:ff:ff:ff
    altname wlp2s0
4: vmbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether b0:41:6f:0b:a4:47 brd ff:ff:ff:ff:ff:ff
53: veth111i0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master fwbr111i0 state UP mode DEFAULT group default qlen 1000
    link/ether fe:1c:df:49:ab:21 brd ff:ff:ff:ff:ff:ff link-netnsid 0
54: fwbr111i0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 76:d1:50:8f:33:88 brd ff:ff:ff:ff:ff:ff
55: fwpr111p0@fwln111i0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vmbr0 state UP mode DEFAULT group default qlen 1000
    link/ether c6:b7:08:c2:2c:91 brd ff:ff:ff:ff:ff:ff
56: fwln111i0@fwpr111p0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master fwbr111i0 state UP mode DEFAULT group default qlen 1000
    link/ether 76:d1:50:8f:33:88 brd ff:ff:ff:ff:ff:ff
root@pve1:~# ip -br -4 addr
lo               UNKNOWN        127.0.0.1/8 
vmbr0            UP             192.168.10.41/24 
root@pve1:~# ip route
default via 192.168.10.1 dev vmbr0 proto kernel onlink 
192.168.10.0/24 dev vmbr0 proto kernel scope link src 192.168.10.41 

子:Dockerインストール時にdocker0(C)というブリッジが作成され、alpine01コンテナ起動時にvethc52438e(C1)が作成された。
vethc52438eのmasterはdocker0
一方、(docker01の)eth0はmasterがないので、ブリッジポートになったわけではなく、あくまでもnetwork namespace Yに所属する普通のネットワークインターフェイス。

子docker01
root@docker01:~# ip link
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: eth0@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether bc:24:11:bf:ee:9b brd ff:ff:ff:ff:ff:ff link-netnsid 0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 02:42:35:df:6b:af brd ff:ff:ff:ff:ff:ff
15: vethc52438e@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether 96:84:3a:6b:4b:df brd ff:ff:ff:ff:ff:ff link-netnsid 1
root@docker01:~# ip -br -4 addr
lo               UNKNOWN        127.0.0.1/8 
eth0@if53        UP             192.168.10.103/24 
docker0          UP             172.17.0.1/16
root@docker01:~# ip route
default via 192.168.10.1 dev eth0 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 
192.168.10.0/24 dev eth0 proto kernel scope link src 192.168.10.103

孫:eth0だけのシンプルな構成
docker01のeth0とalpineのeth0は別物。
alpine:eth0の対向はC1 (以下のip linkのeth0にlink-netnsidがない理由は後述)

孫alpine01
/ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
/ # ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ip route
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

先に図を描くとこんな感じ。

[network namespace X]
nsid 0 -> Y
  +---------------------------------+    +------------------------------------+
  |             vmbr0(A)            |    |             fwbr111i0(B)           |
  |          192.168.10.41/24       |    |                                    |
  +-- enp1s0(A2) -- fwpr111p0(A1) --+    +-- fwln111i0(B2) -- veth111i0(B1) --+
             |            |                         |               |
external NW--+            +-------------------------+               |
====================================================================|=============
[network namespace Y]                                               |
nsid 0 -> X                        +----------------------+         |
nsid 1 -> Z                        |      docker0(C)      |        eth0
                                   |    172.17.0.1/16     |   192.168.10.103/24
                                   +--- vethc52438(C1) ---+
=================================================|================================
[network namespace Z]                            |
nsid 0 -> Y                                     eth0
                                          172.17.0.2/16

network namespaceは3つある。
親からはすべて見える。1~3行目が図中のX, Y, Zに相当する。

親pve1
root@pve1:~# lsns -t net
        NS TYPE NPROCS     PID USER      NETNSID NSFS COMMAND
4026531840 net     293       1 root   unassigned      /lib/systemd/systemd --system --deserialize=23
4026532790 net      28 1329130 100000          0      /sbin/init
4026532869 net       1 1461368 100000 unassigned      /bin/sh
root@pve1:~# ip netns list-id 
nsid 0 

子からは子、孫が見える。
nsid 0はlsnsでは出ないがip linkの出力にはあった。親を指しているはず。

子docker01
root@docker01:~# lsns -t net
        NS TYPE NPROCS   PID USER    NETNSID NSFS                           COMMAND
4026532790 net      29     1 root unassigned                                /sbin/init
4026532869 net       1  3145 root          1 /run/docker/netns/cab9181ad6ac /bin/sh
root@docker01:~# ip netns list-id
nsid 0 
nsid 1 

孫からは孫自身しか見えない。

孫alpine01
/ # apk add util-linux-misc
(1/9) Installing setarch (2.39.3-r0)
(2/9) Installing libblkid (2.39.3-r0)
(3/9) Installing libuuid (2.39.3-r0)
(4/9) Installing libfdisk (2.39.3-r0)
(5/9) Installing libmount (2.39.3-r0)
(6/9) Installing ncurses-terminfo-base (6.4_p20231125-r0)
(7/9) Installing libncursesw (6.4_p20231125-r0)
(8/9) Installing libsmartcols (2.39.3-r0)
(9/9) Installing util-linux-misc (2.39.3-r0)
Executing busybox-1.36.1-r15.trigger
OK: 14 MiB in 33 packages
/ # lsns -t net
        NS TYPE NPROCS PID USER    NETNSID NSFS COMMAND
4026532869 net       2   1 root unassigned      /bin/sh
/ # apk add iproute2
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
(1/9) Installing libcap2 (2.69-r1)
(2/9) Installing zstd-libs (1.5.5-r8)
(3/9) Installing libelf (0.190-r1)
(4/9) Installing libmnl (1.0.5-r2)
(5/9) Installing iproute2-minimal (6.6.0-r0)
(6/9) Installing libxtables (1.8.10-r3)
(7/9) Installing iproute2-tc (6.6.0-r0)
(8/9) Installing iproute2-ss (6.6.0-r0)
(9/9) Installing iproute2 (6.6.0-r0)
Executing iproute2-6.6.0-r0.post-install
Executing busybox-1.36.1-r15.trigger
OK: 10 MiB in 24 packages
/ # ip netns list-id
nsid 0 

上述のip linkの出力にlink-netnsidがなかったのはalpineではデフォルトではipコマンドはbusyboxの簡易版コマンドを使うため。iproute2パッケージをインストール(apk add)した後はlink-netnsidが確認できる。
両コマンドは微妙に出力形式が違う

/ # busybox ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
/ # ip link
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
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
/ # busybox ip -V
BusyBox v1.36.1 (2023-11-07 18:53:09 UTC) multi-call binary.

Usage: ip [OPTIONS] address|route|link|tunnel|neigh|rule [ARGS]

OPTIONS := -f[amily] inet|inet6|link | -o[neline]

ip addr add|del IFADDR dev IFACE | show|flush [dev IFACE] [to PREFIX]
ip route list|flush|add|del|change|append|replace|test ROUTE
ip link set IFACE [up|down] [arp on|off] [multicast on|off]
        [promisc on|off] [mtu NUM] [name NAME] [qlen NUM] [address MAC]
        [master IFACE | nomaster] [netns PID]
ip tunnel add|change|del|show [NAME]
        [mode ipip|gre|sit] [remote ADDR] [local ADDR] [ttl TTL]
ip neigh show|flush [to PREFIX] [dev DEV] [nud STATE]
ip rule [list] | add|del SELECTOR ACTION
/ # ip -V
ip utility, iproute2-6.6.0

親では孫のnetns-idが定義されていないが、PIDを指定してnsenterすれば子を経由せずに直接孫のnetwork namespaceに入れる。

親pve1
root@pve1:~# nsenter -t 1461368 -n ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default  link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

dockerコンテナが外部と通信できる仕組みは以下のようにnetwork namespace YにはルーティングがONになっていて(ip_forward=1)、iptablesによるSource NAPTが有効になっている(POSTROUTINGチェインにMASQUERADEがある)ことによる。

子docker01
root@docker01:~# cat /proc/sys/net/ipv4/ip_forward
1
root@docker01:~# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     0    --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     0    --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  0    --  172.17.0.0/16        0.0.0.0/0           

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     0    --  0.0.0.0/0            0.0.0.0/0           

この設定によってDockerコンテナから出たパケットは以下の処理を行われて外部に出ていく。

  1. network namespace Zのルーティングテーブルに従い、デフォルトルートである172.17.0.1に向かう
  2. 172.17.0.1/24と同じセグメントのIP 172.17.0.2/24を持つalpine01:eth0から送出
  3. veth対向であるnetwork namespace YのC1に着信
  4. network namespace YがIP 172.17.0.1を持っているので、このnetwork namepaceでパケットを受け入れ、処理をする
  5. network namespace Yのルーティングテーブルに従い、デフォルトルートである192.168.10.1に向かう
  6. 192.168.10.1/24と同じセグメントのIP 192.168.10.103/24を持つdocker01:eth0から送出される予定
  7. network namespace YのPOSTROUTINGチェインの設定に従いSource NAPTされたのちにdocker01:eth0から送出
  8. veth対向であるB1に着信
  9. Bridge BのFDB(Forwarding DataBase)に192.168.10.1のMACアドレスがあればそのポートに転送。なければフラッディング。(どちらにせよこの構成ではB2から送出)
  10. veth対向であるA1に着信
  11. Bridge AのFDB(Forwarding DataBase)に192.168.10.1のMACアドレスがあればそのポートに転送。なければフラッディング。(どちらにせよこの構成ではA2から送出)
(再掲)
[network namespace X]
nsid 0 -> Y
  +---------------------------------+    +------------------------------------+
  |             vmbr0(A)            |    |             fwbr111i0(B)           |
  |          192.168.10.41/24       |    |                                    |
  +-- enp1s0(A2) -- fwpr111p0(A1) --+    +-- fwln111i0(B2) -- veth111i0(B1) --+
             |            |                         |               |
external NW--+            +-------------------------+               |
====================================================================|=============
[network namespace Y]                                               |
nsid 0 -> X                        +----------------------+         |
nsid 1 -> Z                        |      docker0(C)      |        eth0
                                   |    172.17.0.1/16     |   192.168.10.103/24
                                   +--- vethc52438(C1) ---+
=================================================|================================
[network namespace Z]                            |
nsid 0 -> Y                                     eth0
                                          172.17.0.2/16

ちなみに9.で、FDBはbridge fdbコマンドで確認可能。当該ブリッジが学習しているMACアドレス・ポートの対応が見れる。
192.168.10.1のMACアドレスである94:83:c4:3a:8f:1aに向かってフレームを送出するにはfwbr111i0(B2)を使えばよいとわかる。

root@pve1:~# bridge fdb show br fwbr111i0
bc:24:11:bf:ee:9b dev veth111i0 master fwbr111i0 
fe:1c:df:49:ab:21 dev veth111i0 vlan 1 master fwbr111i0 permanent
fe:1c:df:49:ab:21 dev veth111i0 master fwbr111i0 permanent
33:33:00:00:00:01 dev veth111i0 self permanent
01:00:5e:00:00:01 dev veth111i0 self permanent
33:33:00:00:00:01 dev fwbr111i0 self permanent
01:00:5e:00:00:6a dev fwbr111i0 self permanent
33:33:00:00:00:6a dev fwbr111i0 self permanent
01:00:5e:00:00:01 dev fwbr111i0 self permanent
94:83:c4:3a:8f:1a dev fwln111i0 master fwbr111i0 
76:d1:50:8f:33:88 dev fwln111i0 vlan 1 master fwbr111i0 permanent
76:d1:50:8f:33:88 dev fwln111i0 master fwbr111i0 permanent
33:33:00:00:00:01 dev fwln111i0 self permanent
01:00:5e:00:00:01 dev fwln111i0 self permanent
root@pve1:~# ip neighbor show 192.168.10.1
192.168.10.1 dev vmbr0 lladdr 94:83:c4:3a:8f:1a REACHABLE 
root@pve1:~# bridge fdb get to 94:83:c4:3a:8f:1a br fwbr111i0
94:83:c4:3a:8f:1a dev fwln111i0 master fwbr111i0 

Bridgeが3つあり、それぞれ違う役割を果たしているのが興味深い

  • A: L2スイッチングハブとして機能。自身もIPを持ちProxmox自体の通信に使われる
  • B: L2スイッチングハブとして機能。ただスイッチングするだけ。(名前に"fw"が入っているのでおそらくFireWallとして使えそう)
  • C: L3スイッチとして機能。ルーティング、NAPTする。

Discussion