以前、sbt-native-packagerでScalaのDockerイメージを作る方法を紹介した。今回はsbt-packを利用した、より軽量な手法を紹介する。
前回の記事はこちら:
sbt-pack
: 1つのディレクトリにまとめる君
sbt-pack
を聞いたことがなくても、sbt-assembly
について聞いたことがあるScalaエンジニアは多いと思う。今回紹介するsbt-pack
は、sbt-assembly
からUberjar*1を作成する機能を取り除いたような、シンプルなsbtプラグインだ。Scala 3のパッケージングにも使われているとのことで、信頼性は折り紙付きだ。
sbt-pack
の使い方は簡単だ。sbt pack
を実行すると、target/pack/
以下に、アプリケーションの実行に必要な全てのライブラリとアプリケーションコードが展開される。これだけだ。
$ sbt pack ... $ tree target/pack target/pack # 簡易的なMakefileとVERSIONファイルが入る ├── bin # ラッパースクリプトが入る └── lib # JARファイルがぞろぞろ入っている 3 directories
デフォルトでは、target/pack/bin/main
にmainメソッド実行用のラッパーが出力される。エントリポイントは複数指定でき、複数指定した場合はその数だけラッパースクリプトが配置されるという仕組みだ。
設定自体は簡単なので公式ページを参照されたい。最低限必要な設定は以下の通りだ:
// plugins.sbt // 執筆時点での最新版 addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.17")
// build.sbt
enablePlugins(PackPlugin)
これだけあればsbt pack
を実行可能だ。
sbt-pack
を使ってDockerイメージを作成する
sbt-pack
で作成されたファイル群をJavaのDockerイメージの上に載せれば、それだけでDockerファイルが完成する。この方式の利点は、圧縮工程が含まれない(後述)ためsbt-assembly
を使う手法よりも非常に速いという点だ。
例えば以下のようなDockerfile
を構成できる:
# Dockerfile # RUN THIS SCRIPT AFTER sbt pack. FROM amazoncorretto:17.0.8 WORKDIR /opt/docker COPY --chown=daemon:root /target/pack/bin /opt/docker/bin COPY --chown=daemon:root /target/pack/lib /opt/docker/lib RUN ["chmod", "u+x,g+x", "/opt/docker/bin/main"] USER daemon ENTRYPOINT ["sh", "/opt/docker/bin/main"] CMD ["some", "argument", "for", "entrypoint"]
このDockerfile
は、sbt pack
した結果をそっくりイメージに吸い上げる。簡単だ。このイメージを実行すると、あらかじめ定義しておいたmain
メソッドに引数(some argument for entrypoint
)が渡される。
ちなみにADD
コマンドは.tgz
形式の展開を行ってくれるため、以下のようなテクニカルな構成も可能だ*2:
// build.sbt // tar.gz内に中間ディレクトリを挟まず直接binやlibを配置する packArchiveStem := ""
$ sbt packArchiveTgz # target/project名-0.1.0-SNAPSHOT.tar.gzが生成される
# Dockerfile FROM amazoncorretto:17.0.8 WORKDIR /opt/docker # ADDがtar.gzを展開する ADD --chown=daemon:root /target/project名-0.1.0-SNAPSHOT.tar.gz /opt/docker/ RUN ["chmod", "u+x,g+x", "/opt/docker/bin/main"] USER daemon ENTRYPOINT ["sh", "/opt/docker/bin/main"] CMD ["some", "argument", "for", "entrypoint"]
何らかの都合でアーカイブをURL上に配置したい場合などに使えるかもしれない(だったらJARにしたほうがいいと思うけど)。
個人的には無圧縮TARを生成するタスクが定義されていると速度とハンドリングの簡易さを両立できて便利だと思う(コードを見たところ、そのようなタスクは定義されていないが、PRを作るのは容易そうに見える)。
sbt-assembly
との対比
sbt-pack
の特異な点は、実行に必要なファイルを集める一方、圧縮などを一切行なわないという点だ。これが速度に利している。JARファイルは実はZIPファイルなので、作成時に圧縮過程が入り時間を取られてしまう。sbt-pack
を使う場合はこの過程がまるっと不要になるので、CI/CD速度を上げられる。
加えて、JARファイルにパッケージする際に必ずと言ってもいいほど必要になるファイルコンフリクト時の処置についても考えなくて良いため、build.sbt
はかなりシンプルになる。
sbt-pack
が複数のエントリポイントにも対応しているのも嬉しいポイントだ。sbt-assembly
を使う場合はjava -jar
のかわりにjava -classpath
を使ってJARファイルを起動することになるだろう。
まとめ
ScalaのDockerイメージを構成したい場合はsbt-pack
をファーストチョイスとしても良いかもしれない。直交的でミニマルな優れたプラグインだ。よくメンテナンスされており実績もある。
参考文献
@taroleoさんにsbt-pack
を教えていただきました。ありがとうございました。
そういう理由で、自作のsbt-packプラグインを今までさして不便もなく使い続けてしまえています。依存ライブラリをまとめて簡単な起動スクリプト追加するだけなのですが。 https://t.co/PYaYP9dtJs
— Taro L. Saito (@taroleo) 2023年8月22日
Scalaアプリケーションを実行可能な状態でフォルダにまとめるsbt-pack。実はScala 3 (dotty)のパッケージングにも使われています。 Dockerイメージ作成にも便利。 https://t.co/Vc0WvpFIDM
— Taro L. Saito (@taroleo) 2023年8月22日