Dockerネットワーキングのトラブルシューティング:接続問題を効果的に解決する

コンテナDNS、ユーザー定義ネットワーク、ポート公開、ホストアクセス、DNS、ファイアウォールを使用してDockerネットワーキングの問題を修正します。

Dockerネットワーキングのトラブルシューティング:接続問題を効果的に解決する

Dockerネットワーキングの問題は、失敗した接続の方向を特定すると解決がはるかに簡単になります。「コンテナが接続できない」という表現は曖昧すぎます。ホストがコンテナに到達しようとしているのか? あるコンテナが別のコンテナに到達しようとしているのか? コンテナがインターネットに到達しようとしているのか? 別のマシンからトラフィックが入ってきているのか? 各経路で異なるDockerの動作が使用されます。

まず、経路を平易な言葉で書き出します:

ホスト上のブラウザ -> localhost:8080 -> コンテナポート80
apiコンテナ -> dbコンテナ -> ポート5432
workerコンテナ -> パブリックインターネット -> api.example.com:443
リモートラップトップ -> サーバーのパブリックIP -> 公開されたコンテナポート

経路がわかれば、ランダムにネットワークを変更する代わりに、各ホップをテストできます。

基本的なDockerネットワークモードを知る

ほとんどのシングルホストDockerセットアップはブリッジネットワーキングを使用します。Dockerはホスト上に仮想ネットワークを作成し、コンテナにプライベートIPアドレスを割り当て、選択したコンテナポートをホスト上に公開できます。

デフォルトのbridgeネットワークでも機能しますが、ユーザー定義のブリッジネットワークはアプリケーションにとってより優れています。コンテナ名による組み込みDNSを提供するからです。つまり、apiコンテナとdbコンテナが同じユーザー定義ネットワークに接続されていれば、apiコンテナはdb:5432dbコンテナに到達できます。

次のように作成します:

docker network create appnet
docker run -d --name db --network appnet postgres:16
docker run -d --name api --network appnet my-api

他のモードも存在します。hostネットワーキングはホストのネットワーク名前空間を共有し、通常のポート公開動作を無効にします。これは一部のLinuxケースで有用ですが、分離性を低下させます。noneはコンテナにネットワークを与えません。overlayはマルチホストDocker Swarmネットワーキング用です。Composeは、特に設定しない限り、プロジェクト用に自動的にユーザー定義ネットワークを作成します。

「ネットワークが見つかりません」

このエラーは通常、ネットワーク名が間違っているか、ネットワークがコマンドが想定するものとは異なるコンテキストに存在することを意味します。

利用可能なネットワークを確認:

docker network ls

使用したいネットワークを検査:

docker network inspect appnet

存在しない場合は作成:

docker network create appnet

Composeを使用する場合、実際のネットワーク名にはプロジェクト名がプレフィックスとして付加されることがあります。compose.yml内のbackendというネットワークは、myproject_backendとして表示されることがあります。次を使用:

docker compose ps
docker network ls

外部のComposeネットワークを宣言した場合、Composeは自動的に作成しません:

networks:
  appnet:
    external: true

その場合は、手動で作成するか、Composeに管理させる場合はexternal: trueを削除します。

コンテナ間通信が失敗する

2つのコンテナが名前で通信するには、通常、同じユーザー定義ネットワーク上にある必要があります。まずそれを確認:

docker network inspect appnet

Containersセクションで両方のコンテナを探します。

次に、基本的なツールが含まれているコンテナからテストします。アプリケーションイメージにはcurldigpingが含まれていない場合がありますが、それで問題ありません。同じネットワーク上の一時的なデバッグコンテナを使用:

docker run --rm -it --network appnet nicolaka/netshoot

内部から:

dig db
curl -v http://api:8080/health
nc -vz db 5432

DNSが失敗する場合、コンテナが同じユーザー定義ネットワーク上にないか、デフォルトのブリッジネットワークを使用していて、それが同じ方法で名前解決を提供していない可能性があります。DNSが機能するが接続が失敗する場合、宛先サービスが期待されるポートでリッスンしているか確認します。

宛先コンテナ内部:

docker exec -it api sh
ss -ltnp || netstat -ltnp

よくある間違いは、コンテナ内部でアプリを127.0.0.1にバインドすることです。これにより、そのコンテナ内のループバックでのみリッスンします。他のコンテナは到達できません。アプリを0.0.0.0でリッスンするように設定します。

また、コンテナ間トラフィックには、ホスト公開ポートではなく、コンテナポートを使用するようにしてください。データベースがコンテナ内で5432でリッスンしている場合、他のコンテナはlocalhost:15432や公開されたホストポートではなく、db:5432を使用する必要があります。

ホストがコンテナに到達できない

ホストがブリッジネットワーク上のコンテナ内のサービスに到達するには、通常、公開されたポートが必要です:

docker run -d --name web -p 8080:80 nginx

これにより、ホストポート8080がコンテナポート80にマッピングされます。ホストからテスト:

curl -v http://localhost:8080

Dockerが公開したものを確認:

docker port web
docker ps --format 'table {{.Names}}	{{.Ports}}'

ポートマッピングがない場合、Dockerfile内のEXPOSEはポートを公開しません。EXPOSEはドキュメントとメタデータです。-pまたはComposeのports:が依然として必要です。

ポートが公開されているが接続が失敗する場合、4つのことを確認:

  1. アプリケーションがコンテナ内のコンテナポートでリッスンしていること。
  2. アプリケーションが0.0.0.0でリッスンしており、127.0.0.1のみではないこと。
  3. ホストファイアウォールがホストポートをブロックしていないこと。
  4. 他のプロセスがすでにホストポートを所有していないこと。

