普通のScalaアプリケーションをebuildでdeployする

会社ではほとんどのアプリケーションをScalaで開発しさくらのクラウドやハウジング環境で運用しているのですが、どんな言語で開発するにせよどのような環境で運用するにせよ、当然ながらアプリケーションはdeployする必要があります。
deployに関しては色々な方法があると思うのですが、私は「ミドルウェアのインストールやアップデートと同じように自社プロダクトもパッケージとしてdeployしたい」と考えていました。 つまりnginxやMariaDBをアップデートするように yum updateapt-get upgrade , emerge -uND world (「うどんアップデート」というらしいですね)すると自社プロダクトもアップデートされるということです。
そう!emerge !! portageパッケージ管理システムに則ってアプリのebuildを作ればいいではないですか!!! 1日に何回も何十回もcommitがpushされる自社プロダクトで、もし都度バイナリパッケージを作るとなるとパッケージ作成自体のコストが高く感じてしまいますが、ebuildなら「GitHubのリポジトリから直接masterのHEADを取得後コンパイルしインストールする」という手順自体をパッケージングできます! portageにはインストールすると同時にバイナリパッケージを作る機能もあるので、たとえScalaアプリのコンパイルに初回は1曲聴ける程度の時間がかかったとしても2回目以降はイントロを聴いている間にインストールできます!
ということでScalaアプリをebuildでdeployするようになってから数ヶ月経ち課題を残しつつも安定してきたので概要を書き留めておきます。
なお、世間にはGentoo Linuxでサービス運用している会社が少なくとも2社あるようです。
2000年以前はおそらくどこにも採用されていなかったことを考えると近年の導入会社数の伸び率は目覚ましく、ひとりのGentoo Linuxユーザーとしてもうれしい限りです。

流れ

例としてHelloPlayというPlayFrameworkを使ったScalaのサンプルアプリケーションをebuildでパッケージングしてみます。 インストール先はさくらのクラウドですが、今回プラットフォーム固有の機能は使っていないのでクラウドでもハウジングでも差異はありません。 また、バージョンとして下記2つを想定します。
  • 安定版である 0.0.1 → tag v0.0.1 がインストールされる
  • 開発版である 9999 → その時点のmasterのHEADがインストールされる
なおこのアプリケーションでは v0.0.1 に対して masterGET /hello というエンドポイントが追加されており、リクエストを受けるとステータス200で"Hello"というメッセージを返す機能が追加されています。 差分はこんな感じです。

ebuild作成

このアプリケーションを www-apps/playscala-example というパッケージにします。 こんなebuildを書いてみました。 また、 application.conf の雛形や起動スクリプトなども作成します。
ebuild自体の差分としては開発版である 9999 には ~amd64 キーワードを指定しますが、安定版である 0.0.1 にはキーワードを指定しません。 ebuild内でバージョンを判定することにより 0.0.1 ではtagを、 9999 ではmasterのHEADを取得するよう振る舞いを分けます。
if [[ ${PV} != 9999 ]]; then
    # set tag as "EGIT_COMMIT"
    EGIT_COMMIT="v${PV}"
    SLOT=$(get_version_component_range 1-2)
fi

リポジトリ登録

内容はこのようなXMLファイルです。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE repositories SYSTEM "/dtd/repositories.dtd">
<repositories xmlns="" version="1.0">
  <repo quality="experimental" status="unofficial">
    <name><![CDATA[mazgi-experimental]]></name>
    <description lang="en"><![CDATA[An overlay by @mazgi]]></description>
    <homepage>http://mazgi.com/</homepage>
    <owner type="person">
      <email>MATSUKI.Hidenori@gmail.com</email>
      <name><![CDATA[Hidenori MATSUKI]]></name>
    </owner>
    <source type="git">git://github.com/mazgi/portage-overlay.git</source>
  </repo>
</repositories>
このrepositories.xml/etc/portage/repos.confに今回はmazgi-experimental.confという名前で配置します。 内容はこんな感じです。
$ cat /etc/portage/repos.conf/mazgi-experimental.conf
[mazgi-experimental]
location = /var/lib/portage/repo.data/mazgi-experimental
sync-type = git
sync-uri = git://github.com/mazgi/portage-overlay.git
auto-sync = yes
またScalaアプリケーションをsbtでビルドするようebuildを書いたので、sbtが必要になりますが、sbtのebuildパッケージも作成して同じリポジトリに入れています。 普通にsbtのebuildとして使えますのでもし必要でしたら上記の設定を行って emerge sbt-bin してみてください。

インストール

