【第6回】Dockerネットワーク|種類・コマンド・Rocky Linux firewalld対策

「コンテナ名でpingが届かない」「-p でポートを公開したのに外部から見えない、または逆に意図せず見えてしまう」——Dockerネットワークに関するトラブルは、仕組みを理解していないと原因の見当もつきません。

この記事では、Dockerネットワークドライバの種類から、コンテナ間通信のDNS解決の仕組み、docker network コマンドの実践的な使い方、Docker Composeのネットワーク定義パターン、そしてRocky Linux 9環境特有のfirewalld干渉問題まで体系的に解説します。

この記事で分かること
  • bridge / host / none の違いと、カスタムbridgeネットワークの作成・運用方法
  • コンテナ名でDNS解決できる仕組みと、デフォルトbridgeで使えない理由
  • Rocky Linux 9 での firewalld と Docker の干渉問題と DOCKER-USER チェーンによる安全な制御
目次

Dockerネットワークの全体像 — 4種類のドライバ早見表

Dockerコンテナは起動時にいずれかのネットワークドライバに接続されます。どのドライバを選ぶかで、コンテナ間通信の方法やホストネットワークとの関係が大きく変わります。

ドライバ主な用途コンテナ名DNS特徴
bridge(デフォルト)単一ホスト・開発用途無効コンテナ名での名前解決不可。IPアドレス直接指定のみ
bridge(カスタム)単一ホスト・推奨有効コンテナ名・サービス名で通信可。本番環境にも使用可
host高スループット・特殊用途不要ホストのネットワーク名前空間を共有。ポートマッピング不要
none完全隔離・バッチ処理なしループバック(lo)のみ。外部通信を完全遮断
overlayDocker Swarm・マルチホスト有効複数ホスト間でコンテナを通信させる(Swarm専用)
macvlanレガシーシステム連携N/AコンテナにMACアドレスを直接割り当て

日常的に使うのは bridge(カスタム) がほとんどです。以降はこの4種類を順番に解説します。

bridge — 同一ホスト内の標準ネットワーク

bridgeドライバは、Dockerのデフォルトネットワークです。docker run でネットワークを指定しない場合、コンテナは自動的に bridge(docker0)に接続されます。ホストには仮想ブリッジ docker0(デフォルトサブネット: 172.17.0.0/16)が作成され、コンテナはそのサブネット内のIPアドレスを取得します。

重要な制限: デフォルトの bridge ネットワークでは、コンテナ名によるDNS解決が無効です。別のコンテナと通信するには、docker network inspect でIPアドレスを調べて直接指定する必要があります。後述のカスタムbridgeで解決できます。

host — ホストのネットワーク空間を共有

hostドライバを使うと、コンテナはホストのネットワーク名前空間をそのまま使用します。コンテナ内のプロセスがポート80でListenすれば、ホストの80番ポートに直接バインドされます。

# ポートマッピング不要で起動できる
docker run --rm --network host nginx

# ホストの80番ポートで直接アクセス可能
curl http://localhost

コンテナとホストがポート空間を共有するため、ポートの競合リスクがあります。ネットワークの隔離もなくなるため、セキュリティ要件が厳しい環境では使用を避けてください。

none — 完全ネットワーク遮断

noneドライバを使うと、コンテナはループバックインターフェース(lo)のみを持ち、外部との通信が完全に遮断されます。バッチ処理(ファイル変換・データ処理など)やセキュリティ上ネットワーク接続を禁止したい処理に使います。

docker run --rm --network none alpine ip addr
# lo のみ表示される
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 ...
#     inet 127.0.0.1/8 scope host lo

overlay — Docker Swarm 向けマルチホストネットワーク

overlayドライバは Docker Swarm クラスタ専用です。複数のDockerホストにまたがるネットワークを構成し、別ホストのコンテナとも同一ネットワーク内のように通信できます。単独のDockerホスト環境では使用しません。

デフォルトbridgeの落とし穴とカスタムbridgeの作り方

デフォルトbridgeではコンテナ名のDNS解決ができない

最もよくある落とし穴を実際に確認してみます。

# コンテナAを起動
docker run -d --name containerA alpine sleep 3600

# コンテナBからコンテナAにpingしてみる
docker run --rm alpine ping -c 2 containerA
# ping: bad address 'containerA'  ← 名前解決できない!

