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_ra
を 2
に設定すべきです。そうしなければ、IPv6 転送を有効化した結果、ルータ・アドバタイズメントを拒否します。例えば、 eth0
を経由してルータ・アドバタイズメントを使いたい場合は、次のように設定すべきです。
$ sysctl net.ipv6.conf.eth0.accept_ra=2
それぞれの新しいコンテナは、定義されたサブネットから 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
)を持ちます。コンテナは、リンク・ローカル・ゲートウェイに eth0
の fe80::1
を使い、2001:db8:1::/6
ネットワークの外と通信します。
サーバや仮想マシンは /64
IPv4 サブネットを割り当てられます(例: 2001:db8:23:42::/64
)。今回の例では、ホスト上の他のアプリケーションとの分離に /80
サブネットが使いますが、 Docker の設定でサブネットを /80
以上にも分割できます。
このセットアップでは、サブネット 2001:db8:23:42::/80
は 2001: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/125
と 2001:db8::c008/125
に分割しましょう。1つめのサブネットはホスト自身によって使われるもので、もう1つは Docker が使います。
dockerd --ipv6 --fixed-cidr-v6 2001:db8::c008/125
Docker サブネットには、 eth0
に接続するルータが管理しているサブネットが含まれているのに気を付けてください。つまり、Docker サブネットで公開される全てのデバイス(コンテナ)のアドレスは、ルータ側のサブネットから見つけることができます。つまり、ルータはこれらのコンテナと直接通信できると考えられます。
ルータは 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 クラスタの例を見ていきましょう。
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::/64
、 2001:db8:1::/64
、 2001: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 サブネットに関する全ての経路情報も保持しています。この環境でホストの追加や削除時は、各ホストではなく、ルータ上のルーティング・テーブルを更新しなくてはいけません。
このシナリオでは、同じホスト上のコンテナは直接通信可能です。異なったホスト上にあるコンテナ間のトラフィックは、ホストとルータを経由して経路付けられます。例えば、 コンテナ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 のサブネットに対する経路です。線から上の設定は、ユーザが個々の環境に合わせて書き換え可能です。
参考