東京でウェブオペレーションエンジニアをしている id:dekokun です。
本記事ではAWSでELBを使用してHTTP/2 or SPDYを運用する上で直面する問題としてのクライアントIPが分からなくなる問題の紹介を行い、その後に解決策としてのPROXY protocolの紹介・PROXY protocolの設定方法について記載します。
この記事は先日公開しましたAWS EC2でのHTTP/2 or SPDY導入方法 - Hatena Developer Blog (以下、"前回の記事"と呼びます) の続編となります。
ELBのTCPモードにおける問題点
さて、前回の記事にてELBをTCPモードで動かすことによってHTTP/2 or SPDY対応を行う方法を紹介しましたが、この方法では1つ大きな問題点があります。 それは、nginxがクライアントのIPを取得することができなくなってしまう(nginxはELBのIPを取得してしまう)ということです。
クライアントのIPを取得することができないことによって、具体的には以下のような問題が起きます。
- アクセスログに記載されるIPがクライアントのIPではなくなるため何か調査の必要が発生した際に詳しい調査を行うことができなくなります
- IPによるアクセス制御ができなくなります
- 具体的には「特定のIPからのアクセスだけ許可したい」「ngx_http_limit_req_moduleなどを使用してあるIPからの接続数が急増した際に一時的なアクセス制限を入れたい」などができなくなります
解決策としてのPROXY protocol
PROXY protocolとは、ロードバランサがクライアントのIPをupstreamサーバに伝えることができるものです。TCPレイヤで動く、X-Forwarded-Forのようなものともいえます。 PROXY protocolを使うことで、upstreamサーバがクライアントのIPを取得することができるようになり、上記問題を解決することができます。
PROXY protocolの必要となる技術的背景及びPROXY protocolの動作
この節では、なぜTCPモードのロードバランサがクライアントのIPを取得できないのか及び、ELB等関係なくそもそもPROXY protocolがどのようにしてその問題を解決しているのかを説明します。
なんらかのロードバランサ(ELBやhaproxyやLVS等)からTCPモードでupstreamのサーバに対してパケットを転送する時、基本的にはupstreamサーバ(今回の例だとnginx)に届けるパケットはTCP/IP上でのアクセス元IPをロードバランサのIPに変換することになります。それにより、upstreamのサーバが認識する"アクセス元のIP"は、上記ELBやhaproxyのIPとなってしまい、upstreamのサーバはロードバランサと通信しているクライアントのIPを取得することができません。
[クライアント(PCとかスマートフォンとか)] <- upstreamサーバは本当ならこいつのIPを知りたい ↓ ↓ ↓ [ロードバランサ] クライアントから来たパケットを転送する際にTCP/IPレイヤでアクセス元IPを自分のIPに変換する ↓ ↓ ↓ [upstreamサーバ] パケットのアクセス元IPがロードバランサのIPに変換されているためクライアントのIPが分からない
ここで、「アクセス元のIPをupstreamのサーバに伝えてあげる」と聞いてパッと思い浮かぶのは、例えばhttpだと"X-Forwarded-For"を付与する等の、L7のプロトコルを使ってupstreamに対してIPを伝えてあげる手法かと思います。ELBのhttpモード使用等のL7レイヤのロードバランサであれば特に問題なくその方法を使えばいいかと思います。 しかしTCPモードで"httpだった場合はX-Forwarded-Forを付与する"というようなことを行おうとすると、以下2つを実施する必要が出てきます。
- TCP上で流れているL7レイヤのプロトコルを判別
- それぞれのプロトコルに応じて、必要なヘッダを挿入する(HTTPだったらX-Forwarded-Forを挿入)などしてアクセス元のIPを伝える
1.
は、世の中に数多あるL7レイヤのプロトコルのどれであるかを正確に判断することになるので、非常に困難であることが容易に想像できます。
"このロードバランサはHTTPしか考えなくて良い"という制約を課して1. の問題を回避したとしても、2. の問題が依然として残ります。
2.
を解決するにはL7レイヤのパーサをロードバランサが用意しなくてはならず実装が大変になってしまいますし、動作時の負荷も非常に大きく増えてしまいます。
というわけで上記のような問題を解決するために、TCPレイヤでの"X-Forwarded-For"的なものとして出てきたのがPROXY protocolです。ロードバランサがPROXY protocolでアクセス元の情報を伝えることにより、upstreamのサーバがアクセス元のIPやポートを知ることができるようになります。
この一連の流れ及びPROXY protocolの仕様についてはThe PROXY protocol により詳しく記載してあります。
なお、上記で"基本的にはupstreamサーバ(今回の例だとnginx)に届けるパケットはTCP/IP上でのアクセス元IPをロードバランサのIPにすることになります"と書きましたが、そうならない例としては、LVSでDSRを使用するなどアクセス元IPを変換しないようなロードバランサを使う場合が考えられます。そのような場合はPROXY protocolなしでupstreamサーバはクライアントのIPを取得することができます。
ELB + nginx構成でのPROXY protocolの設定方法
以下では実際にどのように設定を行うことでPROXY protocolを使うことができるのかを記載します。
ELBの設定
PROXY protocolに対応させるにはELB側でも設定が必要です。前回の記事 にてTCPモードの設定について記載しましたが、その上でAWS CLIを使用して以下のようにPROXY protocol対応させる必要があります
以下は nginx-test
という名前のELBで443 portに対して設定する場合の設定方法です。
$ aws elb create-load-balancer-policy --load-balancer-name nginx-test --policy-name EnableProxyProtocol --policy-type-name ProxyProtocolPolicyType --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true $ aws elb set-load-balancer-policies-for-backend-server --load-balancer-name nginx-test --instance-port 443 --policy-names EnableProxyProtocol
設定が完了したかどうかは、jqコマンドを使用して以下のように確認することができます。
$ aws elb describe-load-balancers --load-balancer-name nginxtest | jq '.LoadBalancerDescriptions[].BackendServerDescriptions[] | select(.InstancePort == 443)' { "InstancePort": 443, "PolicyNames": [ "EnableProxyProtocol" ] }
nginxの設定
前回の記事にて「TLS対応さえ済めば後はlistenディレクティブにて"spdy"または"http2"と記述するだけです」と記載しましたが、PROXY protocol対応は更にその上に以下3点を行う必要があります。
proxy_protocol
という記述を追加real_ip_header proxy_protocol;
という記述を追加set_real_ip_from
にロードバランサのIPレンジを追加
~略~ server { set_real_ip_from x.x.x.x/x; real_ip_header proxy_protocol; listen 443 ssl http2 proxy_protocol; ~略~
あとがき
前回の記事にも記載しましたが、この記事は以下3本についての連載の2作目となります。
- AWS EC2上の環境へのHTTP/2 or SPDY対応の導入方法
- HTTP/2 or SPDY運用の課題とその解決方法としてのPROXY protocolについての解説
- PROXY protocol自体の運用の課題と解決案
3作目は監視周りについて書く予定です。3作目もお楽しみに!
はてなでは、HTTP/2を導入していくぜ!というエンジニアを募集しています。 あと、私、東京でインフラエンジニアしています。東京で働きたいぜ!というエンジニアも絶賛募集中です。