Nginxを用いたWebSocketサーバのReverseProxy構成及びSSL/TLS接続
TL;DR
今更ながら、随分前の作業メモが貯まっていたのと、もう一つの記事のつなぎとするために吐き出しておく。
WebSocket 通信のSSL/TLS通信をさせたりロードバランシングさせたい場合など、WebSocketサーバの前段にReverseProxyを置きたい時は Nginx (v1.3.13 以降) を使おうという話。
なお、AWSのELB (ALB: Application Load Balancer) を使っても実現できるようになったので、AWSで運用しているサービスの場合はALB使ったほうがお手軽省コストメリット大きいと思う。
はじめに
以前Node.js 自身でSSL/TLS通信する方法を記したが、 シングルコアで動作するNode.jsプロセスに、暗号化のオーバーヘッドまで食わせるのは非常にもったいない。 pm2などのNode.js製のクラスタリングツールでマルチプロセス化はできるとはいえ、Node.jsサーバのコンピューティングリソースは本来の責務であるアプリケーションロジックに集中させたい。
Nginx v1.3.13以降 のWebSocket Support
WebSocket接続のハンドシェイク時、HTTP/1.1 101 (Switching Procotols) というステータスコードとHop-by-Hopヘッダを使用してWebSocket通信への切り替えを行う。
2013年02月にリリースされたNginx v1.3.13 で、これらの仕様に対応された。
WebSocket proxying - http://nginx.org/
Since version 1.3.13, nginx implements special mode of operation that allows setting up a tunnel between a client and proxied server if the proxied server returned a response with the code 101 (Switching Protocols), and the client asked for a protocol switch via the "Upgrade" header in a request.
これをReverseProxyとして挟む。
Try it!
ということで試してみる。
Install Nginx
まずはインストール。
NginxのRepositoryを登録し、Nginxをインストールする。 Nginx 1.3.13以降であれば良いので、Latest versionをインストールする。
Ansible Playbookのサンプルを載せておく。
Nginx Configuration
設定方法は公式ドキュメントにあるとおり。
WebSocketのハンドシェイク時に使われる Upgrade ヘッダと Connection ヘッダ は Hop-by-Hop ヘッダ である。 通常のEnd-to-End headerと異なり基本的に一度の転送に対して有効なヘッダなため、ReverseProxyを間に挟んでいる場合これらのheaderはバックエンドサーバまで届かない。
以下、Hop-by-Hop ヘッダに関するRFCのリンク。
RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1
以下、Nginxのリンク。
WebSocket proxying - http://nginx.org/
As noted above, hop-by-hop headers including "Upgrade" and "Connection" are not passed from a client to proxied server, therefore in order for the proxied server to know about the client's intention to switch a protocol to WebSocket, these headers have to be passed explicitly:
Nginxも例外ではないため、WebSocket通信をプロキシする場合は明示的にこれらのヘッダをバックエンドに渡すようにする必要がある。
この設定を入れて、Nginxを起動する。
$ sudo systemctl start nginx.service
なお、後段のNode.jsサーバは、以前使ったサンプルアプリケーションを再利用する。
server
package.json
client
How to run
$ vim app.js $ vim package.json $ vim /usr/share/nginx/html/sample.html $ npm install $ node app.js
動作確認
ブラウザで動作確認してみる。複数のタブで sample.html を開いてそれぞれで投稿したコメントが同期できればOK。
画像左部のDeveloper Toolsでも分かる通り、101 (Switching Protocols) が通り、WebSocket接続が確立できていることが分かる。
Request / Response Headerは以下。Hop-by-Hopヘッダも期待通りセットされている。
Request URL
ws://your.domain.or.ip/socket.io/?EIO=3&transport=websocket&sid=3_K8sGX3W6bYyXAAAAAe
Request Headers
Accept-Encoding : gzip, deflate, sdch Accept-Language : ja,en-US;q=0.8,en;q=0.6 Connection : Upgrade Cookie : io=lsIxmwKy6QY8s9ocAAAQ Host : (your domain or IP) Origin : http://your.domain.or.ip Sec-WebSocket-Extensions : permessage-deflate; client_max_window_bits Sec-WebSocket-Key : aZWrWEnJQsBvp38sAtBP1A== Sec-WebSocket-Version : 13 Upgrade : websocket
Response Headers
Connection : upgrade Date : Mon, 19 Sep 2016 07:30:43 GMT Sec-WebSocket-Accept : 9wz2i4UrQXQqilnyAtByxzwbwb8= Sec-WebSocket-Extensions : permessage-deflate Server : nginx/1.10.1 Upgrade : websocket
WebSocket通信を暗号化する
SSL/TLS Termination
あとは、このNginx Reverse Proxy - cient間の通信を暗号化すれば良い。 普通に、サーバ証明書 + 中間CA証明書 と 秘密鍵を配置して ssl on; すれば良いだけ。
Nginx を再起動して、https で sample.html を開き直せばws通信もSSL/TLS化される。
Request URL
wss://your.domain.or.ip/socket.io/?EIO=3&transport=websocket&sid=uLFmikk0IVrjBy8bAAAb
非常に簡単。
HTTP/HTTPSとWebSocket (ws/wss) の両接続を単一のNginxで終端する
上述の Nginx の設定ファイルを見て分かる通り、locationを分けることで単一のNginx かつ同じ 443 portで、通常のHTTP/HTTPSとWebSocket (ws/wss) の両通信を扱うことが出来る。 今回のように両要件を共存で動かす場合や、一つのNginx Reverse Proxy クラスタ、同一ドメインでサービス提供する時などに活用できると思う。
※ステートレスな通信であるHTTP/HTTPSと違い、WebSocketは接続を持続するステートフルな通信となるので、リソースには要注意。
まとめ
Reverse Proxyを挟んでおくと色々と小回りが効き運用上も楽になることが多いので、前記事のように対応するよりもこちらの方がベターだと思う。 餅は餅屋。
ちなみに、2016年8月にAWSのElastic Load Balancerが WebSocketに対応した。ALB (Application Load Balancer) というらしい。 ELBのラインナップに含まれる形で、以前のELBは "Classic" という扱いとなるらしい。
また別に書くが、スムーズすぎて心配になるくらい簡単に導入できるので、AWSで運用している方はALBを使ったほうがメリット大きいと思う。 お手軽で省コスト。
- 作者: 久保達彦,道井俊介
- 出版社/メーカー: 技術評論社
- 発売日: 2016/01/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る