jibを使ってJavaアプリケーションのDockerイメージをビルドする
実案件でもJavaアプリケーションをDockerコンテナ上で稼働させる事例もかなり増えていますね。 今回は Jib を使ったDockerfileイメージのビルドを紹介します。
モチベーション
アプリケーションのビルドとDockerイメージのビルドをいい感じに統合したい
最終的な実行可能なDockerイメージを作成するために、定義ファイルを複数管理する というのはまどろっこしいです。 可能であれば1つにしたいです。その場の解として2つになってしまうことはありますけど。
ビルド定義が複数あるということは、複数のビルドコマンドを実行する必要があり、つまり、ビルドの順序を意識する必要があるということです。
Javaアプリケーションの場合、RubyやPythonなどのスクリプト言語と異なり、アプリケーションのビルドという前工程があり、
その後、Dockerfile内の COPY
コマンドにて、ビルド成果物をコンテナ内にコピーすることで、ようやくDockerイメージを作成できるわけです。
ビルドにおける一連の流れを、列挙すると以下のようになります。
- 依存モジュールの解決とインストール
- アプリケーションのビルド
- Dockerイメージのビルド
3
の部分をGradleやMavenの定義ファイルに移動できれば、管理対象ファイルを減らすことができるので画期的ですよね。
jibとは
jib は Google のエンジニアが実装した JavaアプリケーションのためのOCI(Docker)イメージビルドツールです。
MavenやGradleのプラグインが提供されていて、従来のJavaアプリケーションのビルド定義ファイル( pom.xml
や build.gradle
)中にOCIイメージのビルド定義、リポジトリへのpush処理を集約できます。
jibではプロダクトの目的として以下の3つを謳っているのですが、dockerデーモンを起動しなくてもイメージが焼けるのは個人的には嬉しいです。
・Fast - Deploy your changes fast. Jib separates your application into multiple layers, splitting dependencies from classes. Now you don’t have to wait for Docker to rebuild your entire Java application - just deploy the layers that changed.
・Reproducible - Rebuilding your container image with the same contents always generates the same image. Never trigger an unnecessary update again.
・Daemonless - Reduce your CLI dependencies. Build your Docker image from within Maven or Gradle and push to any registry of your choice. No more writing Dockerfiles and calling docker build/push.
やってみる
ゴール設定
今回は以下の2つをゴールにします。
- jibでSpringbootのアプリケーションのDockerイメージを作成する
- 作成したDockerイメージをAWS ECRにpushする
環境の準備
以下のような環境で試しています。
- MacOSX
- Java SE:
1.8.0_152
- Gradle:
4.8.1
- jib-gradle-plugin:
0.9.6
- jib-gradle-plugin:
build.gradleの編集
まずは、 build.gradle
を編集しましょう。以下のような感じで build.gradle
を編集します。
1buildscript {
2 repositories {
3 maven {
4 url 'http://repo.spring.io/plugins-release'
5 }
6 maven {
7 url "https://plugins.gradle.org/m2/"
8 }
9 }
10 dependencies {
11 classpath "gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:0.9.6"
12 classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: "2.0.3.RELEASE"
13 }
14}
15
16apply plugin: 'java'
17apply plugin: 'idea'
18apply plugin: 'eclipse'
19apply plugin: 'org.springframework.boot'
20apply plugin: 'com.google.cloud.tools.jib'
21
22sourceCompatibility = 1.8
23targetCompatibility = 1.8
24[ compileJava, compileTestJava ]*.options*.encoding = 'UTF-8'
25
26repositories {
27 mavenCentral()
28}
29
30jib {
31 from {
32 image = 'openjdk:alpine'
33 }
34 to {
35 image = getProperty('aws.accountid') + ".dkr.ecr." + getProperty('aws.region') + ".amazonaws.com/jib-test"
36 credHelper = 'ecr-login'
37 }
38 container {
39 jvmFlags = ['-Xms512m', '-Xms1g', '-Xmx1g', '-Xss10m', '-XX:MaxMetaspaceSize=1g']
40 mainClass = 'com.soudegesu.example.MainApplication'
41 args = []
42 ports = ['8080']
43 format = 'OCI'
44 }
45}
46
47ext {
48 verSpringboot = '2.0.3.RELEASE'
49}
50
51dependencies {
52 compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: verSpringboot
53}
Dockerイメージのビルド
まずは、Dockerイメージのビルドをしてみましょう。 イメージのビルドには不要ですが、ビルドが失敗するので実行引数にプロファイルを指定します。
1./gradlew jibDockerBuild -Paws.accountid=${awsアカウントID} -Paws.region=${awsのリージョン}
完成したイメージを確認します。
1docker images
2
3> REPOSITORY TAG IMAGE ID CREATED SIZE
4> jib-test unspecified 155f1b17a8bc 48 years ago 119MB
「CREATED」が 48 years ago なのが少し気になりますが、そのうち改善されるでしょう。(きっと)
作成したイメージからコンテナをローカルで起動してみます。
1docker run -p 8080:8080 -it 155f1b17a8bc
2
3 . ____ _ __ _ _
4 /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
5( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
6 \\/ ___)| |_)| | | | | || (_| | ) ) ) )
7 ' |____| .__|_| |_|_| |_\__, | / / / /
8 =========|_|==============|___/=/_/_/_/
9 :: Spring Boot :: (v2.0.3.RELEASE)
10
112018-07-18 00:34:52.773 INFO 1 --- [ main] com.soudegesu.example.MainApplication : Starting MainApplication on 1b3277472466 with PID 1 (/app/classes started by root in /)
122018-07-18 00:34:52.780 INFO 1 --- [ main] com.soudegesu.example.MainApplication : No active profile set, falling back to default profiles: default
13(以下略)
プロセスを見てみる
springbootで実装したアプリケーションをイメージに入れたわけですが、特に executable-jar を作るタスクを実行したわけではありません。 dockerコンテナの中に入って、プロセスを確認してみましょう。
1ps ax | grep java
2
3> java -Xms512m -Xms1g -Xmx1g -Xss10m -XX:MaxMetaspaceSize=1g -cp /app/libs/*:/app/resources/:/app/classes/ com.soudegesu.example.MainApplication
なるほど。 /app
の各ディレクトリ配下にクラスパスを通して、指定されたMainクラスを実行しているのですね。
言われてみれば、別にfat-jarは必須じゃないので、これでも良い気がします。
DockerイメージのAWS ECRへのpush
次にAWS ECRにビルドしたイメージをpushしてみましょう。
事前に amazon-ecr-credential-helper をインストールする必要があります。
以下コマンドを実行してみます。
1./gradlew jib -Paws.accountid=${awsアカウントID} -Paws.region=${awsのリージョン} --stacktrace
2
3> Containerizing application to ${awsアカウントID}.dkr.ecr.${awsのリージョン}.amazonaws.com/jib-test...
4>
5> Retrieving registry credentials for ${awsアカウントID}.dkr.${awsのリージョン}.amazonaws.com...
6> Getting base image openjdk:alpine...
7> Building dependencies layer...
8> Building resources layer...
9> Building classes layer...
10> Retrieving registry credentials for registry.hub.docker.com...
11> Finalizing...
12>
13> Container entrypoint set to [java, -Xms512m, -Xms1g, -Xmx1g, -Xss10m, -XX:MaxMetaspaceSize=1g, -cp, /app/libs/*:/app/resources/:/app/classes/, > com.soudegesu.example.MainApplication]
14>
15> Built and pushed image as ${awsアカウントID}.dkr.${awsのリージョン}.amazonaws.com/jib-test
16>
17> Deprecated Gradle features were used in this build, making it incompatible with Gradle 5.0.
18> See https://docs.gradle.org/4.8/userguide/command_line_interface.html#sec:command_line_warnings
19>
20> BUILD SUCCESSFUL in 22s
21> 2 actionable tasks: 1 executed, 1 up-to-date
イメージをAWSコンソール上から確認してみましょう。
すばらしい!
まとめ
今回はjibを触ってみました。まだメジャーバージョン1には到達していないので、機能的に不足しているところもあるかもしれませんが、Dockerイメージのビルドとpushが簡単にできるので、今後の動向には注目したいところです。
Javaのプロセスを確認したところ、クラスパスを通したアプリケーション起動をしていました。Java 9以降で導入されているModule Pathを使った場合のアプリケーション起動なども実装されていくと、更に実用性が上がるかもしれませんね。