Docker ネットワークとポートマッピング
Docker コンテナ (Container) を実行する際、最も重要な設定の一つは、コンテナが外部世界や他のコンテナとどのように通信するかという点です。
デフォルトでは、コンテナは完全に隔離された環境で実行されており、まるで超小型の PC のようです。もしコンテナ内部のアプリケーションが外部からのリクエストを受信する必要がある場合——例えば、Web サーバーが HTTP リクエストをリスン (Listen) したり、API がデータを提供したり、データベース (Database) がクライアントからの接続を受け入れたりする場合——そのポート (Port) は明示的に「エクスポーズ (Expose)」され、ホストマシン (Host Machine) のポートにマッピング (Mapping) される必要があります。
適切なポート設定がなければ、コンテナ化されたアプリケーションは「音信不通」状態になり、ユーザーや他のサービス、さらにはあなた自身もアクセスすることができません。本章では、Docker がネットワーク (Network) 接続を管理するメカニズムを深く掘り下げ、コンテナ起動時にアプリケーションのポートをエクスポーズする方法と、基礎的なネットワークの振る舞いを設定する方法を重点的に解説し、アプリケーションが期待通りにスムーズにアクセスされることを保証します。
1. コンテナネットワークの基礎
ポートエクスポーズの原理を理解するには、まず Docker が使用する基礎的なネットワークモデル (Network Model) を理解する必要があります。
1.1 なぜネットワーク接続が必要なのか?
想像してみてください、あなたの Docker コンテナ内で Web サーバーが稼働しています。このアプリケーションは 80 番または 443 番ポートで HTTP リクエストを受信するように設計されています。もしホストマシン(あなたの PC やサーバー)のネットワークカード (NIC) とコンテナのネットワークカードを接続する方法がなければ、外部からのリクエストはこの Web サーバーを見つけることが絶対にできません。
同様に、2つのコンテナがある場合——1つは Web アプリケーションを実行し、もう1つはデータベースを実行しているとします——これらが正常に動作するためには、相互に通信する手段が必要です。ここで Docker ネットワークの真価が発揮されます。
1.2 デフォルトのネットワーク挙動:bridge ネットワーク
ネットワーク設定を一切指定せずにコンテナを実行すると、Docker は自動的にそれを bridge (ブリッジ) という名前のデフォルトネットワークに接続します。
この bridge は Docker によって管理されるプライベートな内部ネットワークです。このネットワークに接続された各コンテナには、独自の内部 IP アドレス (Internal IP Address) が割り当てられます。同じ bridge ネットワーク上にあるコンテナは、これらの内部 IP を介して、あるいはより便利に、コンテナ名 (Container Name) を介して相互に通信することができます(これは Docker に組み込まれた DNS 解決機能のおかげです)。
しかし、ホストマシンや外部ネットワークの視点から見ると、これらの内部 IP アドレスには直接アクセス (Access) することはできません。
1.3 コンテナの隔離と内部 IP アドレス
コンテナの核心原則の一つは隔離 (Isolation) です。各コンテナは、独自の独立したファイルシステム (File System)、プロセスツリー (Process Tree)、そしてネットワークスタック (Network Stack) を持っています。これはつまり、80 番ポートでアプリケーションを実行しているコンテナは、自分自身のネットワーク名前空間 (Network Namespace) 内でのみ 80 番ポートを開いているということを意味します。この内部の 80 番ポートは、ホストマシン上で開かれている可能性のあるいかなる 80 番ポートとも全く無関係です。
コンテナの内部ポートをホストマシンや外部ネットワークからアクセス可能にするには、この隔離を打ち破るメカニズムが必要です——そのメカニズムがポートエクスポーズ (Port Exposure) です。
2. コンテナのポートをホストマシンにエクスポーズする
ホストマシンや外部のクライアントにコンテナのサービスへアクセスさせる最も主要な方法は、そのポートをパブリッシュ (Publish)(またはエクスポーズ)することです。
2.1 -p パラメータの使用:ポートのマッピング
docker run コマンドにおいて、--publish または -p パラメータは、コンテナ内の1つ以上のポートをホストマシンにマッピングするために使用されます。これは、ホストマシンの特定のポートへのトラフィックをコンテナ内の特定のポートにフォワード (Forward) するファイアウォールルール (Firewall Rule) を作成するようなものです。
基本構文: -p ホストマシンポート:コンテナ内ポート
これが最も一般的で直感的な書き方です:
- ホストマシンポート (host_port): あなたの Docker ホストマシン上のポート番号です。外部クライアントはこのポートを通じて接続を開始します。
- コンテナ内ポート (container_port): コンテナ内部のアプリケーションがリスンしているポート番号です。
例:Web サーバーをエクスポーズする
Nginx コンテナがあり、デフォルトで 80 番ポートで HTTP リクエストをリスンしているとします。これをホストマシンの 8080 番ポートでアクセスしたい場合は:
docker run -d --name my-nginx -p 8080:80 nginxdocker run: 新しいコンテナを実行するコマンドです。-d: コンテナをバックグラウンド(デタッチモード)で実行します。--name my-nginx: コンテナにmy-nginxという名前を付けます。-p 8080:80: コアとなる設定です。ホストマシンの 8080 番ポートをコンテナ内の 80 番ポートにマッピングします。nginx: 使用する Docker イメージです。
実行後、ブラウザを開いて http://localhost:8080 にアクセスすると、Nginx のウェルカムページが表示されます。ホストマシンの 8080 番ポートに入ってくるすべてのトラフィックは、my-nginx コンテナの 80 番ポートにシームレスにフォワードされます。
2.2 ホストマシンの IP アドレスを指定したマッピング
より高度なシナリオでは、ポートマッピングをホストマシンの特定のネットワークカードや IP アドレスに制限したい場合があります。ホストマシンが複数の IP を持っている場合、これによりサービスを特定のネットワークインターフェース上でのみ外部にエクスポーズさせることができます。
応用構文: -p ホストマシンIP:ホストマシンポート:コンテナ内ポート
例:特定のホストマシン IP にバインド (Bind) する
ホストマシンが 192.168.1.100 と 10.0.0.5 という2つの IP を持っていると仮定します。Nginx に 192.168.1.100 の 8080 番ポート経由でのみアクセスを許可したい場合は:
docker run -d --name my-nginx-specific-ip -p 192.168.1.100:8080:80 nginxこれにより、Nginx には [http://192.168.1.100:8080](http://192.168.1.100:8080) を介してのみアクセス可能になり、他の IP アドレスの 8080 番ポートではこのコンテナにアクセスできなくなります。
2.3 複数のポートのマッピング
1つのコンテナが複数のサービスを実行し、それぞれのサービスが異なるポートをリスンしている場合があります。-p パラメータを複数回使用して、異なるポートペアをマッピングすることができます。
例:SSH アクセス付きの Web サーバー(想定シナリオ)
本番環境 (Production Environment) では推奨されませんが、開発時 (Development) には Web サービス(80 番ポート)と SSH デバッグサービス(22 番ポート)に同時にアクセスする必要があるかもしれません:
docker run -d --name dev-server -p 8080:80 -p 2222:22 my/dev-imageここでは、ホストマシンの 8080 番がコンテナの 80 番にマッピングされ、ホストマシンの 2222 番がコンテナの 22 番にマッピングされています。
2.4 利用可能なホストマシンポートの選択
ホストマシンのポートを選択する際、そのポートがホストマシン上の他のプログラムによって占有されていないことを確認する必要があります。競合 (Conflict) が発生した場合、Docker はエラーを報告します。通常、システムでよく知られたポート(80、22、443 など)との競合を避けるために、開発やカスタムアプリケーションでは高いポート番号(3000、8080、5000 など)を使用するのが習慣です。
3. 自動ポートパブリッシュと EXPOSE インストラクション
明示的に1対1でポートをマッピングするだけでなく、Docker はポートを自動的に割り当てるメカニズムも提供しています。
3.1 -P (大文字) パラメータの使用:自動パブリッシュ
--publish-all または大文字の -P パラメータを使用できます。このパラメータは、Dockerfile 内で EXPOSE インストラクションによって明示的に宣言されたすべてのポートを、ホストマシン上の利用可能な高ポートに自動的かつランダムにマッピングします。
例:-P パラメータの使用
nginx の Dockerfile に EXPOSE 80 が含まれていると仮定します。
docker run -d --name my-nginx-auto -P nginx実行後、Docker はホストマシンポート(例えば 32768)をランダムに割り当て、それをコンテナの 80 番ポートにマッピングします。どのポートが割り当てられたかを正確に確認するには、docker ps コマンドを使用します:
docker ps出力は以下のようになります:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:32768->80/tcp my-nginx-autoここの 0.0.0.0:32768->80/tcp は、ホストマシンの 32768 番ポートがコンテナの 80 番ポートに正常にマッピングされたことを示しています。
3.2 Dockerfile の EXPOSE インストラクション
EXPOSE インストラクションは、Dockerfile において主に宣言とドキュメント (Document) としての役割を果たします。これは Docker およびユーザーに対して、このコンテナ内のアプリケーションがどのポートを「リスンするつもりであるか」を伝えます。
注意: EXPOSE 自体が実際にホストマシン上でこれらのポートを開いたりマッピングしたりすることはありません。それは単なる宣言であり、-P パラメータと連携して機能します。
# Dockerfile 内部の例
FROM ubuntu
RUN apt-get update && apt-get install -y nginx
EXPOSE 80 443 # このイメージのサービスが 80 と 443 ポートをリスンすることを宣言する
CMD ["nginx", "-g", "daemon off;"]この例で、ユーザーが docker run -P my-nginx-image を実行した場合、80 番と 443 番の両方のポートが自動的にマッピングされます。ユーザーが docker run -p 8080:80 my-nginx-image を実行した場合、80 番ポートのみがマッピングされ、443 番ポートは内部のプライベートな状態に保たれます。
3.3 いつ -p を使い、いつ -P を使うべきか?
- 正確な制御には
-pを使用する: 本番環境での第一選択です。どのホストマシンポートを使用するかを正確に制御し、アクセスアドレスが固定され予測可能であることを保証します。 - 迅速なテストには
-Pを使用する: 開発環境で素早くコンテナを起動し、具体的にどのホストマシンポートを使用するか気にしない場合、-Pは非常に便利です。
4. 他のネットワークモードの探索:Host モード
デフォルトの bridge ネットワークと明示的なポートマッピングが最も一般的に使用されますが、Docker は特定のシナリオ向けに他のネットワークモードも提供しています。その中で最も有名なのが host ネットワークモードです。
4.1 Host ネットワークモード (--network host)
--network host を使用してコンテナを実行すると、コンテナはホストマシンのネットワークスタックを直接共有します。
host モードでは、コンテナは独自の隔離されたネットワーク名前空間、IP アドレス、またはポートスペースを持ちません。代わりに、ホストマシンのネットワークカードと IP アドレスを直接使用します。コンテナ内のアプリケーションが 80 番ポートをリスンしている場合、それは実際にはホストマシンの 80 番ポートを直接リスンしていることになります。
主要な影響:
- ポートマッピングは不要: ネットワークが共有されているため、
-pや-Pを使用する必要はありません。コンテナのポートはホストマシン上に直接エクスポーズされます。 - ネットワーク隔離の喪失: コンテナとホストマシン間のネットワーク隔離が打破されます。慎重に管理しないと、コンテナがホストマシンネットワークの最高権限を持つことになり、セキュリティ上のリスクが生じます。
- 潜在的なポート競合: コンテナがバインドしようとするポートがすでにホストマシン上の他のプログラムによって占有されている場合、コンテナは起動できないか、アプリケーションがクラッシュします。
4.2 Host モードの応用シナリオ
host モードは通常、極めて高いネットワークパフォーマンスが要求されるシナリオや、コンテナが NAT (ネットワークアドレス変換) レイヤーをバイパスしてホストマシンのネットワークサービスに直接アクセスする必要がある状況で使用されます。
- 高性能なリバースプロキシ (Reverse Proxy) / ネットワーク監視: ホストマシンのすべてのネットワークトラフィックをスニッフィングする必要がある監視ツール(例:侵入検知システム NIDS)は、
hostモードを使用することでbridgeによるパフォーマンスのオーバーヘッドを回避できます。 - カスタム DNS サーバーの実行: DNS は標準の 53 番ポートで直接応答する必要があります。隔離性を犠牲にはしますが、
hostモードを使用することで設定を簡素化できます。
docker run -d --name my-dns-server --network host my/dns-image5. デフォルト Bridge ネットワーク上のコンテナ間通信
ポートマッピングは「外部から内部へどのようにアクセスするか」という問題を解決しますが、実際の開発では、コンテナ同士が相互に通信する必要がよくあります。コンテナがすべてデフォルトの bridge ネットワーク上にある場合、Docker はそれらに相互接続のメカニズムを提供します。
5.1 コンテナ名を介した通信
Docker のデフォルトの bridge ネットワークには DNS サービスが組み込まれています。同じネットワーク上のコンテナは、コンテナ名を介して互いの IP アドレスを解決 (Resolve) できます。これは、コード内でいつ変更されるかわからない内部 IP をハードコーディング (Hardcode) する必要がないことを意味します。
実戦シナリオ:Web アプリケーションがデータベースに接続する
1. まず、PostgreSQL データベースコンテナを実行します:
docker run -d --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword postgres注意:-p を使用していないため、そのデフォルトポート 5432 はホストマシンにエクスポーズされていません。
2. 次に、データベースに接続する必要がある Web アプリケーションコンテナを実行します:
docker run -d --name my-webapp --link my-postgres:db -p 8000:80 my/webapp-image注意:--link はコンテナ間の相互接続のためのレガシーなパラメータです。現代の Docker デプロイメントでは、カスタムネットワーク(モジュール 4 で解説)や Docker Compose (モジュール 5 で解説) の使用をより推奨しています。しかしここでは、「名前を介して他のコンテナに接続する」という概念をうまく示しています。
この例では、Web アプリケーションはエイリアス (Alias) db または元の名前 my-postgres をホスト名として使用してデータベースに接続できます。例えば、接続文字列 (Connection String) を db:5432 と記述できます。データベースのポートを外部にエクスポーズする必要はなく、Web アプリケーションは依然としてスムーズにデータベースにアクセスできます。
5.2 内部サービスはポートマッピングが不要
ベストプラクティス: 本当に外部アクセスが必要なポートのみをエクスポーズしてください。
あるサービスが環境内の他のコンテナからのみ使用される場合(Redis キャッシュ (Cache) など)、絶対にそれをホストマシンにマッピングしないでください。これにより、攻撃されるリスクを大幅に軽減できます。
docker run -d --name my-redis redis上記の Redis コンテナは、同じ bridge 上の Web コンテナからのアクセスのみを許可し、ホストマシンや外部ネットワークからは完全に不可視であり、セキュリティが最大限に確保されています。
6. ポートエクスポーズとネットワークの実戦デモンストレーション
今学んだ知識を定着させるために、いくつかの実戦デモ(Demo)を実行してみましょう。
Demo 1:基本的な Nginx Web サーバーのデプロイ
Nginx コンテナを実行し、ホストマシンからアクセスできるようにします。
1. 実行とポートのマッピング:
docker run -d --name my-webserver -p 8080:80 nginx2. ポートマッピングの検証:
docker psPORTS の列に、0.0.0.0:8080->80/tcp と表示されているのが確認できます。
3. サービスへのアクセス:ブラウザを開いて http://localhost:8080 にアクセスし、Nginx ページを確認します。
4. 環境のクリーンアップ:
docker stop my-webserver
docker rm my-webserverDemo 2:シンプルな Python Flask API の実行
このデモではカスタムコードを使用して、非標準ポートをどのようにエクスポーズするかを示します。
1. Flask アプリケーションのコードを作成します (app.py):
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello from Dockerized Flask API!'
if __name__ == '__main__':
# Flask はデフォルトで 5000 番ポートで実行されます
# host='0.0.0.0' はコンテナ内部で極めて重要です。これは Flask に対して、
# 利用可能なすべてのネットワークインターフェースをリスンするように指示し、
# アプリケーションがコンテナの外部からアクセスできるようにします。
app.run(host='0.0.0.0', port=5000)2. 対応する Dockerfile を作成します:
# Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 5000 # このアプリケーションが 5000 番ポートを使用することを宣言する
CMD ["python", "app.py"]3. 依存関係ファイルを作成します (requirements.txt):
Flask4. ビルドと実行:
docker build -t my-flask-app .
docker run -d --name flask-api -p 5000:5000 my-flask-app5. アクセスの検証:http://localhost:5000 にアクセスするか、curl http://localhost:5000 を使用すると、「Hello from Dockerized Flask API!」が表示されるはずです。
6. 環境のクリーンアップ:docker stop flask-api および docker rm flask-api。
Demo 3:内部データベース接続(外部ポートをエクスポーズしない)
ポートをエクスポーズすることなく、コンテナがデータベースコンテナに接続する方法を示します。
1. 完全な内部用の PostgreSQL を実行します:
docker run -d --name my-internal-db -e POSTGRES_PASSWORD=dbpassword postgres(注意:-p パラメータは使用していません)
2. それに接続するためのクライアントコンテナを実行します:
docker run --rm --link my-internal-db:db-alias postgres psql -h db-alias -U postgres -c "SELECT 1;"--rm: 実行終了後、クライアントコンテナを自動的に削除します。--link: ターゲットデータベースをリンクし、エイリアスdb-aliasを与えます。psql -h db-alias ...: クライアント内部で接続コマンドを実行し、ホスト名には直接エイリアスdb-aliasを使用します。
接続に成功すると、ターミナルにはクエリ結果 1 が出力されます。もしホストマシン上で直接 5432 番ポート経由で接続しようと試みると、接続拒否がプロンプトされます。