TT Lab
はじめる
学ぶ 学習パス コース

ボリューム・ネットワーク・Compose

コンテナはお互いをどうやって見つけるのか

TT Labで続きを見る

一言でいうと

ネットワークネームスペースは、インターフェースとルーティングテーブルだけでなく、ループバックアドレスとポート番号の空間までまるごと複製します。そのため、コンテナの127.0.0.1は、ホストの127.0.0.1とはまったく別のアドレスです。

なぜ必要なのか

初心者が踏む3つの症状は、すべてこの1つの文で説明できます。

  1. ブラウザーで接続が拒否されます
  2. 隣のコンテナからcurl http://api:3000を実行しても、名前が見つかりません
  3. コンテナからcurl http://127.0.0.1:5432を実行しても、ホストのDBに接続できません

3つ目が特に紛らわしいです。コンテナの中から127.0.0.1に接続すると、そのコンテナ自身を指します。ホストではありません。

どう動くのか

デフォルトのブリッジネットワークでは、コンテナ名でお互いを見つけられません。

docker run -d --name db postgres:16
docker run --rm alpine:3.20 ping -c1 db
# ping: bad address 'db'

ユーザー定義ネットワークを作って接続すれば解決します。違いは、コンテナ内の/etc/resolv.confにそのまま表れます。ネームサーバーが、コンテナのネームスペース内にだけ存在するアドレスに設定され、そのリクエストがランタイムのリゾルバーに転送されます。リゾルバーは、同じネットワークにいるコンテナの名前とエイリアスを知っているため、答えてくれます。デフォルトのブリッジは、後方互換のために残っているもので、実務では常にユーザー定義ネットワークを作って使います。

2つ目に重要なのは、アプリケーションのバインドアドレスです。

docker exec api ss -tlnp
# LISTEN 0 511 127.0.0.1:3000 ...

このソケットには、ホストから入ってきたパケットが決して届きません。パケットはeth0のアドレスに届くからです。正解は0.0.0.0:3000です。「0.0.0.0は危険ではないのか」という反論がありますが、コンテナ内での0.0.0.0は、そのコンテナのネットワークネームスペース全体を意味するだけで、外部に公開されるかどうかは、パブリッシュのバインドアドレスが決めます。2つの階層を混ぜると、両方とも間違います。

現場での姿

ポートのパブリッシュのデフォルトは、ホストのすべてのインターフェースです。

docker run -d -p 5432:5432 postgres:16            # 위험
docker run -d -p 127.0.0.1:5432:5432 postgres:16  # 안전

最初の行は、サーバーがパブリックIPを持っている場合、インターネットにデータベースを開放したのと同じです。そして、ホストのファイアウォールで防いだからと安心してはいけません。コンテナに向かうパケットは、ホストが最終的な宛先ではないため、通常のINPUTルールを通らず、それより前の段階ですでに宛先が書き換えられます。最も安全でシンプルな解決策は、そもそもバインドアドレスを制限することです。

コンテナ同士でだけ通信するサービスは、そもそもパブリッシュしないのが正しいです。そして、コンテナを複数のネットワークに同時に接続できる点も、覚えておいてください。フロントエンドのネットワークとバックエンドのネットワークを分け、アプリだけを両方に所属させれば、DBは外部からまったく見えなくなります。

ネットワークの種類と使う場面

bridge  (기본)   가상 스위치. 컨테이너끼리 사설 IP 로 통신
host             호스트 네트워크를 그대로 씀. 격리 없음
none             네트워크 없음. 계산만 하는 작업
container:<이름>  다른 컨테이너의 네임스페이스 공유

デフォルトのbridgeとユーザー定義のbridgeは違います。デフォルトのネットワークでは、名前でお互いを見つけられず、IPを知っている必要があります。ユーザーが作ったネットワークには、内蔵のDNSが付き、コンテナ名がそのままホスト名になります。

docker network create app-net
docker run -d --name db --network app-net postgres:16
docker run --rm --network app-net alpine ping -c1 db     # 이름으로 닿는다

そのため、実務では常にネットワークを1つ作って使います。composeは、これを自動で行ってくれます。

ポートマッピングがすることとしないこと

-p 8080:80は、ホストの8080をコンテナの80につなぐNATルールです。コンテナ同士は、このマッピングとは無関係に、コンテナのポートに直接通信します。

호스트에서 → localhost:8080
다른 컨테이너에서 → web:80        ← 8080 이 아니다

これを取り違えて、コンテナの中でlocalhost:8080を呼び出すミスがよくあります。コンテナのlocalhostは自分自身です。ほかのコンテナを呼び出すには、名前を使います。

-p 127.0.0.1:8080:80のようにアドレスを付けると、外部に公開されません。開発用のデータベースを開くとき、この習慣が事故を防ぎます。-p 5432:5432は、ファイアウォールがなければ、インターネットにDBを開放することです。

ホストからコンテナの中を覗く

コンテナにssやtcpdumpがなくても、ホストからそのネームスペースに入れます。

PID=$(docker inspect -f '{{.State.Pid}}' myapp)
nsenter -t $PID -n ss -ltnp          # 컨테이너의 리스닝 포트
nsenter -t $PID -n tcpdump -i any -c 20 port 80

-nは、ネットワークネームスペースにだけ入るという意味なので、ホストのツールをそのまま使いながら、コンテナのネットワークを見ます。distrolessイメージを診断する最も確実な方法です。

次のラボですること

ユーザー定義ネットワークを作って名前でお互いを見つけられるようにし、別のネットワークのコンテナが本当に見えないかを確認したうえで、ループバックにだけポートをパブリッシュしてみます。