🐳

Docker) コンテナ間およびコンテナ-ホスト間での通信を観察しつつ、ネットワークの仕組みをおさらい

2021/10/04に公開

Dockerでの通信について調べていたら、通信系のコマンドも含めて色々勉強になったらから覚書。

今回取り扱った疑問

  • コンテナ同士で通信ってどうやってるの
  • なんでホストからコンテナにアクセスするとき何にも設定しない場合、コンテナのIPアドレスではアクセスできないの

前提

Dockerコンテナを起動したときのネットワークの全体像

我が家のネットワーク全体像です(本当はルーターとWANの間にモデムがありましたが、もう構っていられないので無いことにしました)。
ここに出てくる一つ一つの役割をまずは理解するところからです。なりふり構わずに手書き。

(いちおう)ルーターってなんですか

ルーターはLAN同士を繋いで世界規模のインターネットを成立させています。
LAN同士の通信を可能にするため、主に下記機能の実行が期待されています。

  • ルーティング=IPアドレスに基づいたLAN内でのパケットの割り振り
  • NAT/IPマスカレード=プライベートIPアドレスとグローバルIPアドレスの変換

これはそれぞれこんな感じ。


ルーティング(なお、ルーティングに使用されるARPテーブルは、登録されていないIPアドレス宛のパケットが届くたびにブロードキャストを実施して作られていく)

NAT/IPマスカレード

dockerの中にはサブネットがあるのです

IPアドレスを調べてみるとこうなります。

Dockerによると、サブネットとゲートウェイをDockerエンジンが自動で作ってくれるそうです!すごいね〜
https://docs.docker.jp/network/dockernetworks.html

サブネットってなんですか

サブネットは、IPアドレスでサブネットマスクをかけられちゃう部分の数字列で表されるネットワークのこと(ただし、サブネットマスクって話はそもそもIPv4でしか出てこないことに注意)。

例えば下記の場合、マスクがかけられてる192.168.2.0がサブネットになる。

  • IPアドレス: 192.168.2.13
  • サブネットマスク: 255.255.255.0

ポイントは同じサブネットに属するホスト機同士は直接通信ができるということ。
直接通信というのは、あくまでもパケットがルーティングされないということです。スイッチはされます。
(出来立てホヤホヤのサブネットでは、スイッチやルーターのような装置が各端末のMACアドレスと自機ポートの対応表を作るため初めにブロードキャストが行われますが、一旦対応表を作れば特定の端末だけに送ることができます)

ゲートウェイってなんですか

発された通信が向かっていく出口のことです。
LANに属する端末にとって、自分が発した通信の向かう先はとりあえずルーターです。ルーターはLANの出口。だから基本的に、LAN内の端末にとってゲートウェイはルーターです。
Dockerエンジンが自動で作るゲートウェイは、要するに仮想ブリッジであるdocker0のことです。ブリッジというもののNATもこなしちゃうから、ほとんどルーターですよね??

ようやく本題

今回考えていたのは、下記2点です。

  • なんでコンテナ同士で、サービス名で通信できるときとできないときがあるの
  • なんでホストからコンテナにアクセスするとき何にも設定しない場合、コンテナのIPアドレスではアクセスできないの
    せっかくなのでコンテナ同士の通信とホストとコンテナでの通信をそれぞれやってみながら考えてみます。

コンテナからコンテナへ通信

先ほどまでの説明を踏まえれば、コンテナ同士の通信はDockerエンジンが作ってくれる同一サブネット内での通信ということになります。つまり直接通信をすることになります。
さっそくコンテナからコンテナへの通信経路を、コンテナ上からtracerouteで追ってみましょう。

$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
	inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255

$ traceroute 172.17.0.3
traceroute to 172.17.0.3 (172.17.0.3), 30 hops max, 60 byte packets
 1  172.17.0.3 (172.17.0.3)  0.390 ms  0.092 ms  0.056 ms

$ traceroute google.com 
traceroute to google.com (172.217.26.46), 30 hops max, 60 byte packets
 1  172.17.0.1 (172.17.0.1)  0.104 ms  0.048 ms  0.058 ms
 2  192.168.0.1 (192.168.0.1)  4.207 ms  4.101 ms  3.998 ms
 3  180.8.126.138 (180.8.126.138)  12.624 ms  12.579 ms  12.497 ms
~~
 8  nrt12s17-in-f46.1e100.net (172.217.26.46)  8.664 ms 108.170.242.209 (108.170.242.209)  8.999 ms 72.14.234.199 (72.14.234.199)  8.374 ms

