Docker Macvlanネットワーク構築:物理LANからコンテナへ固定IPを割り当てる方法

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

クイックスタート:5分でDocker Macvlanを作成し固定IPを割り当てる

Dockerのデフォルトbridgeネットワーク上でAsteriskやFreePBXなどのPBX(構内交換機)システムを動かすと、NATの影響で内線通話が片方向しか聞こえない、30秒で切断される、SIP SDPパケットが正しく認識されないといったトラブルが頻発します。また、スイッチ側の物理IP電話機からのブロードキャスト/マルチキャストパケットも、Dockerのbridgeネットワークへはルーティングできません。

最も手軽な解決策は、macvlanドライバを使用してコンテナを直接物理LANに参加させることです。これにより、コンテナはネットワーク上の他のデバイスと同等に、専用のMACアドレスを持つ独立した端末として動作します。

まず、サーバー上でルーターに接続されているネットワークインターフェースとIP設定を確認します。

ip -br addr show
# 出力例: eth0 UP 192.168.1.50/24
# 現在のゲートウェイ: 192.168.1.1
# ローカルネットワークのサブネット: 192.168.1.0/24

eth0インターフェースを親として、macvlanドライバを使用したDockerネットワークを作成します。

docker network create -d macvlan \
  --subnet=192.168.1.0/24 \
  --gateway=192.168.1.1 \
  --ip-range=192.168.1.200/28 \
  -o parent=eth0 \
  lan_macvlan

割り当てたIPレンジ内の固定IP 192.168.1.202 を指定してコンテナを起動します。

docker run -d \
  --name voip-service \
  --net=lan_macvlan \
  --ip=192.168.1.202 \
  --restart=always \
  nginx:alpine

同一LAN内の別のPCから接続テストを実施します。

ping -c 4 192.168.1.202
curl -I http://192.168.1.202

RTTが1ms未満ですぐに応答が返ってくるはずです。コンテナは独自のMACアドレスを持ち、中間のNAT層を経由することなくLayer 2上で直接トラフィックを送受信できます。

Docker Macvlanの仕組みと動作原理

デフォルトのbridgeモードでは、Dockerは仮想ブリッジdocker0を作成し、iptablesのMASQUERADEによってアドレス変換(NAT)を行います。外部のスイッチやルーターからは、ホストマシンのMACアドレスとIPアドレスしか見えません。この仕組みは手軽な反面、DHCPリレーやVoIP(SIP/RTP)、Home AssistantのmDNS、ONVIFカメラのストリーミングなど、ブロードキャストやマルチキャストに依存するプロトコルの通信が制限されてしまいます。

サブインターフェースの仮想化メカニズム

Macvlanはデータリンク層(Layer 2)でこの問題を解決します。ドライバはホストマシンの物理NIC(parent interface)に直接紐づく仮想サブインターフェースを作成します。各コンテナにはIEEE 802規格に準拠したランダムなMACアドレスが割り当てられます。

  • 独立したMACアドレス: スイッチからは、コンテナが物理ポートに直接有線接続された物理サーバーであるかのように認識されます。
  • NAT不要: コンテナ宛てのトラフィックを直接受信できます。ルーティング遅延が発生せず、Linuxカーネルのconntrackテーブルに依存することもありません。
  • ポートマッピングの排除: -p 80:80のようなポート転送設定が不要になります。同一ホスト上で複数のサービスが並行稼働していても、ポート競合のリスクを回避できます。

押さえておくべき主要ネットワークパラメータ

  1. --subnet:ルーター側のLANサブネットと完全に一致させる必要があります(例:192.168.1.0/24)。
  2. --gateway:デフォルトゲートウェイのIPアドレス(通常はルーターの192.168.1.1)。
  3. --ip-range:Dockerが割り当て可能なIP範囲を制限します。例えば192.168.1.200/28と指定すると16個のアドレス(.200~.215)のみが使用され、社内DHCPサーバーとのIP競合を防ぎます。
  4. -o parent:接続先の物理ネットワークインターフェース名(eth0、ens18、enp3s0など)。

Docker Composeでの構築とホスト・コンテナ間通信のトラブルシューティング

完全なdocker-compose.ymlファイル

現在のDocker Compose仕様では、非推奨となったversion:の記述は不要です。Macvlanを使用してPi-holeをデプロイする設定は以下のようになります。

services:
  dns-server:
    image: pihole/pihole:latest
    container_name: local-pihole
    environment:
      TZ: 'Asia/Ho_Chi_Minh'
      WEBPASSWORD: 'AdminSecurePassword123'
    networks:
      lan_net:
        ipv4_address: 192.168.1.205
    restart: unless-stopped

networks:
  lan_net:
    name: lan_macvlan
    driver: macvlan
    driver_opts:
      parent: eth0
    ipam:
      config:
        - subnet: 192.168.1.0/24
          ip_range: 192.168.1.200/28
          gateway: 192.168.1.1

トラブル解決:ホストマシンからコンテナへpingが通らない場合

構築後によく直面するのが、「LAN内の他のPCからはコンテナに正常アクセスできるのに、ホストマシン自身からping 192.168.1.205を実行すると100%パケットロスになる」という現象です。これは不具合ではなく、Linuxカーネルのセキュリティ仕様によるものです。カーネルはbridgeモードにおいて、親インターフェース(parent)とmacvlanの子サブインターフェース間の直接通信を意図的に遮断しています。

これを解決する確実な方法は、ホスト側にブリッジ役となるmacvlanインターフェース(shim interface)を作成することです。

# 1. ホスト上に仮想macvlanブリッジを作成
sudo ip link add macvlan-shim link eth0 type macvlan mode bridge

# 2. 未使用のIPをshimインターフェースに割り当て
sudo ip addr add 192.168.1.210/32 dev macvlan-shim

# 3. インターフェースを有効化
sudo ip link set macvlan-shim up

# 4. コンテナのIPレンジ全体をこのshim経由でルーティング
sudo ip route add 192.168.1.200/28 dev macvlan-shim

上記4つのコマンドを実行すれば、サービスを再起動することなくホストからコンテナへ即座にpingが通るようになります。

本番環境(プロダクション)運用における3つの重要ポイント

  • ハイパーバイザー側でプロミスキャスモードを有効化: ホストマシンがVMware ESXiやProxmox VE上のVMである場合、vSwitchやLinux Bridge側でPromiscuous Mode(無差別モード)、MAC Address Changes、Forged TransmitsをAccept(承諾)に設定する必要があります。無効のままだと、ハイパーバイザーによってコンテナの未知のMACアドレス宛てパケットがすべて破棄されます。
  • IPアドレス帯の綿密な設計: ルーターの自動割り当て範囲(DHCPプール)と重複しないよう注意してください。例えばルーターのDHCP範囲を192.168.1.10~192.168.1.150に設定し、192.168.1.200/28をDocker Macvlan専用として確保するのがベストプラクティスです。
  • 再起動時のshim設定の永続化: ip linkやip routeコマンドで設定した内容はサーバー再起動時に消えてしまいます。systemdサービス化するか、NetplanやNetworkManagerへ設定を組み込んで、OS起動時に自動適用されるようにしておきましょう。
Share: