SREチームの@cubicdaiyaです。今回はnginxによるTCPレイヤーでのロードバランスについて解説します。
ロードバランサーとしてのnginx
nginxはHTTPやTCP、UDP等の複数のレイヤーでロードバランサーとして稼働させることができます。(TCPロードバランサーは1.9.0以降、UDPロードバランサーは1.9.13以降で利用可能です)
また、ngx_http_ssl_module
や ngx_stream_ssl_module
を利用することでそれぞれのレイヤーでTLSを有効化することも可能です。
TCPロードバランサー用のモジュールを有効にする
HTTPレイヤーでロードバランスするためのモジュールはデフォルトで組み込まれますが、TCP(とUDP)レイヤーでロードバランスするにはnginxのconfigure
スクリプトに--with-stream
(あるいは --with-stream=dynamic
)を付与してビルドする必要があります。
cd nginx-1.11.3 ./configure --with-stream make sudo make install
以下はTCPロードバランサー向けに明示的に組込む必要があるモジュールの一覧です。
$ ./configure --help | grep with-stream --with-stream enable TCP/UDP proxy module --with-stream=dynamic enable dynamic TCP/UDP proxy module --with-stream_ssl_module enable ngx_stream_ssl_module --with-stream_geoip_module enable ngx_stream_geoip_module --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module
nginxの標準モジュールで明示的に組み込む必要があるモジュールについてはこのように --with-
で始まるオプションが用意されています。一方デフォルトで組み込まれるモジュールについては --without-
で始まるオプションが用意されており、特定のモジュールを無効する際に指定します。
# ngx_stream_geo_moduleとngx_stream_map_moduleを無効にする ./configure \ --with-stream \ --without-stream_geo_module \ --without-stream_map_module
nginxをTCPロードバランサー向けに設定する
nginxのTCPロードバランサー設定は stream
コンテキストに記述します。(ポート番号やIPアドレスはテキトーです)
http { # こっちじゃない } stream { error_log /var/log/nginx/stream.log info; proxy_protocol on; upstream grpc { server 192.168.0.1:12345; server 192.168.0.2:12345; } server { listen 12345; proxy_pass grpc; } }
ちなみにUDPロードバランサーとして動かすには listen
ディレクティブの引数に udp
を追加します。
listen 1234 udp;
このように若干の違いはあるもののアップストリームやプロキシの設定方法はHTTPのロードバランサーの場合とほとんど同じです。一方で、HTTPレイヤーでのロードバランサーに比べるとできることが限られるので注意が必要です。例えばHTTPヘッダの操作なんかは当然できませんし、ログフォーマットの設定もHTTPの場合と違ってできません。
追記(20160926): nginx-1.11.4からstreamコンテキストでlog_formatディレクティブが利用可能になりました。
次にnginxのTCPロードバランサー特有の設定について解説していきます。
TCPロードバランサーでのロギング
nginxでアクセスログを出力するには通常 access_log
ディレクティブを利用しますが、stream
コンテキストでは access_log
ディレクティブが使えないのでかわりに error_log
ディレクティブを利用します。また、ログレベルは info
以上である必要があります。
追記(20160926): nginx-1.11.4からstreamコンテキストでaccess_logディレクティブが利用可能になりました。
error_log /var/log/nginx/stream.log info;
ログはこんな感じで出力されます。
2016/08/09 11:38:16 [info] 76796#0: *4 client 127.0.0.1:63501 connected to 0.0.0.0:9999 2016/08/09 11:38:16 [info] 76796#0: *4 proxy 127.0.0.1:63502 connected to 127.0.0.1:9001 2016/08/29 11:38:16 [info] 76796#0: *4 client disconnected, bytes from/to client:78/171, bytes from/to upstream:171/78
アップストリームのサーバに接続元のIPアドレスを伝搬する
一般にHTTPレイヤーのロードバランサーでは X-Forwarded-For
や X-Real-IP
といったヘッダを利用してアップストリームのサーバに接続元のIPアドレスを伝搬するといったことがよく行われます。
TCPロードバランサーで同じことをやるにはPROXY protocolを利用します。nginxでは proxy_protocol
ディレクティブでPROXY protocolのON/OFFを切り替え可能です。(デフォルトはOFF)
proxy_protocol on;
この際、アップストリームのサーバもPROXY protocolに対応する必要がある点に注意しましょう。メルカリではgRPCを利用したサーバの前段にnginxを配置してTCPレイヤーでロードバランスしているのですが、当時開発していた際にgRPCのサーバがPROXY protocolに対応していないことがわかったので@kazegusuriに対応してもらったことがありました。以下のスライドにそのへんの話が少し載っています。
GRPCの実践と現状での利点欠点 / Go Conference 2016 Spring
サーバステータスの取得
nginxをTCPロードバランサーとして動かす場合でも各種サーバステータスの取得はHTTPの場合と同じく、ngx_http_stub_status_moduleを利用します。
http { server { listen 80; location /status { stub_status on; allow 127.0.0.1; deny all; } } } stream { # こっちじゃない }
なお、HTTPの場合と違ってnginxが処理している各リクエストのステータス(Reading、Writing、Waiting)はカウントしないので注意しましょう。( Active Connections
やserver accepts handled requests
はカウントされます)
まとめ
nginxによるTCPレイヤーでのロードバランスについて解説しました。若干癖はあるものの、nginxに慣れている開発者であればHTTPレイヤーでロードバランスするのと同じような感覚で設定できるので使いやすいのではないかと思います。