code up

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

EC2 + WebSocketsを構築 → 諦めた

既存のAWS ELB → Apache HTTPd → Tomcatの構成をWebSocket (RFC 6455; 2012/11/11現在PROPOSED STANDARD; あと一歩)に対応させようとして色々奮闘したけどうまくできなかったという話。

まずは基本的な構築方針を。

  • 現在の環境はできるだけ変えない
  • WebSocketを使って無駄な通信が減ればいいな程度
  • 静的コンテンツはApache HTTPdから送っており、HTTP/HTTPS混在。動的コンテンツはフルSSL
  • ELBを使った負荷分散
  • ソースからコンパイルとかはしない(一般的、標準ではない技術を使っていると思うので)
  • C10K問題は当分先なアクセス数なのでそこまでの話ではない
  • 現在は2台のWebサーバーをELB(Elastic Load Balancing)で分散している
  • Cookieを使ったSession Stickinessを使っているが、セッションはデータベースに保存しているのでStickinessが無くなることは構わない
  • httpdは2.2.23, tomcatは7.0.32, atmosphereは1.0.4, EC2はAmazon Linux 3.2.32, varnishは3.0.3あたりを使用した

Apache HTTPdはWebSocketに対応していない

このあたりで議論中。mod系であればmod_proxy_websocketapache-websocketというのがあるらしいが、前者はメンテされていない可能性が高いのと、後者はmod_proxyと連携してTomcatへの伝達はできなそうなのでどちらも試さずに断念。

ELBが60秒でタイムアウト

ある程度がんばってWebSocketが使えるようになったとしてもELBで60秒間無通信だと接続を切断してしまう仕様がある。Atmosphereフレームワーク側で自動的に再接続してくれるけど、それだったらComet(Long Polling)でいいんじゃないかと(コネクションの再利用という意味では大きく違うけど)。フォーラムでリクエストすれば60秒以上にあげてくれるらしいけど最大17分が限度みたい。クライアント側からポーリングすれば生き続けられるけど、やっぱりComet(Long Polling)でいいような気がする。

結局見送った

上記2つの理由から私が担当している環境ではWebSocketsを使うのは当面見送ることに。

ELBをやめて専用インスタンスを用意してHAProxyやVarnishなどで負荷分散を行い、SSLのTerminationをstunnelやstudとかで行った上でWebSocketのリクエストを直接Tomcatにつなぎ、それ以外をApacheに送り、静的コンテンツはApacheが返して、動的コンテンツはそこからさらにTomcatに送るようにすればいいのかもしれない。けどWebSocketのためだけにそこまで構築する体力は無いし、逆に(コスト)パフォーマンスが悪化しそう。

ELBおよびApache HTTPdまたはmod_proxy系が対応するまで(1~2年先?)はWebSocketのことは忘れよう。

Atmosphereではtransportにstreaming, fallbackTransportにlong-pollingを使用することにした。組み合わせを見るとStreamingがダメなのはOperaだけのようだし、担当しているサイトではOperaはサポート外ということなので。

streamingやlong-pollingであればELBはHTTP, HTTPSのまま使える。

この結論までに試した事を以下に。

Apacheの代わりを探す

Apache HTTPdがWebSocketに公式対応していないということでその他のリバースプロキシを調査。

nginx
こちらもモジュール(nginx_tcp_proxy_module)での対応となる。結構な量のhttpd.confをWebSocketのためだけに移植するのは時間がないのでパス。結局ELBのタイムアウト問題があるのでこれに代えても完全とは言えない。
Varnish
EC2だとyumでインストールできる。こちらもタイムアウトが60秒で組み込まれているので/etc/sysconfig/varnish-p pipe_timeout=600を追加した。この場合は10分接続を維持する。
DAEMON_OPTS="-a ${VARNISH_LISTEN_ADDRESS}:${VARNISH_LISTEN_PORT} \
             -f ${VARNISH_VCL_CONF} \
             -p pipe_timeout=600 \
             -T ${VARNISH_ADMIN_LISTEN_ADDRESS}:${VARNISH_ADMIN_LISTEN_PORT} \
             -t ${VARNISH_TTL} \
             -w ${VARNISH_MIN_THREADS},${VARNISH_MAX_THREADS},${VARNISH_THREAD_TIMEOUT} \
             -u varnish -g varnish \
             -S ${VARNISH_SECRET_FILE} \
             -s ${VARNISH_STORAGE}"

