OpenJDK11のdokcerイメージ(1GB)が大きいのでalpine linux+ jlinkで小さいイメージ(85MB)を作成する

# はじめに
openjdk 11のdocker imageは、docker hubのofficial respoitoryで公開されています。

https://hub.docker.com/_/openjdk/

ただ・・・これを使う上でちょっと気になるのが、

- イメージサイズがそもそも大きい。約1GBもあります。
- jlinkを使っても、300MB以上ある

という問題があります。もっと小さくしたいな。という所で、どれくらい小さくできるか試してみました！

## 前提

 - Java Flight Recorderが動くこと
 - Spring Boot アプリケーションが動くこと

# 結果
**1GB→85MBと1/10以下に圧縮できました:grinning:**
個人的には大満足です。

`docker images` でサイズを確認した結果は下記の通りです。

| imageの種類 |jlink使用|size |
|:-----------|:------------|-----:|
|openjdk:11-jdk| しない | 1GB |
|openjdk:11-jdk| する   | 468MB |
|独自alpinelinux| しない | 336MB |
|独自alpinelinux| する | 84.6MB |

jdk11はea版です。そのうちちゃんとopenjdk 11のofficial imageでも小さいイメージが作れるようになると思います。
独自alpinelinuxは、
　https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/
です。

# 問題

OpenJDK 11のofficial imageの問題を詳細に記載します。

## イメージサイズが大きい

```
$ sudo docker images |grep jdk
docker.io/openjdk                            11-jdk   f684efd78557  2 weeks ago         979 MB
$
```
jreではちょっとflightrecorderが動かないので、jdkを使いたいのに・・・ちょっと大きすぎます。
参考：[Qiita - OpenJDK11-JRE-SLIMじゃFlightRecorderは動きません。](https://qiita.com/h-r-k-matsumoto/items/ca1a6d80f3984c975c20#openjdk11-jre-slim%E3%81%98%E3%82%83flightrecorder%E3%81%AF%E5%8B%95%E3%81%8D%E3%81%BE%E3%81%9B%E3%82%93)

## openjdk 11(official)ではjlink使っても小さくならない。

下記issueでも同じ問題を取り上げています。
https://github.com/docker-library/openjdk/issues/217

試しにやると・・・下記のようになぜか400MBほどになり、imageサイズも450MBくらいになってしまいます。

```jlinkでのjvmを小さくしてみる。
$ docker run -it --rm openjdk:11-jdk /bin/sh
# ls -l /usr/lib/jvm/java-11-openjdk-amd64/lib/server/
total 34944
-rw-r--r-- 1 root root     1322 Jul 27 03:41 Xusage.txt
-r--r--r-- 1 root root 18210816 Jul 27 22:22 classes.jsa
-rw-r--r-- 1 root root    14440 Jul 27 03:41 libjsig.so
-rw-r--r-- 1 root root 17551048 Jul 27 03:41 libjvm.so　　#まだ小さい
圧縮してみる
# jlink \
     --module-path /opt/java/jmods \
     --compress=2 \
     --add-modules java.base,java.logging,jdk.jfr  \
     --no-header-files \
     --no-man-pages \
     --output /opt/jdk-11-mini-runtime
#  ls -l /opt/jdk-11-mini-runtime/lib/server/
total 414452
-rw-r--r-- 1 root root      1322 Aug 14 09:41 Xusage.txt
-rw-r--r-- 1 root root     25384 Aug 14 09:41 libjsig.so
-rw-r--r-- 1 root root 424362808 Aug 14 09:41 libjvm.so #なんでや・・・
#
```

# イメージサイズ小さくするぞ！

alpine linuxを使う、そしてjlinkを使って小さくするというアプローチをとってみます。


## alpine linuxのベースのbuild用イメージを作成する。
他の人でも約に立つかな？と思って公開してます。

image: https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/
docker file: https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/~/dockerfile/

checksumしてなくてすみません…。

## jlinkを使って小さい実行イメージを作る。

multi stageを使ってビルドします。

1. 上記で作ったalpine linux + openjdk 11のimageをベースに、jlinkで小さいjava runtime moduleを作成します。
2. runtime module、jar moduleをコピーしたalphine linuxのimageを作成します。

```Dockerfile
FROM hirokimatsumoto/alpine-openjdk-11:latest as jlink-package
# First: generate java runtime module by jlink.

RUN jlink \
     --module-path /opt/java/jmods \
     --compress=2 \
     --add-modules jdk.jfr,jdk.management.agent,java.base,java.logging,java.xml,jdk.unsupported,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument \
     --no-header-files \
     --no-man-pages \
     --output /opt/jdk-11-mini-runtime

# Second: generate run image.
FROM alpine:3.8

ENV JAVA_HOME=/opt/jdk-11-mini-runtime
ENV PATH="$PATH:$JAVA_HOME/bin"

COPY --from=jlink-package /opt/jdk-11-mini-runtime /opt/jdk-11-mini-runtime
COPY target/k8s-jmc-sample-0.1.0-SNAPSHOT.jar /opt/spring-boot/

EXPOSE 30001 7199
CMD java \
    -XX:StartFlightRecording=name=sample,filename=/spring-boot/jfr/sample.jfr,delay=30s,maxage=2h,maxsize=10m,dumponexit=true,settings=/spring-boot/config/profile.jfc \
    -Dcom.sun.management.jmxremote.port=7199 \
    -Dcom.sun.management.jmxremote.rmi.port=7199 \
    -Dcom.sun.management.jmxremote.authenticate=false \
    -Dcom.sun.management.jmxremote.ssl=false \
    -jar /opt/spring-boot/k8s-jmc-sample-0.1.0-SNAPSHOT.jar \
    --server.port=${SPRING_SERVER_PORT} \
    --spring.config.location=/spring-boot/config/application.yaml
```

実行アプリケーションは、
https://github.com/h-r-k-matsumoto/k8s-jmc-sample
です。


# ハマったこと

## Spring Bootアプリケーションをjdepsで依存関係見ても正確ではない

最初jdepsを使い、下記のように必要なmoduleを確認してました。

```
>　jdeps --list-deps .\target\k8s-jmc-sample-0.1.0-SNAPSHOT.jar
   java.base
   java.logging
>
```

これでruntime作っても・・・実行時に、下記のようにエラーになります。

```
Caused by: java.lang.ClassNotFoundException: java.sql.SQLException
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:93)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
```

うーん、spring boot内部でauto configurationしている箇所は上手く追跡できないのかな・・・。
めんどくさかったので、
　https://dev.solita.fi/2018/01/24/Java9-modules-Spring-Boot-2-Docker.html
みて適当に追加しました。

# 参考

 - OpenJDK official image: https://hub.docker.com/_/openjdk/
 - 独自のalpine linux image: https://hub.docker.com/r/hirokimatsumoto/alpine-openjdk-11/
 - Spring Boot + jlinkの参考情報： https://dev.solita.fi/2018/01/24/Java9-modules-Spring-Boot-2-Docker.html