Kubernetes 解体新書Part 2 ネットワーク

Docker のネットワーク

Docker Compose の Web アプリを例に、コンテナ同士の通信と、外部からの通信の経路を追います。

  • L1 Web ターミナル
  • 更新 2026年10月6日

ブラウザーで開く Web サーバーと、その裏で動くアプリやデータベース。 Docker Compose では、この 3 つを別々のコンテナにして、名前で接続できます。 ホストの 80 番ポートを Web サーバーに公開すれば、外からもアクセスできます。

同じマシンにいるコンテナ同士は、どこを通って通信しているのでしょうか。 図の配線をたどりながら、コンテナ同士の通信と、外から入ってくる通信を見ていきます。

GOAL この章のゴール

  • コンテナ同士の通信と、外部からの通信の経路を図で追える
  • コンテナのネットワーク接続口がホストのどこにつながるか分かる
  • コンテナ名で接続できる理由を説明できる

よくある構成: 80 番だけ公開する

Docker Compose で web、app、db の 3 つを動かす構成を例にします。 docker compose up を実行すると、Compose は <プロジェクト名>_default という名前のネットワークを 1 つ作り、全サービスをそこにつなぎます。 ports: ["80:80"] を書くのは web だけで、app は app:3000、db は db:3306 としてコンテナ同士の通信だけに使います。

Docker Compose の典型構成: web はホストの 80 番で公開、app と db はネットワークの中だけ1 台のホストの中で web / app / db の 3 コンテナが、compose が作った myapp_default という bridge ネットワークに繋がり、web だけがホストの 80 番で公開されている。インターネットDocker ホスト (1 台)bridge ネットワーク myapp_default 172.18.0.0/16webnginx :80appnode :3000dbmysql :3306ports 80:80app:3000db:3306 は内部だけapp:3000 は内部だけ
図 1典型的な Compose 構成。web だけホストの 80 番に公開され、app と db は compose が作った bridge ネットワークの中だけで届く。

この構成には 2 種類の通信があります。 外からホストの 80 番に入ってくる通信と、コンテナ同士が app:3000 のような名前で話す通信です。 前者では、ホストの Linux カーネルがパケットの宛先アドレスを web コンテナの IP に書き換えています (この書き換えを NAT と呼びます)。 後者では、ホスト内のソフトウェアスイッチ (ブリッジ) がコンテナ同士をつなぎ、Docker の DNS サーバーが名前を IP に変えています。 以下、ブリッジ、NAT、DNS の順に見ていきます。

docker0 ブリッジと veth

Docker をインストールすると、ホストに docker0 という Linux ブリッジができます。 物理スイッチのポートに NIC をケーブルでつなぐのと同じように、ここへ NIC をつなぐと、同じブリッジにつながった NIC 同士が直接パケットをやり取りできます。 Docker のドキュメントは bridge ネットワークを「同じブリッジにつながったコンテナ同士が通信でき、つながっていないコンテナからは隔離されるソフトウェアブリッジ」と説明しています。 ホストで ip link show type bridge を実行すると見えます。 既定では 172.17.0.0/16 が docker0 に、ユーザー定義のネットワークには 172.18.0.0/16 以降が順に割り当てられます。

コンテナを起動すると、Docker は vethブイイーサ仮想イーサネットのペア。片方に入ったパケットがもう片方から出てくる、ケーブル 1 本相当の仮想デバイス。用語集で見る を 1 組作ります。 veth は必ず 2 つ 1 組で作られ、片方で送信したパケットは即座にもう片方で受信されます (veth(4))。 Docker はその片方をコンテナの中に移して eth0 という名前にし、もう片方を docker0 に接続します。 コンテナの中だけで見える NIC とルーティング表の組は、コンテナ専用に用意されたものです (この仕組みの名前は次の章で付けます)。 コンテナの中で ip addr を打つと普通の NIC が 1 枚見えるだけですが、そのパケットはケーブルの反対側にあたる veth の片割れを通って、ホストの docker0 に出ていきます。

