はじめに
ホストOSのLinux上にdockerで構築したサービスがあります。フロントで待ち受けているのはnginx。ネットワークモードはbridgeで80番と443番ポートをexposeしています。
このnginxをIPv6化すなわちネイティブのIPv6で接続できるようにしようと思い、以下の作業を行いました。
- OS側にプロバイダからもらったIPv6アドレスを設定。debianなので/etc/network/interfacesに設定しました。
- DNSのAAAAレコードに上記アドレスを設定
- nginxの設定ファイルで
::
でもlistenするようにする。
listen 443;
listen [::]:443 ipv6only=on;
実はこれだけでIPv6でふつうに接続できるようになります。確認するには以下のように curl に '-6' オプションをつけます。
curl -6 https://some_url/
しかしひとつ大きな問題があります。IPv6で通信した時はクライアントのIPv6アドレスが記録されるかわりにこのネットワークのIPv4アドレスが固定で記録されてしまうのです。もちろんIPv4では正常にクライアントのアドレスが記録されます。
これは実運用を考えると致命的です。この問題を解決するためにいろいろ調べてみました。
dockerの設定
上記の状態ではdockerのネットワークもコンテナもIPv4オンリーで動作しています。実は、docker-proxyというエージェントがホストで動いてIPv6の接続を待ち受け、IPv4に変換してコンテナに転送してくれているからIPv6でもつながるのです。だからアドレスが変わってしまうわけです。docker-proxyを介さずダイレクトにつながるようにすればこの問題は解決します。
調べた範囲で方法は2つあります。
- dockerのネットワークとコンテナをIPv6に対応させ、そこにforwardしてあげる。
- nginxのネットワークモードをbridgeではなくhostにしてダイレクトに接続を待ち受けるようにする。
2が圧倒的に簡単ですが、dockerの流儀から若干はずれる気がして1でがんばろうとしてみました。結論をいうと最終的には2を採用しました。
ただ、1も可能なことは確認しました。せっかくなので1の手順を簡単に示しておきます。
dockerネットワークのIPv6対応
公式サイトのIPv6 with Dockerを参照しました。dockerd の起動時に以下のようにオプションを指定するといいと書いてあります。
dockerd --ipv6 --fixed-cidr-v6="2001:db8:1::/64"
2つ注意点があります。
-
--fixed-cidr-v6
は省略可能と書いてありますが現時点では必須です。しかもfe80:...
のリンクローカルアドレスは指定できません。また、2001:db8 というのはドキュメントの例示用なのでプロバイダから賦与されたグローバルユニキャストアドレスかユニークローカルアドレスを指定する必要があります 。1 - この設定はデフォルトのbridgeネットワーク docker0 をIPv6化する設定なので、他のネットワークを使っている場合は以下のように作成時にIPv6の設定をする必要があります(逆に上記のオプションは不要)。
docker network create some_network --ipv6 --subnet="2001:db8:1::/64"
これでIPv6の通信ができるようになりました。
転送設定
ホストにきたパケットをコンテナに転送するようにします。dockerのIPv4側はiptablesを使って転送の設定をしています。IPv6も同様にしてみましょう。転送先のコンテナのIPv6アドレスを2001:db8:1::4
とします。
sudo /sbin/ip6tables -t nat -I PREROUTING -p tcp -m multiport --dport 80,443 -j DNAT --to-destination 2001:db8:1::4
sudo /sbin/ip6tables -t nat -I OUTPUT -p tcp -m multiport --dport 80,443 -j DNAT --to-destination 2001:db8:1::4
sudo /sbin/ip6tables -t nat -I POSTROUTING -s 2001:db8:1::/64 -o eth0 -j MASQUERADE
まとめ
これで一応クライアントのIPv6アドレスが記録できるようになりました 2 。ただ、デフォルトだとコンテナのアドレスは起動時に動的に変わる可能性があるので、固定するか、あるいはdockerの再起動ごとに毎回なんらかの方法で取得してip6tablesを設定する必要が出てきます。
docker非標準の方法でそこまでがんばることに嫌気がさしてきて、結局hostネットワークを使う方法を採用してしまいました。dockerがいつの日か標準で対応してくれるのを待ちたいと思います。