Lambdaカクテル

Common Lispを書くMT-03ライダー(初心者)です

ScalaのPlayアプリケーションをsbt-native-packagerの力を借りてDockerで動かす

おはこんばんちわ.普段はLispをやっていますが今日はScalaをやります.

わけあってPlayアプリケーションを構築しようとしているのですが,時代はクラウド.環境構築やデプロイであれこれ悩みたくありません.そういうわけで,Docker化を見越したアプリケーションを作ることにします.

素朴にDocker化しようとすると,まずalpineなりdebianなりのベースイメージにjdkとsbtをインストールして・・・という発想になるものですが,今回は違うアプローチをとります.というのも,Dockerの上でビルドツールを動かすのではなく, Dockerに対応したビルドツール(今回はsbt)がDockerfileを生成し,イメージまで作ってくれるのです.

Dockerを開発に導入するとき,これまでは既存の環境をそのままDocker化することが多かったように思います.というのもDockerは環境を構築してくれるツールとして振る舞うので,既存のビルドツールはその上に載せさえすれば動くからです.だから頑張ってDockerfileを書いてその上でビルドツールを動かし,volume mountなどを駆使して成果物を取り出す,といったやり方でビルドを行っていました.

ところが今回はビルドツールがDockerに対応しています.今回使うsbt-native-packagersbtのプラグインとして,Dockerイメージとしてプロジェクトをビルドできるようになります.今までありそうでなかった斬新なアプローチです. 斬新なだけではなく,ビルドツールをまるごとDocker環境内にカプセルしてしまうより,ビルドツールがDockerに対応してその中でビルドしてくれるほうが筋が良いアプローチだといえそうです.というのも,開発環境が持つ依存性やコンテナとしてパッケージしたい複雑性は,ビルドツール自身が持っているわけではなく,ビルドツールが生成する成果物や,それが依存するためにビルドツールがダウンロードするライブラリにあるからです*1. これからはDockerにネイティブに対応したビルドツールがたくさん現れるかもしれない(もしくはもうあるかも).

ビルドツールがDockerに対応していることにより,ビルドツールは発生する依存性や複雑性をDockerfileに押し込み,成果物をDockerイメージに閉じ込めます.とても便利. あとはこの手順をAWS CodeBuildにやらせてしまえば,Pushするだけで自動的にデプロイ可能なDockerイメージが焼き上がるという寸法です.テストもその中でやらせてしまえば良さそうです.

やったこと

プロジェクト作成

まずは新プロジェクトをsbt new playframework/play-scala-seed.g8で作成します.sbtはなんでもできてすごい.

$ sbt new playframework/play-scala-seed.g8

もろもろの質問に答えていくとプロジェクトディレクトリが作成されるので,ひとまずgit repositoryにしておくといいと思います.

$ cd ./testapp/
$ git init
$ git add .
$ git commit -m 'initial commit'

各種プラグイン追加&ビルド設定

sbtの依存性解決で発生するダウンロードは遅いことで有名ですが,それを並列にやってくれる便利なプラグインがあるので,導入します.

// project/plugins.sbt

addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.1.0-M13-4")

そして今回のキモであるsbt-native-packagerを導入します.

// project/plugins.sbt

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.21")

あとはビルドに必要な設定を行います.build.sbtを開き,以下の設定を追記します.

// Dockerパッケージングのための設定
enablePlugins(DockerPlugin) // sbt-native-packagerでdockerビルドを行う設定
dockerBaseImage := "openjdk:11" // デフォルトではopenjdk8になってしまうのでバージョンを指定する

// 実行時にPIDファイルを開こうとして失敗してしまう.
// Dockerの中ではPIDを心配することはないので使わないようにする
javaOptions in Universal ++= Seq(
  "-Dpidfile.path=/dev/null"
)

ここまでできたらPlayが正しく動作するための設定をやる. Playはproductionモードで動かすためにはapplication secretというデータが必要になる(これはセッションキーのsaltなどとして使われるようだ).このsecretはデフォルトでは設定されないので,以下のコマンドで自動的に設定させる.

$ sbt playUpdateSecret

これにより./conf/application.confにsecretが記録される.

ビルドしてみる

さてDockerイメージをターゲットとしてビルドするにはsbt docker:publishLocalコマンドを使う.

$ sbt docker:publishLocal

基本的にはこれだけ.うまくいくとDockerイメージが生成される.

$ docker images
(ここにプロジェクト名の名を冠したイメージが出現するはず)

./target/docker/stage以下にDockerfileが生成されているのも確認できるはずだ.

これを実行するには,このイメージを使って普通にDockerコンテナを起動すればよい. Playは9000番ポートを使うのでdockerコマンドランオプションで開けておく.

$ docker run -it --rm -p 9000:9000 testapp:1.0-SNAPSHOT

すると見事に動作するはずだ.

$ curl localhost:9000
...
Welcome to Play!

SUGEEEEEEE

まとめ

sbt-native-packagerすごい!!!!

sbt-native-packagerはすごくて,debファイルやrpmにもパッケージングできる.しかも開発元がTypesafeなので安心.

このままいい感じにCodeBuildとかに入れて遊びたい.ちなみにCodeBuildのJDKは11らしいので,手元とDockerfileとでバージョンをちゃんと合わせよう.

*1:もちろんビルドツール自体にバージョンをめぐる問題はあるかもしれませんが,全体の複雑性における割合はわずかなものでしょう