まずは sync して先ほど設定したリポジトリを取得します。
$ sudo emaint sync -r mazgi-experimental
>>> Syncing repository 'mazgi-experimental' into '/var/lib/portage/repo.data/mazgi-experimental'...
/usr/bin/git pull
Already up-to-date.
=== Sync completed for mazgi-experimental

Action: sync for repo: mazgi-experimental, returned code = 0
またJDK周りのUSEフラグを必要に応じて変更しておきます。
$ sudo flaggie dev-java/oracle-jdk-bin -awt -fontconfig
$ sudo flaggie dev-java/icedtea-bin -X -alsa -cups
ここまでやって emerge playscala-example すると下記のようにJDKやsbt等必要なパッケージを含めて全てがリストアップされます。
$ sudo emerge -uav playscala-example

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild  N     ] media-libs/giflib-4.1.6-r3::gentoo  USE="-X -rle -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] app-eselect/eselect-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-util/patchelf-0.8::gentoo  0 KiB
[ebuild  N     ] media-libs/libpng-1.6.16:0/16::gentoo  USE="-apng (-neon) -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] media-libs/lcms-2.6-r1:2::gentoo  USE="threads zlib -doc -jpeg -static-libs {-test} -tiff" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] dev-java/java-config-wrapper-0.16::gentoo  0 KiB
[ebuild  N     ] sys-apps/baselayout-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-lang/nasm-2.11.08::gentoo  USE="-doc" 0 KiB
[ebuild  N     ] dev-java/java-config-2.2.0:2::gentoo  PYTHON_TARGETS="python2_7 python3_4 -python3_3" 0 KiB
[ebuild  N     ] gnome-base/gsettings-desktop-schemas-3.14.2::gentoo  USE="-introspection" 0 KiB
[ebuild  N     ] media-libs/libjpeg-turbo-1.3.1::gentoo  USE="-java -static-libs" ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N     ] virtual/jpeg-62:62::gentoo  ABI_X86="(64) -32 (-x32)" 0 KiB
[ebuild  N f   ] dev-java/oracle-jdk-bin-1.8.0.51:1.8::gentoo  USE="-alsa -awt -cups -derby -doc -examples -fontconfig -javafx -jce -nsplugin -pax_kernel (-selinux) -source" 0 KiB
[ebuild  N    ~] virtual/jdk-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] dev-java/icedtea-bin-7.2.5.5:7::gentoo  USE="-X -alsa -cjk -cups -doc -examples -nsplugin -pulseaudio (-selinux) -source -webstart" 0 KiB
[ebuild  N     ] virtual/jdk-1.7.0:1.7::gentoo  0 KiB
[ebuild  N    ~] virtual/jre-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] virtual/jre-1.7.0:1.7::gentoo  0 KiB
[ebuild  N     ] dev-java/sbt-bin-0.13.7:0.13::mazgi-experimental  0 KiB
[ebuild  N     ] www-apps/playscala-example-0.0.1:0.0::mazgi-experimental  USE="autoclean symlink -doc" 0 KiB

Total: 20 packages (20 new), Size of downloads: 0 KiB
Fetch Restriction: 1 package

The following keyword changes are necessary to proceed:
 (see "package.accept_keywords" in the portage(5) man page for more details)
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
=virtual/jdk-1.8.0 ~amd64
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
=virtual/jre-1.8.0 ~amd64

The following license changes are necessary to proceed:
 (see "package.license" in the portage(5) man page for more details)
# required by virtual/jdk-1.8.0::gentoo
# required by virtual/jre-1.8.0::gentoo
# required by www-apps/playscala-example-0.0.1::mazgi-experimental
# required by playscala-example (argument)
>=dev-java/oracle-jdk-bin-1.8.0.51 Oracle-BCLA-JavaSE

Would you like to add these changes to your config files? [Yes/No] 
JDK 1.8 のmaskを外し、Oracle JDKのライセンスに同意し、インストールを進めます。
$ sudo dispatch-conf
$ sudo emerge -uav playscala-example

These are the packages that would be merged, in order:

