Dockerネットワーク

2024年3月2日

背景

Dockerは、コンテナ技術として広く用いられており、業務でも多く使用している。
しかし、コマンドを叩けばなんとなく使えてしまうため、上辺だけの理解で終わり、詳細の仕組みについてわかっていないところが多い。結果として、環境構築時に影響範囲を把握できなかったり、デバッグに時間がかかってしまうことがある。

目的

実際にdockerコンテナを作成し、コンテナ間の通信、コンテナ外との通信を行い、どのようなネットワーク構成になっているかを理解する。

Windows+WSL環境

まず、docker環境の構築を始める前に、私の環境がwindows11のwslを利用してubuntu環境を設定しているため、この環境におけるネットワークの構成について確認する。

windows osのネットワークの確認

コマンドプロンプト上でipアドレスの情報を表示するipcinfigを実行

ipconfig実行結果
C:\Users\sherl>ipconfig

Windows IP 構成


イーサネット アダプター イーサネット 2:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

不明なアダプター ローカル エリア接続:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

Wireless LAN adapter ローカル エリア接続* 1:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

Wireless LAN adapter ローカル エリア接続* 2:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

Wireless LAN adapter Wi-Fi:

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::219:7297:1fb5:82a2%15
   IPv4 アドレス . . . . . . . . . . . .: 172.22.1.30
   サブネット マスク . . . . . . . . . .: 255.255.248.0
   デフォルト ゲートウェイ . . . . . . .: 172.22.0.1

イーサネット アダプター Bluetooth ネットワーク接続 2:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

イーサネット アダプター vEthernet (nat):

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::2820:f9c1:1a25:e14b%26
   IPv4 アドレス . . . . . . . . . . . .: 172.23.48.1
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:

イーサネット アダプター vEthernet (Default Switch):

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::8977:c635:78b:6be8%33
   IPv4 アドレス . . . . . . . . . . . .: 172.23.16.1
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:

イーサネット アダプター vEthernet (WSL (Hyper-V firewall)):

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: fe80::dc31:8e1d:6553:a201%80
   IPv4 アドレス . . . . . . . . . . . .: 172.25.128.1
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:

この実行結果から、

  • 物理NIC(wifi)のipアドレスが172.22.1.30/21
  • 仮想NIC(WSL)のipアドレスが172.25.128.1/20

であることがわかる。

WSL2(Ubuntu)のネットワークの確認

WSL2上でipアドレスの情報を表示するip a(ip address show)コマンドを実行

ip a実行結果
~$ ip -br -4 a
lo               UNKNOWN        127.0.0.1/8
eth0             UP             172.25.140.84/20

この実行結果から

  • 仮想NIC(eth0)のipアドレスが172.25.140.84/20

であることがわかる。
また、経路情報を出力するip rコマンド、指定したアドレスへの通信で中継するアドレスを表示するtracerouteコマンドを実行すると、

ip r, traceroute実行結果
~$ ip route
default via 172.25.128.1 dev eth0 proto kernel
172.25.128.0/20 dev eth0 proto kernel scope link src 172.25.140.84
~$ traceroute google.com
traceroute to google.com (142.251.222.14), 30 hops max, 60 byte packets
 1  ****** (172.25.128.1)  0.506 ms  0.461 ms  0.444 ms
 2  172.22.0.1 (172.22.0.1)  12.829 ms  12.819 ms  12.811 ms
 3  118.23.7.234 (118.23.7.234)  12.769 ms  14.922 ms  14.899 ms
 4  118.23.7.77 (118.23.7.77)  12.691 ms  12.684 ms  14.848 ms
 5  118.23.30.149 (118.23.30.149)  14.834 ms  17.307 ms  12.670 ms
 6  153.146.171.73 (153.146.171.73)  12.659 ms  20.154 ms  20.131 ms
 7  122.1.245.209 (122.1.245.209)  20.118 ms  11.076 ms  11.030 ms
 8  122.28.104.118 (122.28.104.118)  13.472 ms 60.37.54.106 (60.37.54.106)  8.385 ms 122.1.245.10 (122.1.245.10)  8.368 ms
 9  61.199.130.22 (61.199.130.22)  13.410 ms 61.126.86.30 (61.126.86.30)  10.891 ms 211.129.52.86 (211.129.52.86)  10.901 ms
