Dockerネットワーク
背景
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)の属するネットワークと仮想スイッチの属するネットワークは異なる。
※Windowsファイアウォールでは、デフォルトではICMPのエコー要求/応答メッセージの受信を許可していないので、許可をすることでping
により物理NIC(wifi)への疎通を確認することができる。
Docker networkの構築
それでは実際にWSL2上のゲストOS(Ubuntu)にDockerのネットワークを構築し、環境の詳細を調査する。
2つのコンテナをbridgeネットワークでつなぐ
今回構築する環境は、下記の通り2つのコンテナをbridgeネットワークでつないだ環境である。
まずは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@if6
とveth17f547e@if10
の仮想NICが作成されていることがわかる。
vethを作成すると2つの仮想NICのペアができ、この二つの仮想NIC間で通信が可能となる。
ペアのうち上記仮想NICはホスト側の仮想ブリッジ(docker0)に接続され、そのもう片方のペアはコンテナ内からeth0として見えるようになる。
各仮想NICの組み合わせは、下記ページのツールを利用すると良い。
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
この結果からわかるように、ホスト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@if6
→eth0(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/