docker0 ブリッジと veth: コンテナの NIC はホスト内のソフトウェアスイッチに刺さっているホストの中に docker0 というブリッジ (ソフトウェアのスイッチ)があり、各コンテナの eth0 は veth ペアでそのブリッジに繋がる。外へは eth0 を通り、ホストのポート公開は NAT で実現する。Docker ホストweb コンテナeth0172.17.0.2app コンテナeth0172.17.0.3db コンテナeth0172.17.0.4vethvethvethdocker0 (Linux bridge) 172.17.0.1/16eth0 (ホスト)10.0.0.5masquerade / DNAT (netfilter の nat テーブル)外-p 80:80
図 2docker0 ブリッジ。各コンテナの eth0 は veth ペアでブリッジに繋がり、同じブリッジ上のコンテナは直接通信できる。

コンテナから外に出る通信は、docker0 からホストの eth0 に渡されます。 コンテナの IP (172.17.0.2 など) のまま外に出ても返事が戻ってこないので、カーネルは送信元アドレスをホストのアドレスに書き換えて送り出します。 Docker のドキュメントはこれを masquerading と呼び、「ホストの外側のネットワークにある機器からは Docker ホストの IP アドレスしか見えない」と説明しています。 -p 80:80 はその逆向きの書き換えで、ホストの 80 番宛てのパケットの宛先を 172.17.0.2:80 に変えます (DNAT)。 どちらの書き換えも、Docker が nat テーブルの DOCKER チェーンにルールとして登録したもので、実行しているのはカーネルのパケット処理機構 (netfilter) です。

ネットワークドライバー

docker run --network で選ぶネットワークの種類をドライバーと呼びます。 組み込みのドライバーは bridge、host、none、overlay、ipvlan、macvlan の 6 つで、1 台のホストでよく出会うのは次の 4 つです。 違いは、コンテナ専用のネットワーク設定に何を繋ぐかです。

Docker のネットワークドライバーのうち 1 台のホストでよく出会う 4 つ: bridge / host / none / macvlanbridge は docker0 (ソフトウェアのスイッチ) 経由、host はホストの netns を共有、none は loopback だけ、macvlan は物理 NIC 上に MAC アドレス付きの NIC を作る。bridge (既定)コンテナeth0 172.17.0.2docker0eth0 (masquerade)hostコンテナnetns を共有そのままeth0 (ホストと同じ)noneコンテナlo だけ外には繋がらないeth0 (無し)macvlanコンテナ独自 MAC / IPL2 で直結物理 NIC
図 3bridge / host / none / macvlan。違いはコンテナ専用のネットワーク設定に何を繋ぐかだけ。
  • bridge:既定。上で見た docker0 かユーザー定義のブリッジに繋ぐ。同じブリッジのコンテナ同士は直接通信でき、外へは masquerading で出る。
  • host:コンテナとホストの間のネットワークの隔離を無くし、ホストのネットワークをそのまま使う。ブリッジも NAT も無いが、ポートの衝突はホスト上のプロセスと同じように起きる。
  • none:ホストからも他のコンテナからも完全に隔離する。NIC は lo だけ。
  • macvlan:コンテナの仮想 NIC に独自の MAC アドレスを与え、物理ネットワークに直接つながった機器のように見せる。VM からの移行に向くが、Linux カーネルの制約で、macvlan につないだコンテナはホストと直接通信できない。

overlay は複数の Docker デーモンを Swarm でまたぐときのドライバーで、ipvlan は MAC アドレスの数に制限がある環境向けです。 サードパーティのネットワークプラグインを入れる口もあります。 Kubernetes では、この「ネットワークを差し替える口」が独立した規約になっていて、第 4 章で扱います。

コンテナ名で名前が引ける理由

Compose ファイルに db:3306 と書けるのは、dockerd が内蔵 DNS サーバーを持っているからです。 ユーザー定義ネットワーク (Compose が作るネットワークもそうです) に繋いだコンテナの /etc/resolv.conf には nameserver 127.0.0.11 が書かれていて、そのアドレスで dockerd が問い合わせに答えます。 コンテナ名以外の問い合わせは、ホストに設定された DNS サーバーへ転送されます。 既定の docker0 につないだコンテナはホストの /etc/resolv.conf をそのまま引き継ぎ、互いに IP でしか届きません (Docker のドキュメントは既定ブリッジを legacy と位置づけています)。

