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