Java
gradle
docker
コンテナ
Jib

JibでJavaアプリを手軽にDockerコンテナ化してみる〜gradleでビルド、ローカルリポジトリに登録

概要

このエントリでは、Googleが公開しているJibを利用して、Javaアプリケーションを簡単にDockerコンテナ化する手順について試してみます。

確認している内容は、JibのGradleプラグインサイトにあるものとほぼ同等ですが、下記2点が異なります。

  • jibDockerBuildの方を先に試しています(プライベートリポジトリを試すのを後述としたため)。
  • Dockerコンテナの格納先として、{GCR, ACE, Docker Hub Registry}でなく、プライベートリポジトリを使用した例となります。

前提

ミドルウェア、ライブラリは、以下を利用しています。
- OS: macOS 10.13.2
- JDK: 1.8.0_171
- Jib-Gradle-Plugin: 0.9.4
- Docker: Version 18.03.1-ce-mac65 (24312)
- Gradle(4.6以上)
- Macbook Pro(15-inch, Mid 2014)

対象読者

以下の読者を対象とします。
- Java、Spring Frameworkについて知識があり、"Hello World"程度のアプリをビルドツールでビルド、実行した経験がある方
- OSのターミナル上でコマンドラインを利用した作業ができる方
- JavaアプリケーションのDockerコンテナ化に興味がある方

関連エントリ

2018/7/16時点で、Qiita上には、Jibのキーワードで下記エントリがありました。

開発環境の準備

JDK

配布元のサイトから「jdk-8u171-macosx-x64.dmg」を取得し、dmgファイルからインストーラを起動します。

spring (CLI)

サンプルアプリを作るためのコマンドラインプログラムをインストールします。(これは必須ではありません。GitHub上のサンプルを参照いただくだけで十分です。)

sdkmanでインストールします。

$ sdk install springboot

アプリケーションを用意する

Spring BootのCLIを利用して、Gradleでビルドできるアプリケーションのソースコードを準備します。

$ spring init -d=web --build=gradle jib-test
Using service at https://start.spring.io
Project extracted to '/Users/hiroki/jib-test'

改修

プロジェクトのメインクラスを以下のように改修し、RestControllerとして機能するようにしました。(手抜きですみません)
このソースはタグ1.0としてGitHub上に格納してあります。

package com.example.jibtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {

    @RequestMapping("/")
    @GetMapping
    public String hello() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

アプリケーションが実行できることを確認する

gradlewでアプリケーションを起動します。

$ cd jib-test
$ ./gradrew bootRun

ローカル環境の8080ポートでアプリケーションが起動していることを確認します。

$ curl http://localhost:8080/
Hello World!

ビルド

ここまで準備できていてDockerデーモンが動いている前提で、gradleを使ってコンテナをビルドします。

「gcr.io/distroless/java」が利用されていることがわかります。

$ gradle jibDockerBuild
Tagging image with generated image reference jib-test:0.0.1-SNAPSHOT. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --image=<MY IMAGE> commandline flag.
warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible

Containerizing application to Docker daemon as jib-test:0.0.1-SNAPSHOT...

Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...
Loading to Docker daemon...
(以下略)

イメージができていることを確認します。(うーん、タイムスタンプがおかしいのはご愛嬌、でしょうか。今年が2018年だから、計算機的になんとなく1970年からの引き算をしてしまったように見えますね。)。サイズは134MBでした。

$ docker images | grep jib-test
jib-test            0.0.1-SNAPSHOT      2ce8c6f47c83        48 years ago        134MB

起動してみます。ポート8080をホストに公開することと、Gradleプラグインでビルドされたバージョン「0.0.1-SNAPSHOT 」を指定していることに注意してください。

$ docker run -p 8080:8080 -it jib-test:0.0.1-SNAPSHOT 

コンテナ化する前と同じ方法で、アプリケーションが動作していることを確認します。

$ curl http://localhost:8080/
Hello World!

いい感じです。ここまでのプロジェクトを「Add-Gradle-Plugin」としてGitHubに格納しています。

distrolessの上で動いているので、JavaVMのバイナリは存在しますが、

$ docker exec -it 481fc8ea9d2a java -version
openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-1~deb9u1-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)

/bin/bashは存在しません。

$ docker exec -it 481fc8ea9d2a /bin/bash
OCI runtime exec failed: exec failed: container_linux.go:348: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown

buildディレクトリ以下に、jib-cacheというディレクトリができており、その中にサイズが十数MBあるtar.gzファイルがありました。