Docker の内蔵 DNS: コンテナ名で引けるのは dockerd が 127.0.0.11 で答えているからユーザー定義ネットワーク上の app コンテナが db という名前を 127.0.0.11 に問い合わせ、dockerd の内蔵 DNS が 172.18.0.4 を返す。app コンテナconnect("db", 3306)/etc/resolv.conf:nameserver 127.0.0.11dockerd 内蔵 DNSdb → 172.18.0.4web → 172.18.0.2app → 172.18.0.3db コンテナ172.18.0.4db は?172.18.0.4172.18.0.4:3306 へ接続
図 4内蔵 DNS。コンテナ名を 127.0.0.11 に問い合わせると、dockerd が同じネットワーク上のコンテナの IP を返す。

サービスを作り直すと、コンテナは同じ名前のまま別の IP でネットワークに参加し直します。 名前を引き直せば新しい IP が返るので、他のコンテナは名前で接続し続けられます。 この「名前で相手を見つける」しくみを サービスディスカバリサービスディスカバリサービスの所在 (IP とポート) を名前などの安定した識別子から動的に引ける仕組み。用語集で見る と呼びます。 内蔵 DNS はその簡易版で、Kubernetes では CoreDNS と Service がこの役割を担います。

ネットワークとボリュームは外部アタッチ

ここまでに見たとおり、docker0 も veth も、コンテナのプロセスとは別に Docker が作ってホスト側に置いたものです。 コンテナ本体は隔離されたプロセスとルートファイルシステムだけで、ネットワークもボリュームも、コンテナの外で作って起動時に差し込んでいます。

ネットワークとボリュームは外からアタッチする: コンテナの中身はプロセスとファイルだけコンテナ本体 (隔離されたプロセスとルートファイルシステム) に対して、docker network と docker volume で作った資源を外から差し込む。コンテナ隔離されたプロセス+ ルートファイルシステムnetns / mntns / pidns …docker networkbridge / none / macvlandocker network内蔵 DNS も付いてくる--networkdocker volume/var/lib/docker/volumesbind mountホストのディレクトリ-v / --mountどちらも「コンテナの外」で作って、起動時に差し込む
図 5docker network と docker volume で作った資源を、起動時にコンテナへ差し込む。

そのため docker network ls と docker volume ls は、コンテナとは別の寿命を持つ資源を一覧します。 コンテナを消してもネットワークとボリュームは残ります。 NIC を付けるかどうかも起動時に選べます (--network none なら付きません)。 この構造は Kubernetes の Pod でも同じで、Pod のネットワークは Pod の外で用意されます。

手を動かす: Pod の中から同じものを見る

ハンズオン環境の Web ターミナルは、Kubernetes 上の Pod の中で動いています。 中から見ると、Docker コンテナと同じく eth0 が 1 枚あるだけです。

Pod の中の NIC と DNS 設定を見る
ip addr show eth0
cat /etc/resolv.conf

出力例

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 ...
    inet 10.244.0.23/32 scope global eth0
nameserver 10.96.0.10
search handson-you.svc.cluster.local svc.cluster.local cluster.local

nameserver の行には、Docker の 127.0.0.11 に相当する位置に 10.96.0.10 (CoreDNS の Service) が書かれています。 dockerd が名前を答えていた場所に、Kubernetes では CoreDNS が居ます。 IP が /32 なのは、このクラスタのネットワークプラグイン (Cilium) の流儀で、第 4 章で戻ってきます。

手元に Docker がある人は、同じことをコンテナ側とホスト側の両方から見ると腑に落ちます。

docker network create lab
docker run -d --name db --network lab alpine sleep 1d
docker run --rm --network lab alpine nslookup db
ip link show type bridge          # ホスト側: br-<id> が増えている
ip link show type veth            # ホスト側: veth の片割れ

ふりかえり

Q同じ bridge ネットワーク上のコンテナ同士が直接通信できるのはなぜ?

コンテナの eth0 は veth ペアの片割れで、もう片方が docker0 (またはユーザー定義ブリッジ) に刺さっています。同じブリッジにつながった NIC 同士なので、ルーターを介さず直接届きます。

QCompose で db:3306 と書いて繋がる理由として正しいものは?

ユーザー定義ネットワークのコンテナは resolv.conf に 127.0.0.11 を持ち、dockerd がコンテナ名を IP に解決します。既定の docker0 では名前解決が働かず、IP でしか届かない点も覚えておくと良いです。

Q--network host を選んだときに起きることは?

host モードではネットワークが隔離されません。ポートの衝突はホスト上のプロセスと同じように起きますが、ブリッジも NAT も無い分だけ速くなります。

参考