Docker と IPv6

このセクションでは Docker のデフォルト・ブリッジ上の IPv6 を説明します。bridge という名称の bridge ネットワークは、Docker インストール時に自動的に作成されるものです。

IPv4 アドレス枯渇問題 により、IFTF は IPv4 の後継規格 IPv6(インターネット・プロトコル・バージョン6)RFC 2460 で策定しました。IPv4 および IPv6 の両プロトコルは、 OSI 参照モデル のレイヤ3にあたります。

Docker の IPv6 機能

デフォルトでは、 Docker サーバはコンテナ・ネットワークを IPv4 のみ設定します。Docker デーモンに --ipv6 フラグを指定して実行したら、IPv4/IPv6 デュアルスタック・サポートが有効になります。Docker は bridge0 の IPv6 リンク・ローカルアドレスfe80::1 をセットアップします。

デフォルトでは、コンテナはリンク・ローカル IPv6 アドレスのみ割り当てられます。グローバルにルーティング可能な IPv6 アドレスを割り当てるには、コンテナに対して割り当てる特定の IPv6 サブネットを指定します。IPv6 サブネットを設定するには、 --fixed-cidr-v6 パラメータを Docker デーモンの起動時に指定します。

dockerd --ipv6 --fixed-cidr-v6="2001:db8:1::/64"

Docker コンテナ用のサブネットは、少なくとも /80 を持っている必要があります。この方法により、IPv6 アドレスはコンテナの MAC アドレスで終わることができ、NDP ネイバー・キャッシュの無効化問題を Docker のレイヤで発生しないようにします。

--fixed-cidr-v6 パラメータを Docker に設定したら、新しい経路のルーティング・テーブルを作成します。更に IPv6 ルーティング・テーブルも有効化します(有効化したくない場合は、 dockerd 起動時に --ip-forward=false を指定します)。

$ ip -6 route add 2001:db8:1::/64 dev docker0
$ sysctl net.ipv6.conf.default.forwarding=1
$ sysctl net.ipv6.conf.all.forwarding=1

サブネット 2001:db8:1::/64 に対する全てのトラフィックは、 docker0 インターフェースを通る経路になります。

IPv6 転送(IPv6 forwarding)は既存の IPv6 設定に干渉する場合があり、注意が必要です。ホスト・インターフェースが IPv6 設定を取得するために、ルータ・アドバタイズメント(Router Advertisement)を使っているのであれば、 accept_ra2 に設定すべきです。そうしなければ、IPv6 転送を有効化した結果、ルータ・アドバタイズメントを拒否します。例えば、 eth0 を経由してルータ・アドバタイズメントを使いたい場合は、次のように設定すべきです。

$ sysctl net.ipv6.conf.eth0.accept_ra=2
IPv6基本設定

それぞれの新しいコンテナは、定義されたサブネットから IPv6 アドレスを取得します。更に、デフォルト経路(default route)がコンテナ内の eth0 に追加されます。これはデーモンのオプションで --default-gateway-v6 を指定しました。指定がなければ、 fe80::1 経由になります。

docker run -it ubuntu bash -c "ip -6 addr show dev eth0; ip -6 route show"

15: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500
   inet6 2001:db8:1:0:0:242:ac11:3/64 scope global
      valid_lft forever preferred_lft forever
   inet6 fe80::42:acff:fe11:3/64 scope link
      valid_lft forever preferred_lft forever

2001:db8:1::/64 dev eth0  proto kernel  metric 256
fe80::/64 dev eth0  proto kernel  metric 256
default via fe80::1 dev eth0  metric 1024

この例では、Docker コンテナはネットワーク・サフィックス /64 で割り当てられた(ここでは fe80::42:acff:fe11:3/64 )リンク・ローカル・アドレスと、グローバルな経路を持つ IPv6 アドレス(ここでは、 2001:db8:1:0:0:242:ac11:3/64 )を持ちます。コンテナは、リンク・ローカル・ゲートウェイに eth0fe80::1 を使い、2001:db8:1::/6 ネットワークの外と通信します。

サーバや仮想マシンは /64 IPv4 サブネットを割り当てられます(例: 2001:db8:23:42::/64 )。今回の例では、ホスト上の他のアプリケーションとの分離に /80 サブネットが使いますが、 Docker の設定でサブネットを /80 以上にも分割できます。