build/jib-cache以下のtar.gzファイルの中身
$ tar tvf dc3bf5c6afa437de068b6d692f7bd72e29fd7501577c2886c946f719fc3be05f.tar.gz
-rw-r--r--  0 0      0       65100  1  1  1970 app/libs//classmate-1.3.4.jar
-rw-r--r--  0 0      0     1133563  1  1  1970 app/libs//hibernate-validator-6.0.10.Final.jar
-rw-r--r--  0 0      0       66519  1  1  1970 app/libs//jackson-annotations-2.9.0.jar
-rw-r--r--  0 0      0      323848  1  1  1970 app/libs//jackson-core-2.9.6.jar
-rw-r--r--  0 0      0     1349339  1  1  1970 app/libs//jackson-databind-2.9.6.jar
-rw-r--r--  0 0      0       33395  1  1  1970 app/libs//jackson-datatype-jdk8-2.9.6.jar
-rw-r--r--  0 0      0       99987  1  1  1970 app/libs//jackson-datatype-jsr310-2.9.6.jar
-rw-r--r--  0 0      0        8645  1  1  1970 app/libs//jackson-module-parameter-names-2.9.6.jar
-rw-r--r--  0 0      0       26586  1  1  1970 app/libs//javax.annotation-api-1.3.2.jar
-rw-r--r--  0 0      0       66469  1  1  1970 app/libs//jboss-logging-3.3.2.Final.jar
-rw-r--r--  0 0      0        4596  1  1  1970 app/libs//jul-to-slf4j-1.7.25.jar
-rw-r--r--  0 0      0      255485  1  1  1970 app/libs//log4j-api-2.10.0.jar
-rw-r--r--  0 0      0       17519  1  1  1970 app/libs//log4j-to-slf4j-2.10.0.jar
-rw-r--r--  0 0      0      290339  1  1  1970 app/libs//logback-classic-1.2.3.jar
-rw-r--r--  0 0      0      471901  1  1  1970 app/libs//logback-core-1.2.3.jar
-rw-r--r--  0 0      0       41203  1  1  1970 app/libs//slf4j-api-1.7.25.jar
-rw-r--r--  0 0      0      297518  1  1  1970 app/libs//snakeyaml-1.19.jar
-rw-r--r--  0 0      0      366340  1  1  1970 app/libs//spring-aop-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0      660545  1  1  1970 app/libs//spring-beans-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0      930680  1  1  1970 app/libs//spring-boot-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0     1162436  1  1  1970 app/libs//spring-boot-autoconfigure-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0         592  1  1  1970 app/libs//spring-boot-starter-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0         645  1  1  1970 app/libs//spring-boot-starter-json-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0         613  1  1  1970 app/libs//spring-boot-starter-logging-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0         591  1  1  1970 app/libs//spring-boot-starter-tomcat-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0         588  1  1  1970 app/libs//spring-boot-starter-web-2.0.3.RELEASE.jar
-rw-r--r--  0 0      0     1090739  1  1  1970 app/libs//spring-context-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0     1226075  1  1  1970 app/libs//spring-core-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0      280032  1  1  1970 app/libs//spring-expression-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0       21703  1  1  1970 app/libs//spring-jcl-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0     1254656  1  1  1970 app/libs//spring-web-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0      789866  1  1  1970 app/libs//spring-webmvc-5.0.7.RELEASE.jar
-rw-r--r--  0 0      0     3115994  1  1  1970 app/libs//tomcat-embed-core-8.5.31.jar
-rw-r--r--  0 0      0      240244  1  1  1970 app/libs//tomcat-embed-el-8.5.31.jar
-rw-r--r--  0 0      0      256776  1  1  1970 app/libs//tomcat-embed-websocket-8.5.31.jar
-rw-r--r--  0 0      0       93107  1  1  1970 app/libs//validation-api-2.0.1.Final.jar

ビルドのたびにコンテナが作られるようにする

gradleプラグインで、gradleの機能を使い、タスクに依存関係を指定することで実現できます。

別名となるように、guild.gradleでバージョンを変えます。

version = '0.0.2-SNAPSHOT'

ビルドします。

./gradlew build -x test
(中略)
Containerizing application to Docker daemon as jib-test:0.0.2-SNAPSHOT...

Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...
Loading to Docker daemon...

Container entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, com.example.jibtest.DemoApplication]

Built image to Docker daemon as jib-test:0.0.2-SNAPSHOT

ここまでのプロジェクトを「Add-Gradle-Plugin」としてGitHubに格納しています。

ローカルのプライベートリポジトリを使う

作成されたコンテナをリポジトリに登録する部分を試してみます。

プライベートリポジトリの準備

dockerで公開されているプライベートレジストリ用のコンテナを使います。

