Docker コンテナで IPv6 を利用する3つの構成パターン

執筆者 : 山下雅喜


1. はじめに

IPv4 アドレスが枯渇すると言われてから早20年以上経ちますが、IPv6 に対応したシステム構築はなかなか進んでいないように感じています。
企業の社内ネットワークが IPv6 化されていないために、IPv6 に対応した Web システムを構築しようとしても、社内からはその動作を確認できないということもあるでしょう。

とは言え、システムの IPv6 対応 (IPv4 とのデュアルスタック化) は進めないといけないでしょうし、新しく構築するシステムであれば後々のことを考えて初めから IPv6 対応をしておくべきでしょう。

そこで本記事では、Docker コンテナで IPv6 を利用する場合の、3つのネットワーク構成パターンを解説します。


2. 想定環境と基礎知識

想定するネットワーク環境

次の図に示すような IPv6 ネットワーク環境が既にあることを想定しています。

f:id:ymstmsys:20200616141314p:plain

  • ルーター配下に /64 の IPv6 ネットワークセグメントが存在する。
  • そのネットワークセグメントに Docker ホストや PC、サーバ、その他様々な機器が接続されている。
  • Router Advertisement や DHCPv6 など各機器のIPアドレスの設定方法は何でも構わない。
  • ルーターは任意のネットワーク (インターネット、社内ネットワークなど) にも接続されている。

Docker で IPv6 を利用するための基本設定

Docker のブリッジネットワークを利用する場合、コンテナに割り当てて良いIPアドレスの範囲を Docker に設定する必要があります。

Docker の標準のブリッジネットワークで IPv6 を利用するには、設定ファイル "/etc/docker/daemon.json" に次のような設定を記述します。

{
  "ipv6": true,
  "fixed-cidr-v6": "IPアドレスの範囲"
}

Docker エンジンは、ここで指定したIPアドレスの範囲の中から任意のIPアドレスを各コンテナに割り当てます。

なお、現在の Docker では、IPv6 をシングルスタックで利用することはできず、必ず IPv4 とのデュアルスタックでの構成となります。

3つのネットワーク構成パターン

​ コンテナに割り当てるIPアドレスの範囲として何を指定するかによって、ネットワーク構成を次の3つのパターンに分類できます。

  1. ユニークローカルユニキャストアドレスを割り当てて、Docker ホストで IP マスカレードする。
  2. グローバルユニキャストアドレスを割り当てて、適切にルーティングする。
  3. Docker ホストが所属するネットワークのサブネットを割り当てて、Docker ホストで NDP Proxy を行う。

以降では、これらパターンについて解説します。


3. パターン1 : ユニークローカルユニキャストアドレス + IPマスカレード

ユニークローカルユニキャストアドレスとは、IPv4 におけるプライベートアドレス ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16") のように利用できる IPv6 アドレスです。
ユニークローカルユニキャストアドレスは "fd00::/8" の範囲内で、9~48ビット目をランダムに生成したネットワークアドレスを自由に利用できます。

このパターンでは、Docker には例えば次のように設定します。

{
  "ipv6": true,
  "fixed-cidr-v6": "fd1e:490a:42f0::/64"
}

これにより、次の図のようなネットワークが構成されます。

f:id:ymstmsys:20200616141406p:plain

  • Docker ホストは、指定したネットワークアドレスの1番のIPアドレス ("fd1e:490a:42f0::1") を持つ。
  • 各コンテナは、指定したネットワークアドレスの中の何らかのIPアドレスを持つ。
  • Docker ホストは、ホストネットワーク ("2001:db8::/64") とコンテナネットワーク ("fd1e:490a:42f0::/64") との間でルーティングを行う。

しかし、これだけでは、コンテナは Docker ホストや他のコンテナと通信は行えるものの、それ以外の機器とは通信を行えません。
その理由は、ルーターやPCなどの機器はコンテナネットワークに対する経路を持っていないためです。

実際、コンテナからルーターに ping を送信すると、ルーターに ICMPv6 パケットは到達するものの、応答をコンテナに返せないという事象が発生します。

そこで、ユニークローカルユニキャストアドレスを用いる場合は、Docker ホストで次のようなIPマスカレードの設定も行うと良いです。

ip6tables -t nat -A POSTROUTING -s fd1e:490a:42f0::/64 ! -o docker0 -j MASQUERADE

これにより、コンテナから Docker ホストの外に通信を行う場合は、Docker ホストにて送信元IPアドレスが "2001:db8::2" に変換され、ルーター、PC、サーバ、その他の任意のネットワークと通信を行えるようになります。

ただし、Docker ホストの外からコンテナのIPアドレスに直接アクセスすることはできません。
"docker run" コマンドでコンテナを作成する際に "--publish" オプションを付けることで、Docker ホストのIPアドレスのポート経由でアクセスできますが、Docker ホストとコンテナの間の通信は IPv4 で行われます。

なお、ユニークローカルユニキャストアドレスを用いるこのパターンは、IPv4 における Docker の標準のネットワークとほぼ同じ構成であるため、Docker に馴染みのある人にはわかりやすい構成かもしれません。


4. パターン2 : グローバルユニキャストアドレス + ルーティング

グローバルユニキャストアドレスとは、IPv4 におけるグローバルアドレスのことです。
IPv6 においては、ユニークローカルユニキャストアドレスを利用せず、グローバルユニキャストアドレスを用いてネットワークを構成することが一般的です。

このパターンでは、Docker には例えば次のように設定します。

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:0:1::/64"
}

これにより、次の図のようなネットワークが構成されます。