# IPアドレスで指定すれば通信できる
docker inspect containerA --format '{{.NetworkSettings.IPAddress}}'
# 172.17.0.2

docker run --rm alpine ping -c 2 172.17.0.2
# PING 172.17.0.2 (172.17.0.2): 56 data bytes
# 64 bytes from 172.17.0.2: ...  ← IPなら届く

デフォルトの bridge ネットワークは Docker 組み込みDNSサーバーが機能しないため、コンテナ名での名前解決ができません。カスタムbridgeネットワークを使うことで解決できます。

カスタムbridgeネットワークの作成と接続

STEP
カスタムネットワークを作成する
docker network create mynetwork
STEP
カスタムネットワーク上でコンテナを起動する
docker run -d --name containerA --network mynetwork alpine sleep 3600
STEP
コンテナ名でpingが届くか確認する
docker run --rm --network mynetwork alpine ping -c 2 containerA
# PING containerA (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.137 ms
# ← コンテナ名で解決できた!

カスタムbridgeネットワークのメリットをデフォルトbridgeと比較します。

機能デフォルトbridgeカスタムbridge
コンテナ名でのDNS解決不可可能
コンテナ間の分離全コンテナが同一ネットネットワーク単位で分離
実行中コンテナへの追加接続不可docker network connect で可能
ネットワークスコープの分離なしプロジェクトごとに分離できる

カスタムbridgeネットワークは作成コストがほぼゼロです。docker run で複数のコンテナを扱うときは、常にカスタムネットワークを作成してから使う習慣をつけましょう。

サブネット・ゲートウェイを固定指定する

IPアドレス範囲を明示的に指定したい場合(他のネットワークとの競合回避・固定IP運用など)は --subnet--gateway オプションを使います。

# サブネットとゲートウェイを明示して作成
docker network create \
  --driver bridge \
  --subnet 192.168.100.0/24 \
  --gateway 192.168.100.1 \
  myapp-network

# 固定IPを指定してコンテナを起動
docker run -d \
  --name webserver \
  --network myapp-network \
  --ip 192.168.100.10 \
  nginx

# 確認
docker inspect webserver --format '{{.NetworkSettings.Networks.myapp-network.IPAddress}}'
# 192.168.100.10

docker networkコマンド実践リファレンス

docker network ls / create / rm / prune

# ネットワーク一覧を表示
docker network ls
# NETWORK ID     NAME        DRIVER    SCOPE
# a1b2c3d4e5f6   bridge      bridge    local
# b2c3d4e5f6a1   host        host      local
# c3d4e5f6a1b2   none        null      local
# d4e5f6a1b2c3   mynetwork   bridge    local

# ネットワーク作成
docker network create --driver bridge mynetwork

# ネットワーク削除(接続中のコンテナがない場合)
docker network rm mynetwork

# 未使用のネットワークをまとめて削除
docker network prune
# WARNING! This will remove all custom networks not used by at least one container.
# Are you sure you want to continue? [y/N]

docker network inspect の使い方

# ネットワークの詳細情報を表示(JSON形式)
docker network inspect mynetwork

# 出力例(抜粋)
# [
#   {
#     "Name": "mynetwork",
#     "Driver": "bridge",
#     "IPAM": {
#       "Config": [
#         { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" }
#       ]
#     },
#     "Containers": {
#       "abc123...": {
#         "Name": "containerA",
#         "IPv4Address": "172.18.0.2/16"
#       }
#     }
#   }
# ]

–format オプションと jq で出力を加工する

JSON全体が長い場合、--format テンプレートや jq を使って必要な情報だけ抽出できます。

# 接続中のコンテナ名とIPアドレスを一覧表示
docker network inspect mynetwork \
  --format '{{range .Containers}}{{.Name}}: {{.IPv4Address}}{{"\n"}}{{end}}'
# containerA: 172.18.0.2/16
# containerB: 172.18.0.3/16

# jq でコンテナ名とIPアドレスをテーブル形式で表示
docker network inspect mynetwork | \
  jq -r '.[0].Containers | to_entries[] | "\(.value.Name)\t\(.value.IPv4Address)"'

