この記事はScala Advent Calendar 2014の9日目です。
ネタはHerokuにScalaアプリケーションを高速にデプロイする方法。
元々の発端は今年のScala Matsuriで「HerokuにScalaアプリケーションをデプロイしようとするとタイムアウトすることがあるんだけど。。。」という話題が出たことです。
デプロイの制限時間は15分ですが、HerokuのSlug Compilerはすこぶる非力なので、タイムアウトをくらう人もちょいちょいいます。
ちなみに僕も春頃に一度くらってますが、その時はHerokuのサポートに連絡したら該当アプリの制限時間を30分に延ばしてくれました。(^^;;;
そんなんアリかよ?と思わなくもないですが、コンパイル時間を短くするべく頑張っているからしばらくそれでしのいでくれ、とのこと。
自前のbuildpackでどうにかするという構想はScala Matsuriの時点であったんですが、雑事にまぎれて放置している間にはや3ヶ月です。。。(--
Advent Calendarに登録すれば嫌でもやるだろと、自分にプレッシャーをかけてみたもののそうこうしているうちにGitHub Syncとかできちゃいましたね。。。
GitHub SyncではGitHubにコードをpushすれば自動的にHerokuへのデプロイが非同期で走ります。(Heroku gitへのpushはpreHookされているのに対しGitHub SyncはpostHookです。)
僕の場合、ストレスの要因はコンパイルが遅いことよりもコマンドが返ってこないことにあったので、もうこれで良いんじゃないかとも思いましたが、他に提供できるネタもないのでScala用の高速buildpackを作ります。
★ 高速化作戦
どうやって高速化するかを説明するためにまずは標準のScala buildpackがやっていること見ていきます。
大雑把に言うとだいたい以下のような手順でビルドを行っています。
- Gitからコードを取得
- Buildpackのダウンロード
- JVMのダウンロード
- SBTのダウンロード
- 「sbt compile stage」コマンドの実行
- キャッシュや中間ファイルの削除
実際にコンパイルを行っているのは「sbt compile stage」コマンドです。
stageというのはSBT Native Packagerというプラグインのコマンドで、それさえあればScalaアプリケーションを実行できるというファイルセットを作ってくれるものです。
Playframeworkにはこのプラグインが組み込まれているので、Playアプリケーションのルートで「sbt stage」を実行すると「target/universal/stage」以下に必要なファイルがすべてコピーされ、実行スクリプトも生成されていることが確認できます。
要するにこの下のファイル群だけHerokuに渡しちゃえば、Scalaアプリケーションは実行できるわけです。
コンパイルも不要なのでSBTをダウンロードする必要もありません。
つまり、この手法の場合先の手順からこれだけのステップを省略できます。
- Gitからコードを取得
- Buildpackのダウンロード
- JVMのダウンロード
- SBTのダウンロード
- 「sbt compile stage」コマンドの実行
- キャッシュや中間ファイルの削除
ほとんど何もしないので、間違いなく速いはずですよね。
また、SBTやコンパイル前のソースコード、画像ファイル等もSlugに含まれなくなるのでサイズ的にも有利です。
★ heroku-buildpack-scala-compiled
ということで作ったbuildpackがこれです。
https://github.com/shunjikonishi/heroku-buildpack-scala-compiled
Scalaのbuildpackをforkして、要らないところをザクザク削って、Procfileが無い場合に自動生成するパスを変更しただけ。(^^;
腰が重かった割には作り始めるとあっという間でしたね。
最初は標準ライブラリはbuildpack側で生成するようにしようとか、Gitに入れるならherokuブランチを作ってgh-pagesのようにそっくり中身を差し替えようとか、色々難しく考えてたんですが、よくよく考えるとHerokuへのdeployにしか使わないと割り切ってしまえば別にバージョン管理する必要がないんですよね。
なので、コード管理しているgitリポジトリとは別にまっさらのgitリポジトリを作成してそちらにstage以下のコードを全部突っ込むことにしました。
手元にPlayアプリケーションがあるなら以下の手順で、Herokuに新しいアプリケーションを作成できます。
sbt stage mkdir heroku cp -rp target/universal/stage heroku cd heroku git init git add . (※1) git commit -m "Initial commit" heroku create -b https://github.com/shunjikonishi/heroku-buildpack-scala-compiled git push heroku master
※1 Windows環境の場合はデフォルトではスクリプトに実行権が付かないので、ここで
git update-index --chmod=+x stage/bin/[APPNAME]
を実行する必要があります。
このリポジトリはGitHub等の外部リポジトリにホストする必要はありません。コードさえあれば同じものをいつでも作れますし、別のマシンにコピーを作りたい場合はHerokuのgitからcloneしちゃえば良いので。
初回のコミットは標準ライブラリ等が山ほど入るので、少し遅い(それでもscala-buildpackよりは全然速い)ですが、標準ライブラリは基本変更されることがないので2回目以降のコミットでは自前のjarとconfファイル程度しか対象とはならないのでデプロイにかかる時間はわずか数秒です。素晴らしい。。。(^^v
初回はともかく2回目以降の手順はGrunt等で簡単にスクリプト化できるので、それなりに使い勝手は良いんじゃないですかね。(^^;
欠点はDashboardのActivityページからDiffが見られなくなることとかですかね。(GitHub Syncを使えばリリース毎のDiffがGitHubのページで見られます。)
あとはチーム開発時にデプロイ用のgitまで各メンバで同期する運用の場合、ちょっと面倒かもしれません。むしろCI連携で使った方が便利かも。。。
作ってはみたものの本当に使うかどうかはまだわかりませんが、興味のある方は触ってみてくださいな。(^^;
明日は@ponkotuyさんのMyFleetGirlsの話です。