Jibを使ってDockerとMaven/NetBeansを良い感じにインテグレートしてみた

ちょっと仕事でJava環境をDockerで作ってたのですが、そういえばDockerfileを書かなくてもMavenで良い感じにDockerイメージを作ってくれる[Jib](https://github.com/GoogleContainerTools/jib)があったな、と思いそれを試してみました。
<img width="200" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.amazonaws.com/0/54904/2fcbd41a-2bb1-1f99-c2a4-94f435d09a09.png">

正直、「普通にDockerfile書けば良くない？」と思ってたのでそこまで注目して無かったのですが、使ってみると意外に便利。

今回は私の主要開発環境であるMac + NetBeansでJava11な環境を作ったのでそちらのやり方を記載します。
ちょこちょこ罠もあったのでそこも記載。

出来たものは下記にあります。
https://github.com/koduki/example-jib

実行用のプログラムの作成
------

当たり前ですが、まずは実行対象のコードを書きます。
何でも良いのですが一旦下記の感じで。エンドポイントとして`public static void main`は必要っぽいです。

```java
public class Main {

    public static void main(String[] args) {
        System.err.printf("%s %s, %s %s" + System.lineSeparator(),
                System.getProperty("os.name"),
                System.getProperty("os.version"),
                System.getProperty("java.vm.name"),
                System.getProperty("java.vm.version")
        );
        System.out.println("Hello World.");
    }
}
```

Jibの追加
---------

続いて何はともあれJibを追加しましょう。pom.xmlにプラグインを追加するだけの簡単なお仕事です。

```xml
<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>1.0.0-rc2</version>
    <configuration>
        <from>
            <image>openjdk:11-jdk-slim</image>
        </from> 
        <to>
            <image>example-jib</image>
        </to>
        <container>
            <jvmFlags>
                <jvmFlag>-Xmx512m</jvmFlag>
                <jvmFlag>-Xdebug</jvmFlag>
            </jvmFlags>
        </container>
    </configuration>
</plugin>
```

詳しくは[公式ドキュメント](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#build-to-docker-daemon)を参照ですがいくつかポイントがあります。
まず`to/image`タグですが、こちらはビルド後のイメージ名を指定します。そして`from/image`タグですが、こちらは元になるイメージを指定します。

`from/image`は省略可能で、その場合は`gcr.io/distroless/java`がデフォルトとして利用されます。
ただし、この環境はJava8なのでJava11など任意のJDKを使いたい場合は明示的に指定してやる必要があるので要注意です。
`container`ではJVMオプションとか色々指定できます。今回は特に指定してませんが[extraDirectory](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#adding-arbitrary-files-to-the-image)を使うことでDockerfileの`ADD`相当の事もできます。
ただ、src/resoucesにあるファイルは自動でコンテナに追加されるのでそれほど出番はないかも知れないです。

何となくコンテナを複雑に弄る場合はJibでゴリゴリやらずベースのイメージを作ってそれをFROMにするのが楽そうな気もします。

Jibによるイメージのビルド
-------------

続いてイメージのビルドです。

```bash
$ mvn compile jib:dockerBuild
```

これだけでDockerfileの作成をすっ飛ばしてイメージを作ってくれます。以下のようにイメージがちゃんと作られてるのが確認できるはずです。

```bash
% docker images|grep example-jib      
example-jib                    latest              8ec59091042c        49 years ago        491MB
```


`dockerBuild`の場合はローカルのDockerデーモンにイメージを登録しますがDockerHubなどリモートに直接Pushしたい場合は`jib:build`を利用します。

また、このタイミングでDocker for Macを利用していると下記のようなエラーが出るかも知れません。

```bash
Failed to execute goal com.google.cloud.tools:jib-maven-plugin:1.0.0-rc2:dockerBuild (default-cli) on project example-jib: Build to Docker daemon failed, perhaps you should make sure Docker is installed and you have correct privileges to run it -> [Help 1]
```

これは単純にJibがdockerデーモンを見つけれてないだけなのですがdockerコマンドを直接叩いた場合は普通に動くので？？？って感じでした。
ただ種明かしは簡単でdocker for Macをコマンドラインから直接呼ぶ場合は`DOCKER_HOST`の指定が不要なので環境変数に入ってなかったためです。
何で、以下のコマンド等で環境変数に`DOCKER_HOST`の追加が必要です。

```bash
export DOCKER_HOST=unix:///var/run/docker.sock
```

この辺はREADMEとかでも触れておいて欲しいのだけど、詳しい人には自明過ぎて誰も気にして無かったという事だろうか。。。

Mavenからコンテナの実行
----------

続いてMavenからのコンテナの実行です。普通に`docker run`をしても良いのですが、それだと別にターミナル立ち上げないといけなくて不便。。。　という事でこれもMavenから実行できるようにしてみました。

Jib自体はビルドに特化しているようで実行するための仕組みは無さそうだったので[fabric8io/docker-maven-plugin](https://dmp.fabric8.io/)という別のプラグインを入れました。
と言うわけでpom.xmlに以下を追加。

```xml
<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.28.0</version>
    <configuration>
        <showLogs>true</showLogs>
        <images>
            <image>
                <name>example-jib</name>
                <run>
                    <log>
                        <enabled>true</enabled>    
                    </log>
                    <wait>
                        <exit>0</exit>
                    </wait>
                </run>
            </image>
        </images>
        <keepContainer>false</keepContainer>
    </configuration>
</plugin>
```

こちらも詳しくは公式ドキュメントを参照ですが`image/name`で実行するイメージを指定します。
ボリュームとかポートとかが必要な場合はrunに追記していく感じです。

ポイントは`showLogs`, `image/run/log/enabled`, `image/run/wait/exit`です。この辺を指定しないと実行結果が標準出力に出ません。
`showLogs`と`image/run/log/enabled`は実行結果をログとして出力するためのオプションです。
ただ、これだけでは不十分で`wait`タグに何かしら登録しないと実行が瞬時に終わってしまって画面に何も出ません。そのため正常完了を確認する`exit 0`をチェックに入れています。

これを実行すると以下のようになります。

```bash
% mvn docker:start   
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------< cn.orz.pascal:example-jib >----------------------
[INFO] Building example-jib 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- docker-maven-plugin:0.28.0:start (default-cli) @ example-jib ---
[INFO] DOCKER> [example-jib:latest]: Start container ec75063ae514
ec7506> Linux 4.9.125-linuxkit, OpenJDK 64-Bit Server VM 11.0.1+13-Debian-2bpo91
ec7506> Hello World.
[INFO] DOCKER> [example-jib:latest]: Waited on exit code 0 964 ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.142 s
[INFO] Finished at: 2019-01-13T20:09:12-08:00
[INFO] ------------------------------------------------------------------------
```

実行結果のOSの部分がMacでは無くLinuxなので、ちゃんとコンテナで起動している事がわかりますね。

Maven/NetBeansのビルドプロセスとの統合
-------------

全部コマンドラインから操作をするならjib:dockerBuild`や`docker:start`を毎度叩けば良いですが普通はIDE使って開発しますよね？ Javaだし。
NetBeansだとmavenのカスタムゴールを登録することも出来ますがIDE使ってる感がイマイチなので、もうちょっとインテグレートされた感じの設定をしていきます。

### NetBeansの「Build」でjib:dockerBuildを実行

NetBeansの場合はメニューの「Build」でビルドイメージを作る事が一般的ですよね。JavaならjarがJavaEEならwarがと適切なものがこのメニューでビルドされます。
何で今回もDockerイメージが生成されうようにMavenの`package`フェーズを上書きします。

```xml
<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
...
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>dockerBuild</goal>
            </goals>
        </execution>
    </executions>
</plugin>
```

先ほどのプラグイン設定の部分にexecutionタグを追加しました。これで以下のように`mvn package`で`jib:dockerBuild`が走るようになります。

```bash
% mvn package
[INFO] Scanning for projects...
...
[INFO] --- jib-maven-plugin:1.0.0-rc2:dockerBuild (default) @ example-jib ---
[INFO] 
[INFO] Containerizing application to Docker daemon as example-jib...
[INFO] The base image requires auth. Trying again for openjdk:11-jdk-slim...
[INFO] 
[INFO] Container entrypoint set to [java, -Xmx512m, -Xdebug, -cp, /app/resources:/app/classes:/app/libs/*, cn.orz.pascal.example_jib.Main]
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  19.345 s
[INFO] Finished at: 2019-01-13T20:07:28-08:00
[INFO] ------------------------------------------------------------------------
```

NetBeansの`Build`はMavenの`package`フェーズを呼んでいるので、NetBeans側も特にカスタムゴールの設定とかせずに普通にメニューから`Build`をするだけでOKです。

### NetBeansの「Run」でdocker:startを実行

続いてコンテナの実行です。
実のところMavenの標準ライフサイクルには`実行`に相当するフェーズはありません。`org.codehaus.mojo:exec-maven-plugin:1.5.0:exec`を実態としては実行しているのですが、ゴールを直接指定して実行されてるだけなのでMaven側でこれをどうフックすれば良いかはわかりませんでした。

なので、Maven側はスパッと諦めてNetBeansの設定だけ変えることにしました。`Run`などの標準のアクションをカスタマイズするにはプロジェクト直下のnbactions.xmlを編集します。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<actions>
    <action>
        <actionName>run</actionName> 
        <goals>
            <goal>docker:start</goal>
        </goals>
    </action>
</actions>
```

これでNetBeansからRunを実行すれば`exec:exec`の代わりに`docker:start`が実行されるようになります。

まとめ
--------

Jibを使ってDockerとMaven及びNetBeansを良い感じにインテグレートする方法をまとめてみました。
Javaで開発してる場合はCLIよりもIDEに統合された形で色々出来た方がなんだかんだで便利です。
カスタムゴールだとイマイチ統合感が無いのでこの方法ができてよかったです。

今回はCLIベースのJavaを書いてましたがJavaEE系も問題なくJibでイメージ化できるようなので、今度はその辺を調査してみたいと思います。

これでDockerを仕事で使うための道が一歩進んだかな。

それではHappy Hacking!