# サブネット情報を取得
docker network inspect mynetwork \
  --format '{{range .IPAM.Config}}Subnet: {{.Subnet}} / Gateway: {{.Gateway}}{{"\n"}}{{end}}'
# Subnet: 172.18.0.0/16 / Gateway: 172.18.0.1

docker network connect / disconnect

実行中のコンテナをネットワークに後から接続・切断できます。コンテナを再起動せずにネットワーク構成を変更できる便利な機能です。

# 実行中のコンテナを別のネットワークに追加接続
docker network connect mynetwork containerA

# コンテナをネットワークから切断
docker network disconnect mynetwork containerA

# 接続確認(コンテナが複数のネットワークに属していることを確認)
docker inspect containerA \
  --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: {{$v.IPAddress}}{{"\n"}}{{end}}'
# bridge: 172.17.0.2
# mynetwork: 172.18.0.2

コンテナ名でつながる仕組み — Docker内蔵DNSサーバとは

/etc/resolv.conf の中身を確認する

カスタムbridgeネットワークに接続したコンテナの /etc/resolv.conf を確認してみます。

# カスタムネットワーク上のコンテナで確認
docker run --rm --network mynetwork alpine cat /etc/resolv.conf
# search mynetwork
# nameserver 127.0.0.11    ← Docker内蔵DNSサーバー
# options ndots:0

# デフォルトbridgeのコンテナで確認
docker run --rm alpine cat /etc/resolv.conf
# nameserver 192.168.1.1   ← ホストのDNSサーバーが設定される(127.0.0.11ではない)

カスタムネットワーク上のコンテナには nameserver 127.0.0.11 が設定されます。これが Docker 内蔵 DNS サーバーのIPアドレスです。コンテナ名とIPアドレスのマッピングを動的に管理しています。

DNSサーバー 127.0.0.11 の正体

127.0.0.11 は Docker デーモンが管理する組み込みDNSサーバーです。Dockerは iptablesREDIRECT ルールを使って、コンテナから 127.0.0.11:53 へのDNSクエリをDockerデーモンのDNSハンドラーにリダイレクトします。

# dig でコンテナ名のDNS解決を確認
docker run --rm --network mynetwork alpine sh -c \
  "apk add --no-cache bind-tools -q && dig @127.0.0.11 containerA"
# ;; ANSWER SECTION:
# containerA.    600  IN  A  172.18.0.2
# ← コンテナ名が正しくAレコードとして返ってくる

Docker の内蔵DNSはコンテナの起動・停止に連動してAレコードを動的に追加・削除します。Docker Composeでコンテナを再起動してIPアドレスが変わっても、サービス名での通信が自動的に継続するのはこの仕組みのおかげです。

ポートマッピング(-p オプション)の仕組みとセキュリティ注意点

-p の内部動作 — iptables への自動ルール追加

docker run -p 8080:80 を実行すると、Dockerは iptablesDOCKER チェーンに転送ルールを自動的に追加します。

# -p 8080:80 で起動後、iptablesルールを確認
docker run -d -p 8080:80 --name web nginx

sudo iptables -t nat -L DOCKER --line-numbers -n
# Chain DOCKER (2 references)
# num  target  prot  opt  in       out  source     destination
# 2    DNAT    tcp   --   !docker0 *    0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.17.0.2:80
# ↑ 外部からポート8080へのアクセスをコンテナの172.17.0.2:80に転送するルールが追加された

全インターフェース公開と localhost 限定公開の違い

-p オプションのバインドアドレスを意識していないと、意図せず全ネットワークインターフェースに公開してしまいます。

# 全インターフェースに公開(デフォルト)— 外部からアクセス可能
docker run -d -p 8080:80 nginx

# localhostのみに公開 — 外部からアクセス不可、サーバー内からのみ
docker run -d -p 127.0.0.1:8080:80 nginx

# 確認
ss -tlnp | grep 8080
# 0.0.0.0:8080   → 全インターフェース公開
# 127.0.0.1:8080 → localhost のみ

本番環境では、アプリケーションの前段にNginxなどのリバースプロキシを配置し、Dockerコンテナは 127.0.0.1:PORT でバインドする構成が安全です。外部公開はNginxだけに絞れます。

DOCKER-USER チェーンでアクセス制限する

