Dockerコンテナのネットワーク
こちらでProxmoxの上にLXCコンテナを作った場合のネットワークを調べた。
さらにDockerコンテナを作った時のネットワークについて調べる。
pve1=Proxmoxホスト
docker01=Proxmox pve1上のLXCコンテナ=Dockerホスト
apline01=LXCコンテナdocker01上のDockerコンテナ
親:NICの数はDockerをインストールする前と変わらない
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に所属する普通のネットワークインターフェイス。
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がない理由は後述)
/ # 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に相当する。
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の出力にはあった。親を指しているはず。
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
孫からは孫自身しか見えない。
/ # 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に入れる。
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がある)ことによる。
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コンテナから出たパケットは以下の処理を行われて外部に出ていく。
- network namespace Zのルーティングテーブルに従い、デフォルトルートである172.17.0.1に向かう
- 172.17.0.1/24と同じセグメントのIP 172.17.0.2/24を持つalpine01:eth0から送出
- veth対向であるnetwork namespace YのC1に着信
- network namespace YがIP 172.17.0.1を持っているので、このnetwork namepaceでパケットを受け入れ、処理をする
- network namespace Yのルーティングテーブルに従い、デフォルトルートである192.168.10.1に向かう
- 192.168.10.1/24と同じセグメントのIP 192.168.10.103/24を持つdocker01:eth0から送出される予定
- network namespace YのPOSTROUTINGチェインの設定に従いSource NAPTされたのちにdocker01:eth0から送出
- veth対向であるB1に着信
- Bridge BのFDB(Forwarding DataBase)に192.168.10.1のMACアドレスがあればそのポートに転送。なければフラッディング。(どちらにせよこの構成ではB2から送出)
- veth対向であるA1に着信
- 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