結論としてはタイトルに書いた通りですが、調べた点とかをメモしておきます。
現象
ネットワーク構成はざっくりとこんな感じです。https-portalではリバースプロキシしてwebコンテナのコンテンツを返しています。
このとき、webコンテナでは接続元としてグローバルIPアドレスが取得されることを想定していましたが、実際にはDockerのネットワーク(172.16.0.0/16)内のローカルアドレスが取得されていました。
原因
最初は以下の点を調べてみましたが、どれも原因ではなさそうでした。
- --userns-remapが原因?
- 別の環境を用意して--userns-remapを有効にして試してみたが、正常にIPアドレスが取得できた
- --userland-proxy=trueが原因?
- docker-proxyを使用していると接続元IPアドレスが取得できないとの情報があったため、--userland-proxy=falseにして試すも再現せず
- steveltn/https-portalでリバースプロキシさせてるのが原因?
- そもそもhttps-portalの時点で接続元がローカルアドレスになっているので関係なさそう
最終的にはGitHubのこのコメントを見て、IPv6でサーバに接続していることが問題だと気づきました。
自分の環境で調べてみたところ、IPv6でコンテナへの接続が行われた場合、以下の図のように2001:db8::1(グローバルアドレス)から172.16.0.1(Dockerネットワーク上のアドレス)への変換が行われ、Docker内ではその変換されたIPv4アドレスを使用して通信をしていました。
そのため、IPv6で接続した際に取得できる接続元IPアドレスが次のような状態になり、接続元のIPアドレスが取得できなかったようです。
- https-portalコンテナ
- 接続元は172.16.0.1 (変換後のIPアドレス)
- webコンテナ
- 接続元は172.16.0.2 (https-portalのIPアドレス)
- X-Forwarded-Forは172.16.0.1 (変換後のIPアドレス)
対策案
Dockerのデーモンを--ipv6を付けて起動するようにする
「docker」「ipv6」の2つで検索すると一番最初に見つかったのがこのオプションでした。
試していないのですが、調べた限りDockerのIPv6周りは「IPv6ならコンテナそれぞれに固有のIPアドレスを割り振って直接接続すればいいじゃん」という思想のようで、IPv4のようにNATされることはないみたいです1。そのため、--ipv6オプションを指定しても、コンテナにIPv6アドレスの割当が行われるようになるだけで、IPv4のようにホストのIPv6アドレスに接続したらコンテナともIPv6で通信できるわけではないようです。
また、仮にこの方法でIPv6対応するとしても、最低でも/80(MACアドレス64bit分)のサブネットワークが必要みたいでした。自分が今使っているさくらのVPSだと、契約ごとにグローバルIPv6アドレスは1つしか付与されません2。そのため、この方法は実現が難しそうでした。
自分でNATする
自分でIPv6をNATすればIPv6アドレスが1つだけでも対応できそうですが、めんどくさいのと、管理が煩雑になりそうなためこれはやめておきました。3
Docker上ではなく、ホストにリバースプロキシを設置する
ホスト上にリバースプロキシを設置すれば、Dockerの制約は受けないので、IPv6で接続してもIPアドレスを取得できるようになると思います。恐らく、X-Forwarded-ForでIPv6アドレスを渡すようにすれば、コンテナ側でも接続元IPアドレスを取得できるようになると思います。
ただ、せっかくいろいろコンテナ化してるのに、サーバ上にコンテナ化してないサービスを置くのはなんだかなぁという気がするので、今回は見送りました。
IPv6での接続をあきらめる
今のところはIPv6でしか通信できないなんて環境はないだろうから、早急に対処する必要もないかなあということで、今回はこの方法を取りました。
気が向いたら何らかの方法でIPv6でも見れるようにしようと思います。
-
数年前にIPv6もNATして欲しいと要望が出されてはいるみたいですが、数年前に出されて未だに実装されていないことを考えると、だいぶ優先度は低そうです。 ↩
-
蛇足ですが、追加料金を支払うとIPアドレスが追加で付与される、みたいなサービスもこのVPSには無いようです。 ↩
-
このあたりを参考にrobbertkl/docker-ipv6natを使えば自分ですべて構築するよりは楽に済みそうです ↩