build smaller java runtime with sbt-native-packager & docker-plugin

You, sbt-native-packager
Back

Pre-requisites


Background


JDK Version 11+ 以降、JREの提供がデフォルトでされなくなっていることを背景に、 どうやって小さなコンテナイメージを作るかを調べて行き着いた方法。

jlink という仕組みの提供はあるものの、比較的設定項目が多く、 build設定の定期的なメンテナンスコストがかかりそうな印象があったため、

何も考えずJDK入りのイメージを使うのに比べて軽量で、 メンテナンスもさほどかからないだろうやり方のメモ。

Quick Summary


仮に、akka-http で作られたScalaアプリケーションがあるとして、 sbt-native-packager を使ってimageをbuildしたいとすると、以下が build.sbt で設定する方法の例。

lazy val api = Project("app", file("api"))
  .enablePlugins(JavaAppPackaging, AshScriptPlugin, DockerPlugin)
  .settings(
    version:= "latest",
    dockerCommands:= Seq(
      Cmd("FROM", "amazoncorretto:11-alpine as stage"),
      Cmd("RUN",  "rm -rf /usr/lib/jvm/java-11-amazon-corretto/jmods && rm -rf /usr/lib/jvm/java-11-amazon-corretto/lib/src.zip"),
      Cmd("FROM", "alpine:3.16.0 as main"),
      Cmd("WORKDIR", "/opt/docker"),
      Cmd("COPY", "1/opt/docker ."),
      Cmd("COPY", "2/opt/docker ."),
      Cmd("COPY", "--from=stage /usr/lib/jvm /usr/lib/jvm"),
      Cmd("ENV", "LANG C.UTF-8"),
      Cmd("ENV", "JAVA_HOME /usr/lib/jvm/default-jvm"),
      Cmd("ENV", "PATH $JAVA_HOME/bin:$PATH"),
      ExecCmd("ENTRYPOINT", s"/opt/docker/bin/${name.value}")
    )
  )
  .settings(
    libraryDependencies ++= Seq(
      "com.typesafe.akka" %% "akka-http"       % "10.1.12",
      "de.heikoseeberger" %% "akka-http-circe" % "1.33.0"
    )
  )
  1. DockerのMultiStageBuildを利用する
  2. stageフェーズではベースイメージとしてamazon-correttoを利用する
  3. ベースイメージからruntimeとしてはいらないファイルを削除する(これだけで 120MB 近く削れる)
  4. mainステージでアプリケーション本体を sbt-native-packagerdocker:stage の構造に合わせてコピー
  5. stageフェーズでダイエットしたjvmを移植する
  6. sbt-native-packager のアプリのパッケージングとしてのガワの部分を整えてimageを生成

Notes


jlink で一番面倒に思った部分は、--add-modules オプション。これがよしなにやってくれれば一番嬉しかったが、 一工夫必要にみえて、そこまではやらなくてもいいかなと尻込みしてしまった。

イメージのサイズを追求してしまうと、もう言語を変える方が賢明となりがちなので、 簡単に実現できるかどうかはやっぱり重要。

© Shuhei Kimura.RSS