実行中のDockerコンテナに動的にポートマッピングを追加する

実行中のDockerコンテナに動的にポートマッピングを追加する

簡単な回答: できません。Dockerでは、すでに実行中のコンテナに公開ポートを追加する方法は公式にはサポートされていません。ポートマッピングはコンテナ作成時に固定され、docker rundocker container updateでは後から変更できません。--publish-addフラグはSwarmサービス専用であり、スタンドアロンコンテナでは使用できません。

コンテナを再起動できない場合、実際に機能するオプションは次のとおりです。

アプローチ 実際のDockerポートマッピングを追加するか? Docker Desktop(macOS/Windows)で動作するか? 再起動後も持続するか?
socatサイドカーコンテナ いいえ(TCPフォワード) はい はい(サイドカーが再起動すれば)
リバースプロキシ(Nginx/Traefik/HAProxy) いいえ(プロキシ経由) はい はい
ホストのiptables DNAT いいえ(ホストNATルール) いいえ(ネイティブLinuxのみ) いいえ(永続化しない限り)
コンテナを-pで再作成 はい はい はい

Dockerが管理する本当の公開ポート(docker psdocker portに表示されるもの)を生成する唯一の方法は、コンテナを再作成することです。その他の方法は、Dockerのポート管理レイヤーの上または下でトラフィックを転送します。再起動が許容できるかどうかに基づいて選択し、本番環境で実行する前に以下の注意事項を読んでください。

この記事では、各方法、実際のコマンド、およびそれぞれの限界について説明します。

背景: Dockerのポートマッピングの仕組み

コンテナポートマッピングの基本原則

Dockerでは、コンテナの内部ポートとホストマシンのポートとの接続は、ポートマッピングによって実現されます。通常、コンテナ起動時に-pまたは--publishパラメータを使用してポートマッピングを指定します。次に例を示します。

docker run -d -p 8080:80 nginx

上記のコマンドは、ホストマシンのポート8080をコンテナ内のポート80にマッピングします。これにより、外部ユーザーはホストのポート8080を介してコンテナ内で実行されているWebサービスにアクセスできます。

Dockerがこれを許可しない理由

コンテナが起動した後、Dockerは通常、新しいポートマッピングを動的に追加することをサポートしていません。つまり、初期のポートマッピングはコンテナのライフサイクル全体にわたって固定されたままになります。より多くのポートマッピングを追加する必要がある場合、従来の方法ではコンテナを停止して再起動する必要があり、サービスが中断され、本番環境では許容できません。

4つの回避策

実行中のコンテナにポートマッピングを動的に追加するには、いくつかの方法があります。

2.1 ポートを転送するサイドカーコンテナ(推奨)

別のコンテナが新しいホストポートを公開し、共有Dockerネットワークを介して元のコンテナにトラフィックを転送します。元のコンテナに触れないため、これが最も安全なオプションです。

まず重要な修正: --network container:<name>-pを組み合わせることはできません。Dockerのネットワークドキュメントによると、container:ネットワークモードを使用するコンテナでは、--publish--publish-all--exposeサポートされていません。これは、そのようなコンテナにはポートをマッピングするための独自のネットワーク名前空間がないためです。docker run -p 8081:81 --net container:your-container ...と指示するガイドは誤りであり、Dockerはそれを拒否します。

正しいパターンは、ユーザー定義ネットワークを使用して、サイドカーがコンテナ名でターゲットに到達できるようにすることです。

# 1. ネットワークを作成し、実行中のコンテナをそれに接続する(再起動不要)
docker network create app-net
docker network connect app-net your-container

# 2. 8081を公開し、ターゲットのポート81に転送するsocatサイドカーを起動
docker run -d --name port-sidecar \
  --network app-net \
  --restart unless-stopped \
  -p 8081:81 \
  alpine/socat \
  TCP-LISTEN:81,fork,reuseaddr TCP:your-container:81

docker network connectは実行中のコンテナに対して機能するため、ステップ1ではダウンタイムが発生しません。サイドカーは自身の名前空間内のポート81でリッスンし、-p 8081:81でそれがホストに公開されます。

注意事項:

  • これはTCPフォワードであり、Dockerポートマッピングではありません。docker port your-containerには表示されません。
  • alpine/socatはTCPのみを転送します。UDPの場合はUDP-LISTEN/UDPを使用してください。HTTPでホストベースのルーティングが必要な場合は、Nginx、Traefik、Caddy、HAProxyを推奨します。
  • --restart unless-stopped(上記のように)を追加しないと、再起動時にフォワードが消えます。
  • 追加のホップにより、わずかなレイテンシと監視対象のプロセスが1つ増えます。

2.2 ホストのiptables DNATルール(ネイティブLinuxのみ)

ネイティブLinuxホストでは、ホストポートをコンテナの内部IPに転送するDNATルールを追加できます。

# コンテナIPを取得
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' your-container)

# ホストポート8081をコンテナポート81に転送
sudo iptables -t nat -A DOCKER -p tcp --dport 8081 \
  -j DNAT --to-destination "${CONTAINER_IP}:81"

