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

## 概要

このエントリでは、Googleが公開している[Jib](https://github.com/GoogleContainerTools/jib)を利用して、Javaアプリケーションを簡単にDockerコンテナ化する手順について試してみます。

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

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


### 前提

ミドルウェア、ライブラリは、以下を利用しています。
- [OS: macOS 10.13.2](https://www.apple.com/jp/macos/high-sierra/)
- [JDK: 1.8.0_171] (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
- [Jib-Gradle-Plugin: 0.9.4 ](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin)
- [Docker: Version 18.03.1-ce-mac65 (24312)](https://www.docker.com/community-edition)
- [Gradle(4.6以上)](https://gradle.org/install/)
- Macbook Pro(15-inch, Mid 2014)

### 対象読者

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

### 関連エントリ

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

- [JibでJavaアプリケーションを簡単にDocker化](https://qiita.com/zgmf_mbfp03/items/3ff8bbed0bd6aac85b4b)
 ...このエントリではmavenプラグインを利用されています。

### 開発環境の準備

#### JDK

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

#### spring (CLI)

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

[sdkman](https://sdkman.io/install)でインストールします。

```bash
$ 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上に格納](https://github.com/hrkt/jib-test/releases/tag/1.0)してあります。

```java
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でアプリケーションを起動します。

```bash
$ cd jib-test
$ ./gradrew bootRun
```

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

```bash
$ 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でした。

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

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

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

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

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

いい感じです。ここまでのプロジェクトを[「Add-Gradle-Plugin」](https://github.com/hrkt/jib-test/releases/tag/1.1)として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ファイルがありました。


<details><summary>build/jib-cache以下のtar.gzファイルの中身</summary><div>

``` bash
$ 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
```
</div></details>

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

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」](https://github.com/hrkt/jib-test/releases/tag/1.2)としてGitHubに格納しています。

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

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

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

dockerで公開されている[プライベートレジストリ用のコンテナ](https://hub.docker.com/_/registry/)を使います。


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

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

```bash
$ 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
```

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


$ 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
</div></details>

### dockerデーモンの設定変更

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

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

<img width="502" alt="docker_option.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/6b647c61-5010-cdf3-cc43-0dd85ad89003.png">

(補足)本エントリはmacOSを使用していますが、Linux等の方は、StackOverflowの[このあたり](https://stackoverflow.com/questions/32808215/where-to-set-the-insecure-registry-flag-on-mac-os)が参考になるかもしれません。

### オプションを指定する

Jibの[gradleプラグインのページ](https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#export-to-a-docker-context)を参照し、「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"]}
```

####　動かしてみる

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

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

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

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

ここまでのプロジェクトを[「Add-Gradle-Plugin」](https://github.com/hrkt/jib-test/releases/tag/1.3)としてGitHubに格納しています。

## まとめ

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

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