10  * * *
11  216.239.58.207 (216.239.58.207)  10.820 ms 142.250.62.46 (142.250.62.46)  10.811 ms 216.239.58.205 (216.239.58.205)  14.996 ms
12  108.170.242.143 (108.170.242.143)  15.013 ms 216.239.58.207 (216.239.58.207)  14.942 ms nrt13s71-in-f14.1e100.net (142.251.222.14)  12.090 ms

この実行結果から、仮想NIC(eth0)172.25.140.84のネクストホップアドレスは、仮想NIC(vEthernet)172.25.128.1である。そこから物理NIC(wifi)172.22.1.68を経由して、デフォルトゲートウェイである172.22.0.1に到達し、外部ネットワークに接続される。

ネットワーク構成

以上をまとめると、下記のような構成になる。
WSL2のネットワークは、Hyper-Vによる仮想マシンと同じく、仮想スイッチによるネットワーク機能を使用するが、WSL2側から仮想スイッチを介してインターネットアクセスが可能なNAT機能が有効化されている。そのため、物理NIC(wifi)の属するネットワークと仮想スイッチの属するネットワークは異なる。

Windows11 パソコン
Windows11 パソコン
WSL2
WSL2
Windows 11
Windows 11
仮想NIC
vEthernet(WSL)
仮想NIC…
172.25.128.1/20
172.25.128.1/20
物理NIC(wifi)
物理NIC(wifi)
172.22.1.30/21
172.22.1.30/21
仮想NIC
eth0
仮想NIC…
172.25.140.84/20
172.25.140.84/20
ubuntu
ubuntu
インターネット
インターネット
ルーター
ルーター
172.22.0.1(デフォルトゲートウェイ)
172.22.0.1(デフォルトゲートウェイ)
Hyper-V
Hyper-V
wslhost.exe
wslhost.exe
Windows ファイアウォール
Windows ファイアウォール
仮想スイッチングハブ
仮想スイッチングハブ
LAN内別PC
LAN内別PC
スイッチ
スイッチ
172.25.128.0/20
172.25.128.0/20
NAT機能で変換
NAT機能で変換
ネットワークが異なる
ネットワークが異なる
Text is not SVG – cannot display

※Windowsファイアウォールでは、デフォルトではICMPのエコー要求/応答メッセージの受信を許可していないので、許可をすることでpingにより物理NIC(wifi)への疎通を確認することができる。

Docker networkの構築

それでは実際にWSL2上のゲストOS(Ubuntu)にDockerのネットワークを構築し、環境の詳細を調査する。

2つのコンテナをbridgeネットワークでつなぐ

今回構築する環境は、下記の通り2つのコンテナをbridgeネットワークでつないだ環境である。

Docker Container
ping
ping
traceroute
traceroute
疎通確認のためのコマンド
疎通確認のためのコマンド
container1
container1
tcpdump
tcpdump
仮想NIC
eth0@if7
仮想NIC…
172.17.0.2/16
172.17.0.2/16
Docker Container
ping
ping
traceroute
traceroute
疎通確認のためのコマンド
疎通確認のためのコマンド
container2
container2
tcpdump
tcpdump
仮想NIC
eth0@if11
仮想NIC…
172.17.0.3/16
172.17.0.3/16
仮想NIC
eth0
仮想NIC…
172.25.140.84/20
172.25.140.84/20
bridgeネットワーク
docker0
172.17.0.1/16
bridgeネットワーク…
veth293cce7@if6
veth293cce7@if6
veth17f547e@if10
veth17f547e@if10
NATとポート変換
NATとポート変換
Text is not SVG – cannot display