これにより細かい制御が可能になりますが、ここに挙げるすべての方法の中で最も運用リスクが伴います。

  • macOSおよびWindows上のDocker Desktopではこの方法は機能しません。 コンテナはLinux VM内で実行されるため、マシン上のiptablesルールはDockerのネットワークパスに影響しません。この方法はネイティブLinuxのみです。
  • ルールは永続化しません。 再起動、ファイアウォールのリロード、またはnftables/iptablesの移行時に失われます。ホスト再起動後も保持する必要がある場合は、ディストリビューションのファイアウォール永続化メカニズムを使用してください。
  • DockerはDOCKERチェーンを所有しています。 Dockerは実行中のコンテナのポート設定からこれらのルールを作成および管理しており、ドキュメントではDockerが作成したルールを変更しないように指示しています。カスタムフィルタリングのために、DockerはDOCKER-USERをユーザー定義ルールのプレースホルダーとして指定しています。これは、FORWARDに追加されたルールはDocker自身のルールの後に処理されるためです。
  • コンテナIPは安定していません。 コンテナが再作成されるたびにアドレスが変更され、古いルールが黙ってどこにも転送されない状態になります。
  • Dockerの管理をバイパスします。 ポートはdocker psdocker portに表示されません。

2.3 ホスト上でsocatを直接実行する

socatをコンテナ内ではなく、プレーンなホストプロセスとして実行することもできます。

socat TCP-LISTEN:8081,fork,reuseaddr TCP:<container_ip>:81

これは、コンテナIPがホストからルーティング可能なネイティブLinuxで機能します。macOSおよびWindows上のDocker DesktopではコンテナIPはマシンから 到達できない ため、代わりに2.1節のサイドカーを使用してください。いずれにしても、再起動後も持続させるにはプロセススーパーバイザー(systemd、またはサイドカーの--restart)が必要です。生のsocatプロセスはシェルとともに終了するためです。

2.4 Docker Composeでサービスを再作成する

これは、本当のDocker管理の公開ポートを生成する唯一の方法です。マッピングをcompose.yamlに追加します。

services:
  app:
    image: your-image:tag
    ports:
      - "8081:81"

次に、そのサービスだけを再作成します。

docker compose up -d app

Composeはコンテナを再作成するため、短い中断が発生します。これはライブ変更ではありません。最新のDockerでは、古いスタンドアロンのdocker-composeバイナリではなくdocker compose(サブコマンド)を使用することに注意してください。名前付きボリュームまたはバインドマウントに状態を保持し、再作成後も持続するようにします。

2.5 Dockerの内部設定ファイルの編集(非推奨)

/var/lib/docker/containers/<id>/config.v2.jsonhostconfig.jsonを手動で編集してPortBindingsエントリを追加し、その後デーモンを再起動するというアドバイスを見かけることがあります。これが動作することもありますが、最後の手段として扱ってください。

  • これらは 内部実装ファイルであり、安定性の保証はなく、サポートされているAPIではありません。フォーマットはDockerリリース間で変更される可能性があります。
  • デーモンはコンテナ状態をメモリに保持しています。実行中のデーモン下でファイルを編集すると、変更が上書きされるリスクがあり、部分的な編集によってコンテナのネットワーク状態がメタデータと矛盾する可能性があります。
  • 編集する前にデーモンを停止する必要があり、ホスト上のすべてのコンテナに影響します。
  • live-restoreが有効な場合、コンテナはデーモン再起動後も実行を続けますが、編集されたポートマッピングは適用 されません。Live-restoreは、新しい公開ポートにはコンテナの再作成が必要であるというルールを変更しません。

デーモンの状態を手動で編集する段階に達したなら、正しい-pフラグでコンテナを再作成する方が迅速かつ安全です。

結論

Dockerは実行中のコンテナに公開ポートを追加することをサポートしておらず、どの回避策もそれを変えることはできません。上記の方法が提供するのは、再起動できないコンテナに新しいトラフィックをルーティングする手段です。

次の順序で選択してください。

  1. 短い再起動は許容できるか? 適切な-pフラグでコンテナを再作成するか、compose.yamlports:を追加してdocker compose up -dを実行します。これが本当のDockerポートマッピングを生成する唯一の方法です。
  2. 再起動できないか? 2.1節のsocatサイドカーを使用するか、HTTPルーティング、TLS、ヘルスチェックが必要な場合はリバースプロキシを使用します。
  3. ネイティブLinuxで迅速な一時的なフォワードが必要か? iptables DNATルールは機能しますが、意図的に永続化し、Dockerが自身のチェーンに干渉する可能性があることを理解しておいてください。
  4. デーモン設定ファイルの手動編集は避けてください。

ポートマッピングが頻繁に変更される場合、それは通常、設計上のシグナルです。最初からサービスの前にリバースプロキシを配置し、ホスト側のポートをプロキシに管理させることで、コンテナのライフサイクルとルーティングを独立させてください。

出典: Docker: ポートの公開, Docker: コンテナネットワークモード, Docker: パケットフィルタリングとファイアウォール, Dockerとiptables, docker container port.

GPUインスタンスとモデルAPIについてはNovita AIをご覧ください。