またバックエンドにWebSocketを飛ばすためには/etc/varnish/default.vclに以下(抜粋)を定義する必要がある。ProtocolをSwitchするのとSSLのCONNECTのようにpipeを返す処理が必要みたい。

sub vcl_pipe {
    if (req.http.upgrade) {
        set bereq.http.upgrade = req.http.upgrade;
    }
    return (pipe);
}
sub vcl_recv {
    return (pipe);
}

Varnishへの接続をELBにのみ限定する場合はSecurity Groupにamazon-elb/amazon-elb-sgを指定するとELBのみの通信しか許可しなくなる。

HAProxy
リポジトリを追加しないとyumでインストールできないので今回はパス。

ELBあれこれ

ELBではHTTP系とTCP系の二種類のリスナーが用意されている。このうちHTTP, HTTPSは現時点では全くWebSocketに対応していない。応答としてHTTP Status 101, Connection: Upgradeというのを返すところでHTTP Status 200, Connection: Keep-Aliveに書き換えてしまいハンドシェイクがうまくいかないためのようだ。

TCP, SSLは通信を通過させるだけなので上記の問題は発生しないが新たに下記ふたつの問題が出てくる。

  • Session Stickinessが使えなくなる
  • クライアントの元IPが分からなくなる(X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Portがセットされない)

SSLのTerminationは大丈夫、つまりSSL(443) - TCP(8080)という構成でもWebSocketは60秒間維持できた。

ただし、上にも書いたとおりデフォルトでは60秒で接続を切断してしまうためTCP系を使ったとしても中途半端な状態である。

だったら30秒毎くらいでハートビート(Heart beat/ping)して通信を発生させておけばよいか、と考えAtmosphere側で次のインターセプターを追加してみた。

<init-param>
	<param-name>org.atmosphere.cpr.AtmosphereInterceptor</param-name>
	<param-value>org.atmosphere.interceptor.HeartbeatInterceptor, org.atmosphere.client.TrackMessageSizeInterceptor</param-value>
</init-param>

だが、設定が悪かったのかやはり60秒で切断してしまった。クライアント側でsetIntervalを使って定期的にデータを送信したところ切断されなかった。ELBはひょっとしたらクライアント→サーバーの通信だけを見ているのかも。

でも常にハートビートさせると結局通信量が増えてしまうので、できたとしても大量のクライアント接続には向かない気がする。

Tomcat

Tomcatでは先日の記事の通りNIOのHTTPコネクタを指定していた。SSL通信のみのサイトなので次のように指定している。

<Connector URIEncoding="UTF-8" secure="true" scheme="https" proxyPort="443" connectionTimeout="20000" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" redirectPort="8443"/>

AJPのNIOコネクタは現時点ではExperimental(実験的)なのでこちらも深くは試していない。

警告、エラーメッセージ on Chrome

Error during WebSocket handshake: 'Connection' header value is not 'Upgrade'

ELB→Apache HTTPd→Tomcatの構成でmod_proxy_ajp + 実験的AJPを使った場合に出たエラー。WebSocketの接続が確立出来ず。

Unexpected response code: 200
Websocket closed, reason: Connection was closed abnormally (that is, with no close frame being sent).

同じくWebSocketの接続が確立できていないエラー。101を期待しているのに200が応答されたケース。ELBがHTTP系のリスナーであったり、Apache HTTPdを通しても出るエラー。

関連記事
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。