IPv6基本設定

このセットアップでは、サブネット 2001:db8:23:42::/802001:db8:23:42:0:0:0:0 から 2001:db8:23:42:0:ffff:ffff:ffff までの範囲を eth0 に割り当て、ホスト側は 2001:db8:23:42::1 をリスニングします。サブネット 2001:db8:23:42:1::/80 は IP アドレスの範囲 2001:db8:23:42:1:0:0:0 から 2001:db8:23:42:1:ffff:ffff:ffff までを docker0 に割り当て、これがコンテナによって使われます。

NDP プロキシの使用

Docker ホストが IPv6 サブネットの範囲にありながら IPv6 サブネットを持たない場合、コンテナが IPv6 を経由してインターネットに接続するには、 NDP プロキシ機能(NDP proxying) を使えます。例えば、ホストの IPv6 が 2001:db8::c001 であり、これはサブネット 2001:db8::/64 の一部です。IaaS プロバイダが 2001:db8::c000 から 2001:db8::c00f: までの IPv6 設定を許可している場合、次のように表示されます。

$ ip -6 addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000
    inet6 2001:db8::c001/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::601:3fff:fea1:9c01/64 scope link
       valid_lft forever preferred_lft forever

それでは、このアドレス範囲を2つのサブネット 2001:db8::c000/1252001:db8::c008/125 に分割しましょう。1つめのサブネットはホスト自身によって使われるもので、もう1つは Docker が使います。

dockerd --ipv6 --fixed-cidr-v6 2001:db8::c008/125

Docker サブネットには、 eth0 に接続するルータが管理しているサブネットが含まれているのに気を付けてください。つまり、Docker サブネットで公開される全てのデバイス(コンテナ)のアドレスは、ルータ側のサブネットから見つけることができます。つまり、ルータはこれらのコンテナと直接通信できると考えられます。

IPv6 NDP Proxying

ルータは IPv6 パケットを1つめのコンテナに送ろうとしたら、すぐにネイバー・ソリシテーション・リクエスト(neighbor solicitation request)を送信し、誰が 2001:db8:;c009 を持っているか訊ねます。サブネット上にアドレスが存在しなければ、誰も応答しません。コンテナはこのアドレスを Docker ホストの後ろに隠します。Docker ホストはコンテナアドレス用のネイバー・ソリシテーション・リクエストを受信したら、自分自身のデバイスがアドレスに対する責任を持っていると応答します。この処理がカーネルの NDP Proxy と呼ばれる機能です。有効化するには、次のコマンドを実行します。

$ sysctl net.ipv6.conf.eth0.proxy_ndp=1

これでコンテナの IPv6 アドレスを NDP プロキシ・テーブルに追加できます。

$ ip -6 neigh add proxy 2001:db8::c009 dev eth0

このコマンドはカーネルに対してネイバー・ソリシテーション・リクエストが届いているかどうか訊ねます。リクエストとは、デバイス eth- 上の IPv6 アドレス 2001:db8::c009 に対してのものです。この結果、全ての IPv6 アドレスに対するトラフィックは、Docker ホストを経由するようになります。そして、Docker ホストはコンテナのネットワークに対し、 docker0 デバイスを経由し、このルーティング・テーブルに従うようにします。

$ ip -6 route show
2001:db8::c008/125 dev docker0  metric 1
2001:db8::/64 dev eth0  proto kernel  metric 256

ip -6 neigh add proxy ... コマンドは、 Docker サブネットの各 IPv6 アドレスごとに実行してきました。残念ながら、サブネットの誰がこのコマンドを実行したか把握する機能はありません。別の方法としては、 ndppd のように NDP プロキシ・デーモンを使う方法があります。

Docker IPv6 クラスタ

ネットワーク環境の切り替え

到達可能な IPv6 アドレスを使い、異なったホスト上のコンテナ間での通信を可能にします。簡単な Docker IPv6 クラスタの例を見ていきましょう。

IPv6 スイッチ・ネットワーク

