Usual Software Engineer

よくあるソフトウェアエンジニアのブログ

header と Nginx と ELB

ブログのネタが見つからずに9月も終わりかけているので苦し紛れで前回のエントリと似たような内容ですw

innossh.hatenablog.com

前回のエントリのおまけで書いたように、 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 を使うほかないと思います。 挙動の違いにハマることがよくありますが、用途に合わせてうまく使っていきましょう。