Dockerは iptablesDOCKER チェーンを自動管理するため、このチェーンに手動でルールを追加してもDockerが上書きしてしまいます。アクセス制限のカスタムルールは DOCKER-USER チェーンに追加します。

# 社内ネットワーク(192.168.1.0/24)からのみアクセスを許可
# それ以外はすべてDROP
sudo iptables -I DOCKER-USER -i eth0 \
  ! -s 192.168.1.0/24 \
  -j DROP

# ルールを確認
sudo iptables -L DOCKER-USER -n --line-numbers
# Chain DOCKER-USER (1 references)
# num  target  prot  opt  in    out  source            destination
# 1    DROP    all   --   eth0  *    !192.168.1.0/24   0.0.0.0/0

# ルールを永続化(Rocky Linux 9)
sudo iptables-save | sudo tee /etc/sysconfig/iptables
sudo systemctl enable iptables

Docker Composeのネットワーク定義パターン

デフォルトネットワークの自動生成

Docker Composeは、docker compose up を実行すると自動的に プロジェクト名_default という名前のカスタムbridgeネットワークを作成します。すべてのサービスはこのネットワークに接続され、サービス名でDNS解決できます。

services:
  web:
    image: nginx
    ports:
      - "8080:80"
  app:
    image: myapp:1.0.0
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret

上記の設定では、webappdb がすべて同じデフォルトネットワークに参加します。app コンテナから db コンテナへは db:3306 でアクセスできます。

frontend / backend の2ネットワーク分離構成

セキュリティ要件に応じてネットワークを分離できます。以下は Web・アプリ・DBの3層構成でネットワークを分離する例です。

services:
  web:
    image: nginx
    ports:
      - "8080:80"
    networks:
      - frontend        # フロントエンドネットワークのみ

  app:
    image: myapp:1.0.0
    networks:
      - frontend        # webと通信できる
      - backend         # dbと通信できる

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    networks:
      - backend         # バックエンドネットワークのみ(webからは直接アクセス不可)

networks:
  frontend:
  backend:

この構成では web(Nginx)から db(MySQL)に直接アクセスできません。app のみが両ネットワークに接続されているため、DBへのアクセスは必ずアプリケーションを経由します。セキュリティの観点から推奨される構成です。

db を外部から完全遮断する internal: true

internal: true を設定したネットワークは、ホスト外部(インターネット)との通信が完全に遮断されます。DBサーバーなど、絶対に外部から通信させたくないコンテナを配置するのに適しています。

services:
  app:
    image: myapp:1.0.0
    networks:
      - frontend
      - internal-net

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    networks:
      - internal-net    # internal ネットワークのみ

networks:
  frontend:
  internal-net:
    internal: true      # ← このネットワークのコンテナはインターネットに出られない

external: true で別 Compose プロジェクトと通信する

複数のdocker-composeプロジェクト間でコンテナを通信させたい場合、共有ネットワークを external: true で参照します。

# まず共有ネットワークを手動で作成
docker network create shared-network
# プロジェクトA の compose.yaml
services:
  api:
    image: myapi:1.0.0
    networks:
      - shared-network

networks:
  shared-network:
    external: true    # 既存のネットワークを参照(作成しない)
# プロジェクトB の compose.yaml
services:
  frontend:
    image: myfrontend:1.0.0
    networks:
      - shared-network

networks:
  shared-network:
    external: true    # 同じ既存ネットワークを参照

【Rocky Linux 9対応】firewalldとDockerの共存設定

Dockerが自動作成するfirewalldゾーン

Rocky Linux 9 など firewalld が有効な環境でDockerを起動すると、Dockerは自動的に docker という名前のfirewalldゾーンを作成し、docker0 インターフェースをそのゾーンに追加します。

# firewalld のゾーン一覧を確認
sudo firewall-cmd --get-zones
# block dmz drop external home internal public trusted work docker
#                                                              ↑ Dockerが自動作成

# docker ゾーンの設定を確認
sudo firewall-cmd --zone=docker --list-all
# docker (active)
#   target: ACCEPT
#   interfaces: docker0

-p でポートを公開すると firewalld をバイパスする問題

Rocky Linux 9 で最も注意が必要なのが、docker run -p によるポート公開が firewalld のルールを無視してしまう問題です。

# firewall-cmd でポート8080を閉じる
sudo firewall-cmd --zone=public --remove-port=8080/tcp