f:id:ymstmsys:20200616141849p:plain

  • Docker ホストは、指定したネットワークアドレスの1番のIPアドレス ("2001:db8:0:1::1") を持つ。
  • 各コンテナは、指定したネットワークアドレスの中の何らかのIPアドレスを持つ。
  • Docker ホストは、ホストネットワーク ("2001:db8::/64") とコンテナネットワーク ("2001:db8:0:1::/64") との間でルーティングを行う。

しかし、パターン1と同様に、ルーターやPCなどの機器はコンテナネットワークに対する経路を持っていないため、コンテナはそれらと通信を行えません。

そこで、今回のパターンではルーターに次のような静的経路を追加しましょう。

  • 宛先ネットワーク : 2001:db8:0:1::/64
  • ゲートウェイ : 2001:db8::2 (Docker ホストのIPアドレス)
  • ゾーン : ホストネットワークに接続されたNIC

これにより、コンテナとルーターやPCなどの様々な機器は相互に通信できるようになります。
コンテナがルーターの先にある任意のネットワークと通信する場合は、そのネットワーク側にも何らかの方法で経路を設定する必要があります。

このパターンは、パターン1と異なり NAT が行われないため、コンテナネットワークの IPv6 アドレスのまま通信できます。


5. パターン3 : ホストネットワークのサブネット + NDP Proxy

パターン2のようにコンテナにグローバルユニキャストアドレスを割り当てて利用したくても、コンテナ用のネットワークアドレスを用意できない場合や、ルーターに静的経路を追加できない場合なども考えられます。

この場合、Docker ホストが接続されたネットワーク ("2001:db8::/64") からサブネットを切り出して、コンテナで利用するという方法があります。

このパターンでは、Docker には例えば次のように設定します。

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:0:0:a::/80"
}

これにより、次の図のようなネットワークが構成されます。

f:id:ymstmsys:20200616141852p:plain

  • Docker ホストは、指定したサブネットの1番のIPアドレス ("2001:db8:0:0:a::1") を持つ。
  • 各コンテナは、指定したサブネットの中の何らかのIPアドレスを持つ。
  • Docker ホストは、ホストネットワーク ("2001:db8::/64") とサブネット ("2001:db8:0:0:a::/80") との間でルーティングを行う。

やはりこれだけでは、コンテナは Docker ホストや他のコンテナと通信は行えるものの、それ以外の機器とは通信を行えません。
ただし、その原因はパターン1やパターン2と異なり、ルーターやPCなどの機器がコンテナの MAC アドレスを取得できないことが原因です。

コンテナから見ると、ルーターやPCなどの機器は別ネットワークのIPアドレスであるため、IPパケットをゲートウェイである Docker ホストに送信し、そこから宛先の機器に届けられます。
一方、ルーターやPCなどの機器から見ると、コンテナは同一ネットワークのIPアドレスであるため、NDP (近隣探索プロトコル) で MAC アドレスを取得して、イーサーネットのフレームでカプセル化して直接届けようとします。

しかし、コンテナのネットワークインターフェースはホストネットワーク上の機器と同一リンクには無いため、それら機器から NDP で利用されるマルチキャストのパケットはコンテナに届かず、コンテナの MAC アドレスを取得できないという事象が発生します。

そこで、Docker ホストで NDP のパケットを中継し、コンテナとホストネットワークが NDP のメッセージを疑似的にやり取りできるようにすれば、問題を解決できます。

具体的には、Docker ホストに NDPPD を導入し、次のような設定を行うと良いです。

proxy eth0 {
    rule 2001:db8:0:0:a::/80 {
        iface docker0
    }
}

これにより、ルーターやPCなどの機器はコンテナのIPアドレスに対する送信先の MAC アドレスを取得でき、相互に通信を行えるようになります。

なお、NDPPD は、ルーターやPCなどの機器から送信されたマルチキャストのパケットをホストネットワークのインターフェース ("eth0") で受け取り、新たなマルチキャストのパケットをコンテナネットワークのインターフェース ("docker0") から送信します。
そのパケットに応答があった場合、問い合わせ元のルーターやPCなどの機器に対して、ホストネットワークのインターフェースの MAC アドレスを返します。

そのため、実際のパケットはそれら機器から Docker ホストに届けられ、そこからコンテナに届けられることになります。

また、Docker がコンテナにサブネットのIPアドレスを割り当てる際、そのIPアドレスがホストネットワーク上で利用されていないかどうかの重複アドレス検出は行われません。
そのため、ホストネットワークで SLAAC によるIPアドレスの自動設定が有効であると、サブネットのIPアドレスをいずれかの機器で利用してしまう可能性があり、そのIPアドレスを Docker がコンテナに割り当てることで問題が発生します。

パターン3を用いる場合は、ホストネットワークでは SLAAC を用いないほうが安全であると考えられます。


6. まとめ

Docker のブリッジネットワークで IPv6 を利用する場合の3つのネットワーク構成パターンを解説しました。

  1. ユニークローカルユニキャストアドレスを割り当てて、Docker ホストで IP マスカレードする。
  2. グローバルユニキャストアドレスを割り当てて、適切にルーティングする。
  3. Docker ホストが所属するネットワークのサブネットを割り当てて、Docker ホストで NDP Proxy を行う。

これら構成パターンの特徴から、用途に応じて次のように使い分けるのが良いのではないでしょうか。

  • 複数のサーバや機器から成る通常のシステムでの用途 : パターン2
  • 単一のPCやサーバ上での検証用途 : パターン1
  • 個人の家庭内用途 : パターン3
    • 特に、NTTフレッツ光ネクストの IPv6 を IPoE 方式でひかり電話を契約せずに利用し、/64 のプレフィックスしか割り当てを受けられないような場合

IPv6 についてこれから学ぶ (または学び直す) には、書籍『プロフェッショナル IPv6』 が非常にお薦めです。
無料の電子書籍版も配布されています。