ELBでクライアントのGIPが取得できるProxy Protocolを試してみた
HTTPでは簡単に取得できるクライアントのGIP(グローバルIP)。
ただ、必ずしもHTTPを使うわけではなく、色々なプロトコルを使いたいときもありまして。WebsocketとかMQTTとかソケット通信とか色々。
そういうときに、ELBではTCPだとデフォルトではGIPが取得できないわけで、その対策をして、GIPを取得しましょうという話。
前提:
- クライアントのGIPを取得したい
- ELB作れるぐらいの知識がある
- サーバ側もproxy protocolの設定が必要になる(たとえば、nginxであれば1.5.12以上のバージョンを使い、かつnginx.confに手を入れる必要がある。この辺ちゃんと書かれた資料が少なくハマった…
ということで、やっていきましょう。
nginxインストール
まずnginxインストールする。 ubuntuだったら以下で最新版をインストールできる。
$ curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add - $ sh -c "echo 'deb http://nginx.org/packages/ubuntu/ trusty nginx' >> /etc/apt/sources.list" $ sh -c "echo 'deb-src http://nginx.org/packages/ubuntu/ trusty nginx' >> /etc/apt/sources.list" $ apt-get update $ apt-get install nginx
serverの項目に以下を追加。
server { listen 80 proxy_protocol; //proxy_protocolという文字列追加 server_name localhost; //適当に set_real_ip_from 10.50.0.0/16; //ELBなりVPCのセグメント追加する real_ip_header proxy_protocol; // この行まるごと追加
nginx起動させる。curl http://localhost して動作確認したいけど、Proxy Protocol対応しないとクライアントと通信できないので、最悪違うVirtual host切って確認する。
次にELB作成する。難しいところはないけど、以下のようにTCPにすることを忘れないようにする。
次にELBをproxy protocol対応をさせる。マネージメントコンソールが対応してないので、awscliからやる。
まずはawscliのインストール。
$ python -V Python 2.7.10 $ pip install awscli # pyenvを使っている場合。 $ pyenv rehash $ aws --version aws-cli/1.7.44 Python/2.7.10 Darwin/14.4.0 $ aws configure AWS Access Key ID [None]: XXXX AWS Secret Access Key [None]: XXXXXXXX Default region name [None]: ap-northeast-1 Default output format [None]: json
便利なので、jqもインストールしておく。
# macでbrew使っているなら以下でインストールできます。 $ brew install jq
次に、ELBの設定・ポリシーを確認しておく。「sample-elb-proxy-protocol」のところは自分が作ったELB名に変えてください。
$ aws elb describe-load-balancers \ --load-balancer-name sample-elb-proxy-protocol | \ jq '.LoadBalancerDescriptions [] .Policies' { "LBCookieStickinessPolicies": [], "AppCookieStickinessPolicies": [], "OtherPolicies": [] }
OtherPoliciesに何もないので、何も適用されていないことがわかります。SSLに対応したELBの場合、何か出てくるかもしれないので、それをメモっておいてください。以下も確認。
$ aws elb describe-load-balancers \ --load-balancer-name sample-elb-proxy-protocol | \ jq '.LoadBalancerDescriptions [] .BackendServerDescriptions' []
こちらもBackendServerDescriptionsが空なので、何も適用されていない。
次に適用すべきポリシーを確認する。といっても、何を設定するかは決まってるから、ここはスルーしてもいい。
$ aws elb describe-load-balancer-policy-types | \ jq '.PolicyTypeDescriptions[] | select(.PolicyTypeName=="ProxyProtocolPolicyType")' { "PolicyAttributeTypeDescriptions": [ { "Cardinality": "ONE", "AttributeName": "ProxyProtocol", "AttributeType": "Boolean" } ], "PolicyTypeName": "ProxyProtocolPolicyType", "Description": "Policy that controls whether to include the IP address and port of the originating request for TCP messages. This policy operates on TCP/SSL listeners only" }
"PolicyTypeName": "ProxyProtocolPolicyType"とあるので、ProxyProtocolPolicyTypeをtrueにする必要がある。
そんで、trueにする。
$ aws elb create-load-balancer-policy \ --load-balancer-name sample-elb-proxy-protocol \ --policy-name my-proxy-protocol-policy \ --policy-type-name ProxyProtocolPolicyType \ --policy-attributes '[{"attribute_name":"ProxyProtocol","attribute_value":"True"}]' Parameter validation failed: Unknown parameter in PolicyAttributes[0]: "attribute_name", must be one of: AttributeName, AttributeValue Unknown parameter in PolicyAttributes[0]: "attribute_value", must be one of: AttributeName, AttributeValue
どっかのブログに書いてあったこのやりかただとUnkwon parameterエラーになってしまう。--policy-attributesの書き方が悪いらしい。
ので、以下にする。
$ aws elb create-load-balancer-policy \ --load-balancer-name sample-elb-proxy-protocol \ --policy-name my-proxy-protocol-policy \ --policy-type-name ProxyProtocolPolicyType \ --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true
設定できました。 確認してみると、OtherPoliciesに設定が入っていることがわかります。
$ aws elb describe-load-balancers --load-balancer-name sample-elb-proxy-protocol | jq '.LoadBalancerDescriptions [] .Policies' { "LBCookieStickinessPolicies": [], "AppCookieStickinessPolicies": [], "OtherPolicies": [ "my-proxy-protocol-policy" ] }
最後に、このポリシーを特定のポートに対して有効にさせる必要があります。 SSLに対応したELBの場合、policy-namesに既存のポリシーも追加してください。「my-proxy-protocol-policy existing-policy」のように。
$ aws elb set-load-balancer-policies-for-backend-server \ --load-balancer-name sample-elb-proxy-protocol \ --instance-port 80 \ --policy-names my-proxy-protocol-policy
これで完了。 設定を確認します。
$ aws elb describe-load-balancers \ --load-balancer-name sample-elb-proxy-protocol | \ jq '.LoadBalancerDescriptions [] .BackendServerDescriptions' [ { "InstancePort": 80, "PolicyNames": [ "my-proxy-protocol-policy" ] } ]
nginxのログに以下のようなものが成功。
123.123.123.123 - - [07/Aug/2015:19:38:39 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36" "-"
Proxy Protocolを無効にしたい場合は以下のようにしてください。
$ aws elb set-load-balancer-policies-for-backend-server \ --load-balancer-name sample-elb-proxy-protocol \ --instance-port 80 \ --policy-names "[]"
policy-namesを空にしてあげる。これだけだとnginxが対応してないので、nginx.confもいじって、proxy protocolが適用されていない状態にする必要がある。
nginxが古い場合
nginxのログにこういうログが出るものの400エラーを返してしまうので、最新版をいれること。これなかなか分からずハマりました。
# tailf /var/log/nginx/access.log 10.100.100.101 - - [07/Aug/2015:15:08:25 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56232 80" 400 181 "-" "-" 10.0.1.248 - - [07/Aug/2015:15:08:28 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2697 80" 400 181 "-" "-" 10.100.100.101 - - [07/Aug/2015:15:08:35 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56236 80" 400 181 "-" "-" 10.0.1.248 - - [07/Aug/2015:15:08:38 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2702 80" 400 181 "-" "-" 10.100.100.101 - - [07/Aug/2015:15:08:45 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56239 80" 400 181 "-" "-" 10.0.1.248 - - [07/Aug/2015:15:08:48 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2705 80" 400 181 "-" "-" 127.0.0.1 - - [07/Aug/2015:15:08:49 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" 10.100.100.101 - - [07/Aug/2015:15:08:55 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56243 80" 400 181 "-" "-" 10.100.100.101 - - [07/Aug/2015:15:08:55 +0900] "PROXY TCP4 124.35.68.250 10.0.1.11 33048 80" 400 181 "-" "-" 10.0.1.248 - - [07/Aug/2015:15:08:58 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2708 80" 400 181 "-" "-"
ブラウザのログはこんな感じ。
Error response Error code 400. Message: Bad request syntax ('PROXY TCP4 10.0.1.248 10.0.1.13 54238 80'). Error code explanation: 400 = Bad request syntax or unsupported method.
終わりに
GIPに依存する設計はあまり好きではないのですが、要件で必要になるケースも多く検証してみました。awscliを使わないと設定できないので、ツールが増えるのがちょっと嫌ですが、それを除けば、そこまで辛いところはないように思います。
最新情報はこちらの公式ドキュメントにあるので、記事が古くなったら、ドキュメントも是非見てください。
何か問題等あれば遠慮なくご指摘ください!ではでは!
AWSシリーズ:
- CloudFrontで署名付きURLによるストリーミング配信をしてみた(Ruby編)