$ docker run -d -p 5000:5000 --restart always --name registry registry:2

プライベートリポジトリのコンテナが動作していることを確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
8378d3fa853c        registry:2          "/entrypoint.sh /etc…"   2 minutes ago       Up 2 minutes        0.0.0.0:5000->5000/tcp   registry

手動でタグを売ってローカルリポジトリにpushしてみます。

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 b2b03e9146e1 9 days ago 33.3MB
graalvm latest 751a11aa3b93 3 weeks ago 985MB
centos centos7 49f7960eb7e4 5 weeks ago 200MB
jib-test 0.0.1-SNAPSHOT 2ce8c6f47c83 48 years ago 134MB
jib-test 0.0.2-SNAPSHOT 2ce8c6f47c83 48 years ago 134MB

$ docker tag jib-test:0.0.1-SNAPSHOT localhost:5000/jib-test-0.0.1

$ docker push localhost:5000/jib-test-0.0.1
The push refers to repository [localhost:5000/jib-test-0.0.1]
2c5e48a8f567: Pushed
6ef8c795d7a5: Pushed
72bcd5740b92: Pushed
40930fb97dd9: Pushed
6189abe095d5: Pushed
a9872a8d1d84: Pushed
latest: digest: sha256:b61de386936e2a5726d91445c1af080c4a32762cd6745298f2872c0fb413b222 size: 1575
hirokis-MBP:jib-test hiroki$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry 2 b2b03e9146e1 9 days ago 33.3MB
graalvm latest 751a11aa3b93 3 weeks ago 985MB
centos centos7 49f7960eb7e4 5 weeks ago 200MB
jib-test 0.0.1-SNAPSHOT 2ce8c6f47c83 48 years ago 134MB
jib-test 0.0.2-SNAPSHOT 2ce8c6f47c83 48 years ago 134MB
localhost:5000/jib-test-0.0.1 latest 2ce8c6f47c83 48 years ago 134MB

dockerデーモンの設定変更

dockerデーモンで、プライベートレジストリへのinsecureアクセスを有効にします。

macOSでは、画面上部のメニューバーのDockerアイコンをクリックし、「Preferences」メニューから「Daemon」タブを選択。
「Experimental features」にチェックを入れ、「localhost:5000」をInsecure registriesに追加します。「Apply & Restart」を押し、Dockerデーモンが再起動するのを待ちます。

docker_option.png

(補足)本エントリはmacOSを使用していますが、Linux等の方は、StackOverflowのこのあたりが参考になるかもしれません。

オプションを指定する

Jibのgradleプラグインのページを参照し、「allowInsecureRegistries」のパラメータを指定します。
デフォルトではfalseなので、オプションを何も指定しない状態でjibタスクを実行すると、エラーとなります。テスト目的でローカルのレジストリを使うという目的で、パラメータをtrueにします。これはテスト目的であり、本番運用等の設定ではないことにご注意ください。

jib {
  allowInsecureRegistries = true
}

コンテナをビルドする

gradleのjibタスクでローカルのプライベートリポジトリにpushしてみます。

warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible

Containerizing application to localhost:5000/jib-test-0.0.2...

Retrieving registry credentials for localhost:5000...
Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...

Container entrypoint set to [java, -cp, /app/libs/*:/app/resources/:/app/classes/, com.example.jibtest.DemoApplication]

コンテナが格納されていることを確認する

curlコマンドで、プライベートレジストリの内容を確認します。jib-test-0.0.2が格納されています。

$ curl -X GET http://localhost:5000/v2/_catalog
{"repositories":["jib-test-0.0.1","jib-test-0.0.2"]}

 動かしてみる

上記でプライベートレジストリに登録したコンテナを動かしてみます。

$ docker run -p 8080:8080 -it localhost:5000/jib-test-0.0.2 

本エントリの今までと同じ方法で、動作を確認します。

$ curl http://localhost:8080/
Hello World!

ここまでのプロジェクトを「Add-Gradle-Plugin」としてGitHubに格納しています。

まとめ

本エントリでは、コンテナかツールのJibと、JibのGradleプラグインを用いて、Javaのアプリケーションを簡単にコンテナ化する方法について確認しました。
プライベートリポジトリを立ち上げ、Gradleのjibコマンドでコンテナを登録できることを確認しました。

本エントリ執筆時点ではJibは開発中であり、WARファイルのコンテナ化など、ロードマップにはありますが実現されていない機能もあります。適用できるかどうかはプロジェクトの都合によるところもあると思いますが、コンテナ化の作業が
簡単になるところにはメリットがあるため、適用を検討することには価値がありそうです。