まずはcontainer1を準備する。

##コンテナ起動
~$ docker run -it --name container1 ubuntu /bin/bash

##コンテナ内で必要なツールをインストール
root@57b04994fece:/# apt update
root@57b04994fece:/# apt -y upgrade
root@57b04994fece:/# apt install -y iproute2 iputils-ping traceroute tcpdump

次にcontainer2を準備する。

##コンテナ起動
~$ docker run -it --name container2 ubuntu /bin/bash

##コンテナ内で必要なツールをインストール
root@091001465cd8:/# apt update
root@091001465cd8:/# apt -y upgrade
root@091001465cd8:/# apt install -y iproute2 iputils-ping traceroute tcpdump

bridgeネットワークの設定を確認すると、ipアドレスが172.17.0.1のdocker0というネットワークが作成され、そのネットワーク上に、ipアドレスが172.17.0.2のcontainer1とipアドレスが172.17.0.3のcontainer2が接続されていることがわかる。

bridge network
~$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "06f6fbb7658aaea85249b8d51abda1b1e757196afda5626a18ced1c2eb96feca",
        "Created": "2024-03-02T15:37:14.692854278+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "091001465cd8a07f5581aba28967387cb443c773d9b5002883b6ed3c95f5224e": {
                "Name": "container2",
                "EndpointID": "d39fd5893248d2b661290985ed1730f9ab99e94f808afa77b883d7a6d5b6479a",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "57b04994fece39be3607ef6d315817d62434683c84e0267f9bc99e791caac842": {
                "Name": "container1",
                "EndpointID": "2d86cee735719881769a6b2a81e33784ee5c8c7e7f331f5c7e54d3c33dc2de0a",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

また、ip address showコマンドで、veth293cce7@if6veth17f547e@if10の仮想NICが作成されていることがわかる。
vethを作成すると2つの仮想NICのペアができ、この二つの仮想NIC間で通信が可能となる。
ペアのうち上記仮想NICはホスト側の仮想ブリッジ(docker0)に接続され、そのもう片方のペアはコンテナ内からeth0として見えるようになる。
各仮想NICの組み合わせは、下記ページのツールを利用すると良い。

https://github.com/pujoheadsoft/docker-netns

Docker networkの通信確認

次に、作成したコンテナを用いて、ネットワーク通信の確認を行う。

コンテナ間の通信を確認

pingでcontainer1からcontainer2に向けて疎通確認を実行している間に、tcpdumpコマンドで各インターフェースをキャプチャする。

container1からcontainer2(172.17.0.3)へping

~$ docker exec -it container1 /bin/bash
root@57b04994fece:/# ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.100 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.074 ms

container1 eth0@if7

root@57b04994fece:/# tcpdump -tnl -i eth0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 9, seq 1, length 64
IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 9, seq 1, length 64

ブリッジ側 veth293cce7@if6(eth0@if7のペア)

~$ sudo tcpdump -tnl -i veth293cce7 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth293cce7, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 10, seq 1, length 64
IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 10, seq 1, length 64

ブリッジ側 veth17f547e@if10(eth0@if11のペア)

~$ sudo tcpdump -tnl -i veth17f547e icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth17f547e, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 11, seq 1, length 64
IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 11, seq 1, length 64

container2 eth0@if11

root@091001465cd8:/# tcpdump -tnl -i eth0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 172.17.0.2 > 172.17.0.3: ICMP echo request, id 12, seq 1, length 64
IP 172.17.0.3 > 172.17.0.2: ICMP echo reply, id 12, seq 1, length 64

以上から、コンテナ間の通信がbridgeを経由して行われていることが確認できました。

コンテナと外部の通信を確認

次にコンテナから外部への通信を確認する。
ゲストOSであるUbuntu内の各インターフェースにおけるパケットキャプチャは、引き続きtcpdumpコマンドを利用する。
ホストPCの物理NICにおけるパケットキャプチャは、wiresharkを利用する。

コンテナ1から、GoogoleのパブリックDNSサービスのIPアドレスである8.8.8.8へ向けてpingを実行する。

~$ docker exec -it container1 /bin/bash
root@57b04994fece:/# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=33.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=16.3 ms

container1 eth0@if7

root@57b04994fece:/# tcpdump -tnl -i eth0 icmp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 172.17.0.2 > 8.8.8.8: ICMP echo request, id 16, seq 1, length 64
IP 8.8.8.8 > 172.17.0.2: ICMP echo reply, id 16, seq 1, length 64

ブリッジ側 veth293cce7@if6(eth0@if7のペア)

~$ sudo tcpdump -tnl -i veth293cce7 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth293cce7, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 172.17.0.2 > 8.8.8.8: ICMP echo request, id 17, seq 1, length 64
IP 8.8.8.8 > 172.17.0.2: ICMP echo reply, id 17, seq 1, length 64

wsl2上のubuntuのeth0

~$ sudo tcpdump -tnl -i eth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 172.25.140.84 > 8.8.8.8: ICMP echo request, id 18, seq 1, length 64
IP 8.8.8.8 > 172.25.140.84: ICMP echo reply, id 18, seq 1, length 64

この結果からわかるように、wsl2のubuntuのeth0インターフェースの直前に、IPマスカレードによって送信元アドレスが変換されて、172.25.140.84のアドレスになることがわかる。

iptablesコマンドでnatの確認を行うと、172.17.0.0/16から外部への通信に対してMASQUERADEが設定されていることがわかる。

~$ sudo iptables --list -t nat -n
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  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     all  --  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  all  --  172.17.0.0/16        0.0.0.0/0

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

ホストPCの物理NIC wifi

wiresharkでのキャプチャ結果

この結果からわかるように、ホストPCの物理NICの直前でも、IPマスカレードによって送信元アドレスが変換されて、172.22.1.30のアドレスになることがわかる。

container1でtraceroute

container1で8.8.8.8へのtracerouteコマンドを実行する。

container1内部から8.8.8.8へのtraceroute
root@57b04994fece:/# traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  172.17.0.1 (172.17.0.1)  0.076 ms  0.007 ms  0.004 ms
 2  ******* (172.25.128.1)  0.231 ms  0.218 ms  0.206 ms
 3  172.22.0.1 (172.22.0.1)  5.584 ms  5.578 ms  5.567 ms
 4  118.23.7.234 (118.23.7.234)  5.553 ms  5.541 ms  7.598 ms
 5  118.23.7.73 (118.23.7.73)  5.506 ms  5.494 ms  5.474 ms
 6  118.23.30.145 (118.23.30.145)  7.514 ms  9.217 ms  9.176 ms
 7  153.146.171.65 (153.146.171.65)  9.165 ms  9.153 ms  9.145 ms
 8  122.1.245.209 (122.1.245.209)  9.132 ms 118.23.168.141 (118.23.168.141)  8.809 ms  8.779 ms
 9  122.28.104.118 (122.28.104.118)  8.785 ms 122.28.104.122 (122.28.104.122)  8.783 ms  8.771 ms
10  61.199.130.22 (61.199.130.22)  15.110 ms  11.003 ms 142.250.168.41 (142.250.168.41)  8.730 ms
11  * * *
12  dns.google (8.8.8.8)  34.181 ms  29.439 ms  34.137 ms

以上から、container1から外部への通信経路が、
eth0@if7(container1)veth293cce7@if6eth0(ubuntu仮想NIC)vEthernet(WSL windows側)物理NIC(wifi)デフォルトゲートウェイ(172.22.0.1)インターネット
であることが確認できる。

参考

  • https://news.mynavi.jp/article/20220925-2462456/
  • https://tech.quartetcom.co.jp/2022/06/29/docker-bridge-network/

Posted by okuribito