Scalaで作ったWebアプリをDockerizeして動かす
2018-02-09
QiitaScalasbtDockerこの記事はなに
タイトル通り。
主に sbt-native-packager のとりあえずの使い方紹介。
Akka-HTTP で Web アプリを実装し、sbt-native-packager を使って Docker イメージを作成、localhost で稼働させて HTTP リクエストを受け付けられるようにするまで。
環境
- Scala 2.12.4
- sbt 1.0.4
- Akka-HTTP 10.0.10
- sbt-native-packager 1.3.3
Scala の Web アプリ
build.sbt には Akka-HTTP の依存を追加しておく。
libraryDependencies += "com.typesafe.akka" %% "akka-http" % "10.0.10",
続いて動かす main クラスの実装。 実装自体は何でもよいので、シンプルに。
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.http.scaladsl.server._
import akka.stream.ActorMaterializer
import scala.concurrent.Await
import scala.concurrent.duration.Duration
object main extends App with Directives {
implicit val system: ActorSystem = ActorSystem("my-sample-app")
implicit val materializer: ActorMaterializer = ActorMaterializer()
// GET /indexでリクエストのURLパラメータとUserAgentを返却する
val route: Route =
(get & pathPrefix("index") & extractUri & headerValueByName("User-Agent")) {
(uri, ua) =>
logRequestResult("/index", Logging.InfoLevel) {
complete(s"param: ${uri.query().toMap}, user-agent: ${ua}}")
}
}
val host = sys.props.get("http.host") getOrElse "0.0.0.0"
val port = sys.props.get("http.port").fold(8080) { _.toInt }
val f = Http().bindAndHandle(route, host, port)
println(s"server at [$host:$port]")
Await.ready(f, Duration.Inf)
}
Akka-HTTP のHttp().bindAndHandleした結果をAwait.readyで無限に待つ。
簡単に動かすだけならこれでよい。
sbt runすれば起動するはず。
build.sbt に dockerize の設定を書く
まずは project/plugins.sbt に以下を書く
// https://github.com/sbt/sbt-native-packager
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.3")
続いて build.sbt に以下のように設定を記述する
enablePlugins(JavaAppPackaging)
// enablePlugins(JavaServerAppPackaging) // どっちでも動く
// Dockerfileに書く内容
packageName in Docker := "sample-webapp"
version in Docker := "1.0"
dockerRepository := Some("petitviolet")
maintainer in Docker := "petitviolet <mail@example.com>"
dockerExposedPorts := List(8080)
dockerBaseImage := "openjdk:latest"
dockerCmd := Nil
Dockerfile を書いたことがあれば何となくわかるはず。
in Dockerって書くかどうかが若干難しいが、dockerXxxなら不要でそうじゃないなら必要、くらいでよいはず(間違ってたら指摘して欲しいです)。
これでpetitviolet/sample-webapp:1.0としてイメージを作ることが出来る。
Docker イメージを作る
sbt でコマンドを実行するだけで良い
sbt 'project main' 'docker:publishLocal'
docker:publishLocalで docker build してくれる。
結果を見るにはdocker imagesすればいい。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
petitviolet/sample-webapp 1.0 5032e8dc5fa9 1 minutes ago 800MB
ちなみにこの際にdocker buildで使用する Dockerfile がtarget/docker/stage配下に生成されていて、中を見るとこうなっている。
FROM openjdk:latest
LABEL MAINTAINER="petitviolet <mail@example.com>"
WORKDIR /opt/docker
ADD opt /opt
RUN ["chown", "-R", "daemon:daemon", "."]
EXPOSE 8080
USER daemon
ENTRYPOINT ["bin/main"]
CMD []
ADDする opt ディレクトリ内には起動用の shell スクリプト(bin/main)と、依存ライブラリ(lib)が入っている。
ENTRYPOINT について
実行可能な main クラスが 1 つしかなければ、target/docker/stage/opt/docker/bin/mainに実行ファイルが置かれる。
そのためENTRYPOINT ["bin/main"]となっていて動くようになっている。
複数の main クラスが存在した場合、たとえばnet.petitviolet.MainServerとnet.petitviolet.MainClientみたいにあった場合、
target/docker/stage/opt/docker/bin/配下にmain-serverとmain-clientという 2 つの実行ファイルが配置される。
しかし、ENTRYPOINTはbin/mainを動かそうとするため、docker runするとコケてしまう。
これに対する解決策は 2 つある。
-
main クラスを指定する
mainClass in Compile := Some("net.petitviolet.MainServer")とすれば、bin/mainで実行可能
-
ENTRYPOINTを指定するdockerEntryPoint := List("bin/main-server")として実行するファイルを決め打ちする
前者の方が実行ファイル名に意識を向けなくてもよいため楽でいいが、ローカルで複数の main クラスを動かしたい時にrunMainしないといけなくて面倒になる。
一方で後者は実行ファイル名に意識を向ける必要があってちょっと歪になってしまうかも。
mainClassの指定もdockerEntryPointの指定も、文字列でミスっていた場合にはdocker runするまでわからないため、ミスの怖さは同等。
ローカルで起動する
普通にdocker run -dすればデーモン起動する。
$ docker run -d -p 8080:8080 --name sample petitviolet/sample-webapp:1.0
9fc179ccfc37b654d766b1eeda944e5f17e29d6b1d51ea08c2322eabc873ba8b
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9fc179ccfc37 petitviolet/sample-webapp:1.0 "bin/main" 7 seconds ago Up 1 second 0.0.0.0:8080->8080/tcp sample
リクエストを送ってみる。
$ curl 'localhost:8080/index?key=value' -A hoge
param: Map(key -> value), user-agent: hoge}
普通に標準出力に垂れ流しているアプリケーションログはdocker logsをすれば見ることが可能。
from: https://qiita.com/petitviolet/items/cf5d699521b08e6ec933