ifconfigで自分のIPアドレスが172.17.0.2であることを確認しています。
そのあとtracerouteしますが、googleへのアクセスがたくさんのルーターを経由しているのに対して、お隣のコンテナへは直接訪問しちゃってます。

コンテナからホストに通信

今度はためしに、ホストに通信してみる。同じくコンテナ上で下記。

# traceroute 192.168.0.57
traceroute to 192.168.0.57 (192.168.0.57), 30 hops max, 60 byte packets
 1  172.17.0.1 (172.17.0.1)  0.095 ms  0.049 ms  0.036 ms
 2  MacBook (192.168.0.57)  1.678 ms  1.622 ms  1.747 ms
 
# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

なにやら172.17.0.1を経由しますが、これはrouteコマンドで確認できるようにdefault gatewayです。つまりDockerエンジンの作ってくれたブリッジを経由してホストに通信をしたことになります。

いちおうホストの側でブリッジネットワークについて調べておくと、やっぱり172.17.0.1がIPアドレスのブリッジが作られていることがわかります。

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Created": "2021-10-04T08:41:14.493330678Z",
        "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": {
            "~~~": {
                "Name": "コンテナA",
                "IPv4Address": "172.17.0.2/16",
            },
	    
           "~~~": {
                "Name": "コンテナB",
                "IPv4Address": "172.17.0.3/16",
            },
    ~~~
]

ホストからコンテナに通信

ここからはホストからコンテナに通信する場合はどうしようってことを考えます。
素直にコンテナのプライベートIP宛にpingをうってみます。ホスト上で下記。

$ ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
Request timeout for icmp_seq 4
Request timeout for icmp_seq 5
~

なぜか全然つながりません。
というのもtracerouteすればわかるように、ホストは172.17.0.3宛ての通信を、まずdefault gatewayである自宅のルーターに向かって始めてしまっていますが、ルーターはDockerコンテナやブリッジが作られたことなんて知らないのでそんな場所に案内できるはずがないのです。ホスト上で下記。

$ traceroute 172.17.0.3
traceroute to 172.17.0.3 (172.17.0.3), 64 hops max, 52 byte packets
 1  192.168.0.1 (192.168.0.1)  8.291 ms  1.999 ms  1.539 ms
 2  180.8.126.138 (180.8.126.138)  9.729 ms  9.582 ms  5.624 ms
 3  180.8.126.141 (180.8.126.141)  10.817 ms  6.705 ms  8.134 ms
 4  122.1.245.69 (122.1.245.69)  33.024 ms  6.896 ms
    60.37.54.165 (60.37.54.165)  5.788 ms
 5  * * *
 6  * * *
 7  * * *
~~
$ netstat -nr #OS Xなのでnetstatでルーティングテーブルを確認
Routing tables
Internet:
Destination        Gateway            Flags        Netif Expire
default            192.168.0.1        UGScg          en0       
127                127.0.0.1          UCS            lo0       
127.0.0.1          127.0.0.1          UH             lo0       
~~
192.168.0.57       a4:83:e7:ce:62:44  UHLWI          lo0    
~~

一般的な処置として、Dockerが持っているポートフォワード機能で自機へのアクセスを通じてコンテナへの通信を実現する方法が採用されているようです。この方法では、もちろんホストと同じLAN内にある他の端末からもアクセスができます。

ホストからコンテナに、コンテナのIPアドレスで通信したい

一方で論理的には、ホスト機上でルーティングテーブルを書き換えればいいのではないかと考えられるはずです。自宅ルーターに飛んでいってしまうことが問題なのですから、コンテナのプライベートアドレス宛のパケットが自宅ルーターではなく仮想ブリッジに飛ぶようにすればよいわけです。さっそくホスト上で下記を実行してみます。

$ sudo route add 172.17.0.3 172.17.0.1
add host 172.17.0.3: gateway 172.17.0.1

$ traceroute 172.17.0.3
traceroute to 172.17.0.3 (172.17.0.3), 64 hops max, 52 byte packets
traceroute: sendto: Network is unreachable

あれ!?って感じです。コンテナ宛はDockerブリッジに飛ぶはずが、全然飛んでいきません。
試しにDockerブリッジにpingを打ってみます。

$ ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
~~

もう全然つながっていません。ポートフォワーディングを実現できるなら、ホスト機からブリッジへは通信できるはずなのに……

う〜〜ん、これは次回までの宿題ですな😭

Discussion