LXC上のDockerコンテナ内のnginxでクライアントIPアドレスが取れなかったのでProxy Protocolを試したらうまくいった。でも結局使わなかった。

柴田さんがUbuntu Weekly RecipeLXDの記事を何度も書いているのを見て、nginx-proxy環境をLXD上で動かしてみました。nginx-proxyは、他のDockerコンテナを認識して自動でリバースプロキシ設定をしてくれる便利なコンテナです。letsencrypt-nginx-proxy-companionとあわせて使えば、Let’s Encryptの証明書の取得・更新も自動でやってくれます。この組み合わせは、2018年に書いた「Ubuntuスタートアップバイブル」でも紹介しました。

LXD上でnginx-proxyや他のWebアプリ(WordPressやNextcloudなど)を動かすようにすれば、他のサーバーへの引っ越しや日々のバックアップが簡単にできるはずです。

lxcコンテナにDockerをインストールし、nginx-proxyコンテナを動かした後、http・httpsを転送するために、以下のコマンドを実行しました(docker-hostはコンテナ名)。

lxc config device add docker-host http proxy listen=tcp:[::]:80 connect=tcp:127.0.0.1:80
lxc config device add docker-host https proxy listen=tcp:[::]:443 connect=tcp:127.0.0.1:443

IPv6も転送したいので「tcp:[::]」と書きましたが、「tcp:0.0.0.0」と書いてもIPv4・IPv6の両方が転送されました。
設定を確認すると、以下のようになります(この例では、eth0が事前に追加・設定済みでした)。

$ lxc config device show docker-host 
eth0:
  ipv4.address: 10.10.10.2
  name: eth0
  nictype: bridged
  parent: lxdbr0
  type: nic
http:
  connect: tcp:127.0.0.1:80
  listen: tcp:[::]:80
  type: proxy
https:
  connect: tcp:127.0.0.1:443
  listen: tcp:[::]:443
  type: proxy

しかし、これだとnginx-proxyのログにクライアントのIPアドレスが出ず、以下のようになってしまいます(IPv6でアクセスした場合も同じ)。

nginx-proxy    | nginx.1    | whoami.local 172.19.0.1 - - [19/Aug/2020:15:11:27 +0000] "GET / HTTP/1.1" 200 17 "-" "curl/7.58.0"

「172.19.0.1」は、nginx-proxyのDockerコンテナ自身のIPアドレスです。LXCのproxy deviceがLXCコンテナに転送し、さらにそれをDockerのuserland-proxyがDockerコンテナ上でリバースプロキシとして動くnginxに転送しています。

そこでクライアントのIPアドレスをログに出せないか調べたところ、「Proxy Protocol」という仕組みを使えばできることが分かりました。

まず、以下のコマンドでlxcのプロキシ設定を作りなおしました。

lxc config device remove docker-host http
lxc config device remove docker-host https
lxc config device add docker-host http proxy listen=tcp:[::]:80 connect=tcp:127.0.0.1:80 proxy_protocol=true
lxc config device add docker-host https proxy listen=tcp:[::]:443 connect=tcp:127.0.0.1:443 proxy_protocol=true

これでProxy Protocolで転送されるようになるので、LXCコンテナ内のDockerコンテナ内で動くnginxの設定ファイルにある「Listen」ディレクティブに「proxy_protocol」を追記する必要があります。

nginx-proxyは動的に設定ファイルを生成するのですが、proxy_protocolには対応していなかったので、対応するよう変更を加えました。

jkbys/nginx-proxy
Automated nginx proxy for Docker containers using docker-gen - jkbys/nginx-proxy

変更後のコードは以下のコマンドで取得できます。

git clone -b accept-proxy-protocol https://github.com/jkbys/nginx-proxy.git

この変更により、環境変数に「ACCEPT_PROXY_PROTOCOL=true」を設定することで、すべてのListenディレクティブに「proxy_protocol」が追加されます。さらに、「SET_REAL_IP_FROM」にIPアドレス範囲(,区切りで複数指定可)を設定すれば、「set_real_ip_from アドレス範囲;」と「real_ip_header proxy_protocol;」が追記されます。これで、Proxy Protocolで送られてきたIPアドレスがログに記録されるようになります。

以下は、docker-compose.ymlの記述例です。

version: '3.8'

services:
  nginx-proxy:
    container_name: nginx-proxy
    build: nginx-proxy
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./_data/nginx/certs:/etc/nginx/certs:ro
      - ./_data/nginx/conf.d:/etc/nginx/conf.d
      - ./_data/nginx/vhost.d:/etc/nginx/vhost.d
      - ./_data/nginx/html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    environment:
      - ACCEPT_PROXY_PROTOCOL=true
      - SET_REAL_IP_FROM=172.16.0.0/12

  letsencrypt-companion:
    container_name: letsencrypt-companion
    image: jrcs/letsencrypt-nginx-proxy-companion
    restart: always
    volumes:
      - ./_data/nginx/certs:/etc/nginx/certs
      - ./_data/nginx/conf.d:/etc/nginx/conf.d
      - ./_data/nginx/vhost.d:/etc/nginx/vhost.d
      - ./_data/nginx/html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
    depends_on:
      - nginx-proxy

  whoami:
    container_name: whoami
    image: jwilder/whoami
    environment:
      - VIRTUAL_HOST=whoami.local
    depends_on:
      - nginx-proxy

これで、以下のようにクライアントのIPアドレスがログに出力されるようになりました。

nginx-proxy              | nginx.1    | whoami.local 10.0.2.2 - - [20/Aug/2020:06:10:32 +0000] "GET / HTTP/1.1" 200 17 "-" "curl/7.58.0"
nginx-proxy              | nginx.1    | whoami.local fd01:2345:6789::1 - - [20/Aug/2020:06:10:45 +0000] "GET / HTTP/1.1" 200 17 "-" "curl/7.58.0"

しかし、この構成だと「LXDのプロキシ(Proxy Protocol有効)」→「Dockerのプロキシ」→「nginx(リバースプロキシ)」→「各Dockerコンテナで動くWebサーバー」となり、パフォーマンス面を考えるとなんとも気持ち悪いので、結局使わなくなりました。LXDやDockerのプロキシを使わずにクライアントのIPアドレスをログに残す方法は、違うノートにまとめたいと思います。

コメント

タイトルとURLをコピーしました