webサーバ(バックエンド)をロードバランサ下に複数置いて負荷分散をするのは極めて一般的なことですが、
そのときの問題として、バックエンドからはアクセスがロードバランサーから来たものとして扱われてしまいます。
バックエンド側のアクセス元IPが変わってしまうので、アクセスログに元IPが載らなくなってしまいます。
また、アプリケーション側でIP制限をかけたい場合も、標準的な方法(PHPだと$_SERVER['REMOTE_ADDR']
)で取得できるIPアドレスが変わってしまうのも困りものです。
この問題を解決するために、ロードバランサーからバックエンドへ元IPを伝えるためのPROXY protocolというプロトコルがあります。
今回はvarnish(ロードバランサ)とnginx(バックエンド)の組み合わせで、PROXY protocolを有効にして、バックエンド側に元IPを伝えてみます。
構成
- varnish 5(4は未対応)
- nginx 1.10
今回はvarnishのバージョンの関係上、Ubuntu 17.04(開発版)で検証しました。
varnishとnginxは同一マシンに同居させ、外部からvarnishが80番ポートで受け付けたアクセスを、8080番で待ち受けてるnginxにforwardします。
クライアント → | varnish(80) - (with PROXY Protocol) → nginx(8080) |
varnish
vcl 4.0;
backend default {
.host = "127.0.0.1";
.port = "8080";
.proxy_header = 1;
}
sub vcl_recv {
return (pass);
}
sub vcl_backend_response {
}
sub vcl_deliver {
}
backend に .proxy_header = 1
を追加することで、PROXY protocolを有効になります。
PROXY Protocolはv1とv2の2つのバージョンがありますが、nginxはv1のみの対応のため、v1(1)を指定します。
nginx
server {
listen 8080 proxy_protocol;
server_name _;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
location ~ [^/]\.php(/|$) {
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
}
}
ポイントは以下の3点。
-
listen
にproxy_protocol
を指定。 -
set_real_ip_from
に ロードバランサーのIPを指定。(今回は127.0.0.1
) -
real_ip_header proxy_protocol
を指定。
で、どうなるの?
ロードバランサーがバックエンドにHTTPリクエストをforwardするときに、通常のHTTPリクエストヘッダの前に1行を挿入します。
PROXY TCP4 192.168.33.1 192.168.33.177 55407 80
左からTCPバージョン、送信元IP、ロードバランサのIP、送信元ポート、ロードバランサのポートなります。
この送信元IPがアクセス元のIPになります。nginxはこの情報を受け取ってアクセス元IPを書き換えます。
$_SERVER['REMOTE_ADDDR']
も自動的に書き換わってくれるので、アプリケーションからはダイレクトにアクセスされた場合と同じように処理ができるようになります。
注意!
非常に便利なPROXY PROTOCOLですが、このPROXY行を無条件に受け付けてしまうと、アクセス元IPの改ざんを許してしまうことになります。set_real_ip_from
でロードバランサーからだけのPROXY行を受け付けることが重要です。
追記
今回はロードバランサーにvarnishを使いましたが、AWS ELBもPROXY Protocolを有効にできます。設定方法はAWSのドキュメントを参照してください。