jibを使ってJavaアプリケーションのDockerイメージをビルドする

jibを使ってJavaアプリケーションのDockerイメージをビルドする
目次

実案件でもJavaアプリケーションをDockerコンテナ上で稼働させる事例もかなり増えていますね。 今回は Jib を使ったDockerfileイメージのビルドを紹介します。

モチベーション

アプリケーションのビルドとDockerイメージのビルドをいい感じに統合したい

最終的な実行可能なDockerイメージを作成するために、定義ファイルを複数管理する というのはまどろっこしいです。 可能であれば1つにしたいです。その場の解として2つになってしまうことはありますけど。

ビルド定義が複数あるということは、複数のビルドコマンドを実行する必要があり、つまり、ビルドの順序を意識する必要があるということです。

Javaアプリケーションの場合、RubyやPythonなどのスクリプト言語と異なり、アプリケーションのビルドという前工程があり、 その後、Dockerfile内の COPY コマンドにて、ビルド成果物をコンテナ内にコピーすることで、ようやくDockerイメージを作成できるわけです。

ビルドにおける一連の流れを、列挙すると以下のようになります。

  1. 依存モジュールの解決とインストール
  2. アプリケーションのビルド
  3. Dockerイメージのビルド

3 の部分をGradleやMavenの定義ファイルに移動できれば、管理対象ファイルを減らすことができるので画期的ですよね。

jibとは

jib は Google のエンジニアが実装した JavaアプリケーションのためのOCI(Docker)イメージビルドツールです。

MavenやGradleのプラグインが提供されていて、従来のJavaアプリケーションのビルド定義ファイル( pom.xmlbuild.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つをゴールにします。

  1. jibでSpringbootのアプリケーションのDockerイメージを作成する
  2. 作成したDockerイメージをAWS ECRにpushする

環境の準備

以下のような環境で試しています。

  • MacOSX
  • Java SE: 1.8.0_152
  • Gradle: 4.8.1
    • jib-gradle-plugin: 0.9.6

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コンソール上から確認してみましょう。

ecr_repo

すばらしい!

まとめ

今回はjibを触ってみました。まだメジャーバージョン1には到達していないので、機能的に不足しているところもあるかもしれませんが、Dockerイメージのビルドとpushが簡単にできるので、今後の動向には注目したいところです。

Javaのプロセスを確認したところ、クラスパスを通したアプリケーション起動をしていました。Java 9以降で導入されているModule Pathを使った場合のアプリケーション起動なども実装されていくと、更に実用性が上がるかもしれませんね。