header と Nginx と ELB
ブログのネタが見つからずに9月も終わりかけているので苦し紛れで前回のエントリと似たような内容ですw
前回のエントリのおまけで書いたように、 AWS の ELB ではレスポンスヘッダが意図せず書き換えられることがあります。 そのことを知らないとハマりやすいところだと思うので ELB と Nginx を使う場合のいくつかのケースをまとめてみます。
ELB (HTTP lister 利用) と Nginx
HTTP listener を利用した場合、 X-Forwarded-For
, X-Forwarded-Port
, X-Forwarded-Proto
のリクエストヘッダがセットされます。 Nginx 上で remote_addr を使うと ELB の IP になってしまっていたり、 ELB で SSL 終端処理を行っていると実際のクライアントが http でリクエストしたのか https でリクエストしたのかわからないなんてことがよくありますが、それらのヘッダを使えば解決できます。いくつか例を紹介しましょう。
正しいクライアント IP をログ出力する
set_real_ip_from <VPC の IPv4 CIDR>; real_ip_header X-Forwarded-For; real_ip_recursive on; ... log_format main 'ipaddr:$remote_addr\t...
set_real_ip_from
, real_ip_header
を正しく設定することで、 $remote_addr
に正しいクライアント IP が出力されます。
バックエンドにクライアントの IP とリクエストのスキームを渡す
map $http_x_forwarded_proto $x_forwarded_proto { default $http_x_forwarded_proto; '' $scheme; } ... proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $x_forwarded_proto;
ELB を通さずにリクエストされた場合も考慮して map を使い、 proxy_set_header
でバックエンドに X-Forwarded-Proto
を渡します。
上記のように便利なロードバランサーとして扱うことができますが、 HTTP listener を利用した場合はレスポンスヘッダが改変されることがあります。例えば次のような場合です。
- ELB は
Content-Length
ヘッダが設定されていないチャンクでないレスポンスを、 チャンク形式にしてtransfer-encoding: chunked
ヘッダを付けて返すことがある - ELB は
Content-Length
ヘッダが設定されていないチャンクでないレスポンスを、Content-Length
ヘッダを付けて返すことがある
あまりこの問題に直面することが多くないかもしれませんが、レガシーなバックエンドの挙動をどうしても変えられない場合には困ったことになります。
ELB (TCP listener 利用、 Proxy Protocol 有効) と Nginx
ELB にレスポンスヘッダを改変されるのを嫌がるのなら、 TCP listener を利用することでそれを回避できます。
ですがその代わりに HTTP listener 利用時に可能であった X-Forwarded-For
ヘッダによるクライアント IP の取得と、 X-Forwarded-Port
ヘッダと X-Forwarded-Proto
ヘッダによるクライアントからのリクエストのポートとスキームの取得ができなくなります。
そこで解決策となるのが Proxy Protocol の有効化です。 Proxy Protocol は HAProxy が設計したもので、 ELB のドキュメントを読むのがわかりやすいと思います。
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
Classic Load Balancer の Proxy Protocol のサポートを設定する - Elastic Load Balancing
細かい話はさておき、 Proxy Protocol 有効の TCP listener 利用時は
- レスポンスヘッダが改変されることはない
X-Forwarded-For
ヘッダは付かないが、クライアント IP は取得できるX-Forwarded-Port
ヘッダは付かないが、クライアントのリクエストのポートは取得できるX-Forwarded-Proto
ヘッダは付かないが、クライアントのリクエストのポートにより、クライアントのリクエストのスキームは取得できる
ということになります。こちらもいくつか例を紹介しましょう。
正しいクライアント IP をログ出力する
set_real_ip_from <VPC の IPv4 CIDR>; real_ip_header proxy_protocol; real_ip_recursive on; ... log_format main 'ipaddr:$remote_addr\t...
real_ip_header
が変わっただけですね。
バックエンドにクライアントの IP とリクエストのスキームを渡す
map $proxy_protocol_port $x_forwarded_proto { default $scheme; 443 https; 80 http; } ... proxy_set_header X-Forwarded-For $proxy_protocol_addr; proxy_set_header X-Forwarded-Proto $x_forwarded_proto;
$http_x_forwarded_proto
を使うことはできませんね。 ELB に設定したポートに合わせてスキームを設定しています。
ALB と Nginx
これまでの文脈で言うと、 ALB を利用した場合も HTTP lister を利用した ELB と挙動は同じです。もちろん新しい ALB の方が AWS のロードバランサーとしては扱いやすくなっているのでそれはいいんですけどね。
NLB と Nginx
NLB は公開されたばかりで、その利用メリットはとても大きいように思います。ですが SSL 終端処理を行うことができないので、 Nginx で SSL 終端処理を行うことになり、 EC2 側の負荷がどうしても高くなってしまうでしょう。逆にそれさえ許容できれば、 TCP listener 利用の ELB よりも扱いやすくなると思います。
公式の各ロードバランサーの比較表はこちら
https://aws.amazon.com/jp/elasticloadbalancing/details/
HTTP header と Nginx と AWS の ELB などについてまとめてみました。レスポンスヘッダを確実にハンドリングしたい場合は、 TCP listener 利用の ELB か NLB を使うほかないと思います。 挙動の違いにハマることがよくありますが、用途に合わせてうまく使っていきましょう。