Docker ホストは 2001:db8:0::/64 サブネットを持ちます。ホスト1はコンテナに対して 2001:db8:1::/64 サブネットを自身が持つコンテナに対して提供します。そのために3つの経路設定をします。

  • 2001:db8:0::/64 に対する全てのトラフィックは eth0 を経由する。
  • 2001:db8:1::/64 に対する全てのトラフィックは docker0 を経由する。
  • 2001:db8:2::/64 に対する全てのトラフィックはホスト2の IP アドレスを経由する。

また、ホスト1は OSI レイヤ3のルータとしても動作します。あるネットワーク・クライアントがターゲットに接続しようとする時、ホスト1のルーティング・テーブルを指定し、ホスト1がトラフィックを指定先に転送します。これはネットワーク 2001:db8::/642001:db8:1::/642001:db8:2::/64 上におけるルータとしても機能します。

ホスト2でも似たような設定を行います。ホスト2のコンテナは 2001:db8:2::/64 から IP アドレスを取得します。ホスト2には3つの経路設定があります。

  • 2001:db8:0::/64 に対する全てのトラフィックは eth0 を経由する。
  • 2001:db8:2::/64 に対する全てのトラフィックは docker0 を経由する。
  • 2001:db8:1::/64 に対する全てのトラフィックはホスト1の IP アドレスを経由する。

ホスト1との違いは、ホスト1の IPv6 アドレス 2001:db8::1 には 2001:db8:1::/64 を経由するのと異なり、ホスト2のネットワーク 2001:db8:2::/64 は直接ホスト上の docker0 インターフェースに接続します。

この方法は全てのコンテナが他のコンテナに対して接続できるようにします。 コンテナ1-* は同じサブネットを共有し、お互いに直接接続します。 コンテナ1-*コンテナ2-* 間のトラフィックは、ホスト1とホスト2を経由します。これはこれらのコンテナが同じサブネットを共有していないためです。

ホストごとの環境の切り替え機能(switched environment)により、全てのサブネットに関する経路が判明しています。常に必要となるのは、クラスタに対するルーティング・テーブルの追加と削除のみです。

図中の各種設定のうち、点線以下は Docker が管理します。 docker0 ブリッジの IP アドレス設定は、コンテナの IP アドレスを持つ Docker のサブネットに対する経路です。線から上の設定は、ユーザが個々の環境に合わせて書き換えられます。

ネットワーク経路の環境

ネットワーク環境の経路は、レイヤ2スイッチとレイヤ3ルータの関係に置き換えられます。ホストはデフォルト・ゲートウェイ(ルータ)を知っており、(Docker によって管理されている)個々のコンテナに対する経路を処理します。ルータは Docker サブネットに関する全ての経路情報も保持しています。この環境でホストの追加や削除時は、各ホストではなく、ルータ上のルーティング・テーブルを更新しなくてはいけません。

IPv6 経路ネットワーク

このシナリオでは、同じホスト上のコンテナは直接通信可能です。異なったホスト上にあるコンテナ間のトラフィックは、ホストとルータを経由して経路付けられます。例えば、 コンテナ1-1 から コンテナ2-1 に対するパケットは ホスト1ルータ 、そして ホスト2 を経由して コンテナ2-1 に到達します。

IPv6 アドレスを短いまま維持するため、ここでは例として各ホストに /48 ネットワークを割り当てます。ホストは自身のサービスで /64 のサブネットを1つ使っており、もう片方は Docker です。3つめのホストを追加する時は、 2001:db8:3::/48 サブネットに対する経路をルータで行い、ホスト3上の Docker で --fixed-cidr-v6=2001:db8:3:1::/64 を設定します。

Docker コンテナのサブネットは、少なくとも /80 以上の大きさが必要なのを覚えておいてください。これは IPv6 アドレスがコンテナの MAC アドレスで終わるようにするためで、Docker レイヤにおけるNDP ネイバー・キャッシュ無効化問題を防止します。もし環境に /64 があれば、 /78 はホストのサブネット用に、 /80 がコンテナ用に使われます。これにより、 16 の /80 サブネットは、それぞれ 4096 のホストを使えます。

図における各種の設定のうち、点線以下は Docker によって管理されます。 docker0 ブリッジの IP アドレス設定は、コンテナの IP アドレスを持つ Docker のサブネットに対する経路です。線から上の設定は、ユーザが個々の環境に合わせて書き換え可能です。