# しかし docker run -p で公開すると...
docker run -d -p 8080:80 nginx

# 外部から確認すると(別ホストから)
curl http://<サーバーIP>:8080
# <h1>Welcome to nginx!</h1>  ← firewall-cmd で閉じているのに届いてしまう!

Dockerは iptablesDOCKER チェーンにポート転送ルールを書き込みますが、このルールは firewalld のルールよりも先に評価されます。結果として、firewalld でポートを閉じていても Docker のiptablesルールが優先されてしまいます。

DOCKER-USER チェーンへのルール追加で安全に制御する

Dockerが管理する DOCKER チェーンにルールを追加するとDockerの再起動時に削除されますが、DOCKER-USER チェーンに追加したルールは保持されます。

# DOCKER-USER チェーンの現在のルールを確認
sudo iptables -L DOCKER-USER --line-numbers -n

# 社内ネットワーク(192.168.1.0/24)からのみアクセスを許可
sudo iptables -I DOCKER-USER -i eth0 \
  ! -s 192.168.1.0/24 \
  -j DROP

# ルールを永続化(Rocky Linux 9)
sudo iptables-save | sudo tee /etc/sysconfig/iptables
sudo systemctl enable iptables

firewalld と Docker の共存に関するよくある問題と対処法をまとめます。

症状原因対処法
firewall-cmd でポートを閉じても Docker コンテナに届くDockerのiptablesルールがfirewalldをBYPASSDOCKER-USER チェーンにDROPルールを追加
Docker 起動後に firewalld のルールが効かなくなったfirewalld が iptables を管理していないiptables-legacy を使用するか firewalld バックエンドを確認
コンテナが外部に通信できない(DNS解決不可)firewalld が docker0 のフォワーディングをブロックdocker ゾーンの設定を確認、masquerade を有効化
Docker 再起動後に DOCKER-USER のルールが消えるiptables-save で永続化していないiptables-save で保存、iptables.service を有効化

よくある質問(FAQ)

docker0 と カスタムbridgeは何が違うの?

最大の違いは DNS解決の有無 です。デフォルトの docker0 ではコンテナ名での名前解決ができず、IPアドレスを直接指定する必要があります。カスタムbridgeではコンテナ名・サービス名で通信でき、ネットワーク単位での分離も可能です。実運用では必ずカスタムbridgeを使いましょう。

Docker Composeのサービス名でコンテナに接続できない

まず docker compose ps でコンテナが起動しているか確認してください。次に docker network inspect <プロジェクト名>_default で両コンテナが同じネットワークに属しているか確認します。networks: を明示指定している場合、接続したいサービス同士が同じネットワーク名を参照しているか見直してください。

Rocky Linux 9 でfirewalldとDockerを両立させる最もシンプルな方法は?

アプリコンテナを 127.0.0.1:PORT:PORT でバインドし、Nginxリバースプロキシだけを外部公開する構成が最もシンプルです。この場合、firewalldで80/443のみを開けるだけで済み、DOCKER-USERチェーンの設定が不要になります。

docker network prune を実行したらどうなる?

現在どのコンテナにも接続されていないカスタムネットワークがすべて削除されます。bridgehostnone の組み込みネットワークは削除されません。実行前に docker network ls で削除対象を確認し、-f(–force)オプションを使えば確認プロンプトをスキップできます。

まとめ

Dockerネットワークの重要ポイントをまとめます。

  • デフォルトbridgeは使わない:コンテナ名でのDNS解決が使えないため、docker network create でカスタムbridgeネットワークを作成してから使う
  • Docker ComposeはデフォルトでカスタムbridgeをDNS有効で作成する:サービス名で通信できるのはこの仕組みのおかげ
  • -p はfirewalldをバイパスする:Rocky Linux 9 では DOCKER-USER チェーンにアクセス制限ルールを追加するか、127.0.0.1 バインドにしてリバースプロキシ経由で公開する
  • DB等はinternalネットワークに閉じ込めるinternal: true で外部通信を完全遮断
  • docker network inspect を活用する:トラブル時は --formatjq で必要な情報だけ抽出する

次回の Docker 連載では、本番環境でのコンテナ運用における Docker のセキュリティ設定(非rootユーザー、read-only filesystem、capabilities の制限など)を解説します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次