ホストポートの競合を見つける:

sudo lsof -i :8080
# または
sudo ss -ltnp 'sport = :8080'

Composeの場合、構文を覚えておいてください:

ports:
  - "8080:80"

左側がホストポート、右側がコンテナポートです。

コンテナがインターネットに到達できない

IP接続とDNSを別々にテスト:

docker exec -it app sh
ping -c 2 1.1.1.1
ping -c 2 example.com

一部のイメージにはpingが含まれていません。利用可能な場合はcurlを使用:

curl -I https://example.com

IPは機能するが名前が失敗する場合、DNSの問題です。確認:

cat /etc/resolv.conf

Dockerは通常、リゾルバ設定を注入します。企業VPN、カスタムDNS、Docker Desktopネットワーキングがこれを複雑にする可能性があります。Dockerのデーモン設定でデーモンレベルのDNSを設定するか、特定のコンテナにDNSを渡すことができます:

docker run --dns 1.1.1.1 ...

内部名を解決する必要がある企業環境では、パブリックDNSを盲目的に使用しないでください。ネットワークに適したDNSサーバーを使用してください。

IPもDNSも機能しない場合、コンテナが--network none上にないか、ホストファイアウォール/NATルールが壊れていないか、Dockerデーモンにカスタムネットワーキング設定がないか、ホスト自体にインターネットアクセスがあるかを確認します。

コンテナがホスト上のサービスに到達する必要がある

コンテナから見ると、localhostはコンテナ自体を意味し、ホストではありません。これは最も一般的なDockerネットワーキングの驚きの1つです。

Docker Desktopでは、host.docker.internalは通常ホストに解決されます。最新のDocker Engine for Linuxでは、ホストゲートウェイエントリを追加できます:

docker run --add-host=host.docker.internal:host-gateway ...

その後、コンテナは次のように呼び出せます:

curl http://host.docker.internal:3000

ホストサービスがDockerから到達可能なアドレスでリッスンしていることを確認してください。環境によってDockerがアクセスできないループバックバインディングのみではないことを確認します。ローカル開発サーバーが127.0.0.1のみにバインドしている場合、OSとセキュリティ要件に応じて、0.0.0.0またはホストインターフェースにバインドする必要があるかもしれません。

リモートマシンがコンテナに到達できない

ホストがlocalhost:8080に到達できるが、別のマシンがserver-ip:8080に到達できない場合、Dockerは問題ない可能性があります。ホストファイアウォール、クラウドセキュリティグループ、ルーター/NAT、およびDockerがループバックのみに公開していないかを確認します。

これはすべてのホストインターフェースに公開します:

docker run -p 8080:80 nginx

これはローカルホストのみに公開します:

docker run -p 127.0.0.1:8080:80 nginx

ループバックのみの公開は、ローカル開発やリバースプロキシ設定で望ましいことがよくありますが、設計上リモートアクセスをブロックします。

クラウドサーバーでは、プロバイダーのファイアウォールも確認してください。クラウドセキュリティグループがまだポートをブロックしている場合、VM上でufwを開いても役に立ちません。

Composeネットワーキングの間違い

Composeは各サービスにサービス名に基づいたDNS名を提供します。サービスがdbと呼ばれる場合、他のサービスは通常localhostではなくdbに接続する必要があります。

例:

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16

depends_onは起動順序を制御しますが、準備状態は制御しません。データベースコンテナは、Postgresが接続を受け入れる準備ができる前に起動する可能性があります。アプリケーションは接続を再試行するか、ヘルスチェック対応の起動パターンを使用する必要があります。

また、portsexposeを区別してください。portsはホストに公開します。exposeはリンクされたサービスにポートを文書化または公開しますが、同じ方法でホストから到達可能にはなりません。

パケットレベルのデバッグ

通常のチェックで問題が説明できない場合、ネットワークデバッグイメージを使用:

docker run --rm -it --network container:<target-container> nicolaka/netshoot

これにより、ターゲットコンテナのネットワーク名前空間に参加し、アプリイメージにツールをインストールせずに同じ視点からネットワーキングを検査できます。

便利なコマンド:

ip addr
ip route
cat /etc/resolv.conf
dig service-name
curl -v http://service-name:port
tcpdump -nn -i any port 8080

パケットがまったく到着するかどうかを知る必要がある場合はtcpdumpを使用します。パケットが到着しない場合、コンテナの前(公開、ファイアウォール、ルーティング、ロードバランサー)を確認します。パケットが到着しても応答が出ない場合、コンテナまたはアプリケーション内部を確認します。

短いトラブルシューティングフロー

ほとんどのDockerネットワーキング問題には次の順序を使用します:

  1. 正確な経路を定義:ホストからコンテナ、コンテナからコンテナ、コンテナからインターネット、またはリモートからホストからコンテナ。
  2. docker network inspectでネットワーク接続を確認。
  3. 送信元側で名前解決を確認。
  4. 宛先プロセスが正しいインターフェースとポートでリッスンしているか確認。
  5. ホストからコンテナに渡るトラフィックのみポート公開を確認。
  6. Docker外部のファイアウォール、VPN、プロキシ、DNS、クラウドセキュリティルールを確認。

ほとんどのDockerネットワーキング問題は深いDockerのバグではありません。通常、間違った名前、間違ったポート、ループバックバインディング、公開ポートの欠落、DNSの仮定、または境界の間違った側からトラフィックをテストしていることが原因です。