Heroku上でスクリーンショットサーバを動かす

前回PhantomJSのbuildpackを作成したので、Heroku上でPhantomJSのプロセスを自由に稼働させることができるようになった。

PhantomJSはGUI環境のない(Headless) WebKitブラウザであるため、ブラウザ上のJavaScript単体テストor結合テストをサーバ上で走らせて、継続的インテグレーションに組み込むなどの利用方法もあるだろう。これも興味深いトピックではあるけど、ここでは触れない。

今回はPhantomJSの画面レンダリングの機能を使って、スクリーンショットサーバをHeroku上に構築する。

構成と処理フロー

PhantomJSにはwebサーバ機能も含まれており、単独でHTTPサーバとしてリクエストを受け取る事もできるが、実サービスで利用するようなシロモノではない。そのためクライアントからリクエストを受け取るNode.jsのサーバを別に立てる。今回はこれもHeroku上で行なっている。

Node.jsのアプリケーションは、Socket.IOサーバとして稼働し、2つのchannelを持つ。
1つめのrequest channelでは、ブラウザからのスクリーンショット取得リクエストを受け取り、ページのURLをキューに入れる。
もう1つのrender channelでは、PhantomJSのスクリーンショットサーバと接続する。PhantomJSはスクリーンショットサーバであるが、Socket.IO的にはクライアントになる。スクリーンショットのリクエストは、接続中のPhantomJSクライアントのうちの1つにdispatchされる。

PhantomJSのアプリケーションは、PushサーバとしてのNode.jsにSocket.IOで接続し、スクリーンショットのリクエストを待つ。ページのURLがプッシュされた時点で、PhantomJSはページにアクセスし、スクリーンショットレンダリングを開始する。

スクリーンショットは画像ファイルとしてPhantomJSのローカルファイルシステムに保存されるが、そのままではどこからもアクセスできないので、イメージサーバであるAmazon S3にアップロードする。ここで、ファイルデータをNode.jsに戻すことなく、PhantomJSから直接Browser Post Formを利用してS3にアップロードする(postの際に必要になるsignatureは事前にNode.js側で生成されており、プッシュ通知の際にページURLと共に同時に渡ってくる)

最後に、PhantomJSはアップロードしたスクリーンショットのURLをNode.jsに通知する。その後、Node.jsはrequest channelに接続しているクライアント全てに対してスクリーンショットのURLをブロードキャストする(ブラウザはそれを受け取って描画する)

利点

スクリーンショットサーバをクラウド上で構築する場合、今まではEC2などのIaaS上でGUI環境を含むOSを稼働させることが多かったが、常時起動によるリソースの有効利用およびスケール時のVMの上げ下げのコストが問題点としてあった。

これを、HeadlessのPhantomJSでスクリーンショットを撮り、それをHeroku上で動作させることにより、スクリーンショットサーバのスケールアウトがスピーディかつ容易になっている。

たとえば、以下のコマンドでインスタンスを瞬時に*1上げ下げ可能である。

$ heroku ps:scale renderer=4

資料

*1:VMの起動停止に比べて