Calculating dependencies... done!
[ebuild  N     ] app-eselect/eselect-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-java/java-config-wrapper-0.16::gentoo  0 KiB
[ebuild  N     ] sys-apps/baselayout-java-0.1.0::gentoo  0 KiB
[ebuild  N     ] dev-java/java-config-2.2.0:2::gentoo  PYTHON_TARGETS="python2_7 python3_4 -python3_3" 0 KiB
[ebuild  N f   ] dev-java/oracle-jdk-bin-1.8.0.51:1.8::gentoo  USE="-alsa -awt -cups -derby -doc -examples -fontconfig -javafx -jce -nsplugin -pax_kernel (-selinux) -source" 0 KiB
[ebuild  N    ~] virtual/jdk-1.8.0:1.8::gentoo  0 KiB
[ebuild  N    ~] virtual/jre-1.8.0:1.8::gentoo  0 KiB
[ebuild  N     ] dev-java/sbt-bin-0.13.7:0.13::mazgi-experimental  0 KiB
[ebuild  N     ] www-apps/playscala-example-0.0.1:0.0::mazgi-experimental  USE="autoclean symlink -doc" 0 KiB

Total: 9 packages (9 new), Size of downloads: 0 KiB
Fetch Restriction: 1 package

Would you like to merge these packages? [Yes/No] 
インストールできたら application.conf を作成し、startしてみます。
$ sudo cp /etc/playscala-example/application.conf{.example,}
$ sudo /etc/init.d/playscala-example start
 * Starging playscala-example ...                                                     [ ok ]
$ /etc/init.d/playscala-example status
 * status: started
$ curl -L 'localhost:9000/' -o /dev/null -w '%{http_code}\n' -s
200
無事 / へのGETが200を返すことを確認できました!

アップデート

安定版である 0.0.1 がインストールできました。
$ curl -L 'localhost:9000/hello' -o /dev/null -w '%{http_code}\n' -s
404
しかしながら 0.0.1/hello へのGETが404になってしまいます。 そこで、今度は開発版の 9999 にアップデートしてみます。
9999 は特定のtagを参照しているわけではなくemergeした時点でのmasterのHEADがインストールされます。
$ sudo emerge -uavq \=www-apps/playscala-example-9999
[ebuild  NS   ] www-apps/playscala-example-9999 [0.0.1] USE="autoclean -doc symlink" 

The following keyword changes are necessary to proceed:
 (see "package.accept_keywords" in the portage(5) man page for more details)
# required by =www-apps/playscala-example-9999 (argument)
=www-apps/playscala-example-9999 ~amd64

Would you like to add these changes to your config files? [Yes/No] 

Autounmask changes successfully written.

 * IMPORTANT: config file '/etc/portage/package.accept_keywords/00_default' needs updating.
 * See the CONFIGURATION FILES section of the emerge
 * man page to learn how to update config files.
~amd64 でマスクされていますので外します。
$ sudo dispatch-conf
マスクを外したら改めてemergeします。
$ USE="-symlink" sudo emerge -uavq \=www-apps/playscala-example-9999
[ebuild  NS   ] www-apps/playscala-example-9999 [0.0.1] USE="autoclean -doc -symlink" 

Would you like to merge these packages? [Yes/No] 
このパッケージは /var/lib/playscala-example/versions 以下にバージョン毎にsymlinkを作る構造になっているのでsymlinkを貼り替えます。
$ cd /var/lib/playscala-example/versions
/var/lib/playscala-example/versions ~
$ sudo rm current
$ sudo ln -s playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08 current
$ sudo chown play:play -h current
$ ls -l
total 4
lrwxrwxrwx 1 play play  63 Aug 31 16:19 current -> playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08/
drwxr-xr-x 1 play play 100 Aug 31 03:10 playscala-example.0.0.1.9e023916c4c911408145ff5f181a42543fbced4a/
drwxr-xr-x 1 play play 102 Aug 31 16:12 playscala-example.9999.957b03072271df6586c1e6db8b2b76b4be5e3f08/
サービスを再起動します。
$ sudo /etc/init.d/playscala-example restart
 * Stopping playscala-example ...                                                     [ ok ]
 * Starging playscala-example ...                                                      [ ok ]
$ sudo /etc/init.d/playscala-example status
 * status: started
/hello へのGETが通るようになったか確認してみます。
$ curl -L 'localhost:9000/' -o /dev/null -w '%{http_code}\n' -s
200
$ curl -L 'localhost:9000/hello' -w '\n%{http_code}\n'
Hello
200
無事アップデートされ /hello へのGETが200を返すことが確認できました!

まとめのかわりに

以上、さらっと普通のScalaアプリケーションをebuildでdeployする方法を書いてみたのですが、これ、「よーし!当社もGentooにしたしちょっとebuild作成に挑戦するぞ!」っていう方にとっては暗黙知が多すぎて伝わらないですね。
自分でフラグ立ててしまったので回収しつつ、もう少し順を追って書いてみようと思います。
まずは自社サービスのebuild化とdeployについて会社のブログに書くところからかな...!