Ubuntu on WSL2でMicronautのネイティブビルドを試す

巷で噂のMicronautをWSL2環境で動かしてみます。

### はじめに

Micronaut（マイクロノート）とは、Grailsチームが新たに開発しているマイクロサービス向けのフルスタックなフレームワークです。
DIやAuto-Config、ServiceDiscovery対応といった機能に加え、起動の速さやフットプリントの軽減も狙いにしています。

Micronautの機能の1つにGraalVMを使ったネイティブビルドがあります。
まだ実験的な段階と[ドキュメント](https://docs.micronaut.io/latest/guide/index.html#_experimental_status)にありますが、今回は実際にJavaのWeb APIをネイティブビルドして動かすところまで確認します。


### 開発環境

WLS2をセットアップし、WLS2にUbuntu 18.04がインストールされている状態を前提にします。

|               | バージョン        |
|:--------------|:-----------------|
| Windows       | Windows 10 Pro バージョン 1903<br>(OSビルド 19025.1)<br>※Insider Preview|
| WSL           | WSL2             |
| Ubuntu on WSL | 18.04            |

WLS2の導入がまだの人は、以下の記事が参考になるかと思います。

[WSL2までの道のり](https://qiita.com/kikiki/items/df9874bae23f3c271ea2)

※「UbuntuがWLS1からWSL2にならない！」という人は、[末尾の参考情報](#参考ubuntuがwsl1からwsl2にならない場合)をチェックしてみてください。

### SDKMAN、Micronautのインストール

MicronautをインストールするためにSDK管理ツールの「SDKMAN」を使います。

まず、SDKMANのインストールに必要となるzipとunzipをインストールします。
パッケージ一覧の更新（apt update）を行ってからapt installしましょう。

```console
$ sudo apt update && sudo apt install -y zip unzip
```

SDKMANをインストールします。
インストール直後にSDKMANを使うため、.bashrcを再読み込みしておきます。

```console
$ curl -s "https://get.sdkman.io" | bash
$ . ~/.bashrc
```

SDKMANを使ってJDKとMicronautをインストールします。
今回インストールするMicronautには、2019/11/29現在の最新バージョンである1.2.6を指定します。

```console
$ sdk install java 8.0.232.hs-adpt
$ . ~/.bashrc
$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.232-b09, mixed mode)
$ sdk install micronaut 1.2.6
$ mn --version
| Micronaut Version: 1.2.6
| JVM Version: 1.8.0_232
```

### Micronautアプリの作成

アプリとコントローラーのスケルトンを作ります。

```console
$ mn create-app hello --features graal-native-image
| Generating Java project...
| Application created at /home/my-user/hello
$ cd hello
$ mn create-controller hello
| Rendered template Controller.java to destination src/main/java/hello/HelloController.java
| Rendered template ControllerTest.java to destination src/test/java/hello/HelloControllerTest.java$　cd hello
```

作成されたHelloController.javaはlocalhost:8080/helloにアクセスするとステータスコード:200を返すだけなので、少し味付けしましょう。
以下のように書き換えます。

```java:HelloController.java

package hello;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.QueryValue;

import javax.annotation.Nullable;

@Controller("/hello") 
public class HelloController {

    @Get(produces = MediaType.TEXT_PLAIN) 
    public String index(@Nullable @QueryValue("name") String name) {
        if (name == null) {
            name = "World";
        }

        return "Hello " + name + "!!\n";
    }
}
```

### JARの作成と実行

ビルドしてFat Jarを作成します。

```console
$ ./gradlew assemble
＜ビルド実行の出力＞
$ ls build/libs
hello-0.1-all.jar hello-0.1.jar
```

ネイティブビルドする前にJavaコマンドでJARを起動し、アプリが動くことを確認してみましょう。

```console
$ java -jar build/libs/hello-0.1-all.jar
11:57:15.406 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1032ms. Server Running: http://localhost:8080
````

ブラウザで http://localhost:8080/hello にアクセスして「Hello World!!」が表示されればOKです。
確認できたらCtrl-Cでアプリを止めましょう。

### ネイティブビルドの方法

Micronautアプリをネイティブビルドして実行するには、以下の2つの方法があります。

* [A] Dockerを使ってネイティブビルドする・・・dockerのコンテナ内でネイティブビルドし、dockerコンテナ内で実行する。
* [B] Ubuntu上でネイティブビルドする・・・ Ubuntu上でネイティブビルドし、Ubuntu上で実行する。

まずは[A]を試してみましょう。

### [A] Dockerを使ってネイティブビルドする

既にビルド用のスクリプトやDockerfileが生成されていますので、docker環境があればコマンド一発でネイティブビルドできます。

WLS2のUbuntu上にDockerをインストールしてもいいのですが、Docker Desktop for WindowsのEdge版には、「WSL 2 Backend」という便利なものができたようなので、今回はそれを使います。

以下のサイトからDocker Desktop for Windowsのインストーラーをダウンロードして実行します。

https://hub.docker.com/editions/community/docker-ce-desktop-windows

ログインしてEdge用インストーラーを取得します。
※画面右上のDownloadボタンではなく、Edgeチャネルのインストーラーを選びましょう。
　表内にある「Get Docker Desktop for Windows(Edge)」ボタンです。

Docker Desktopを起動し、タスクバーのクジラアイコンを右クリックし、Settingを選択します。

以下の２箇所設定します。

* General の「Enable the experimental WSL 2 based engine」にチェックを入れる
* Resources > WSL INTEGRATION を選択し、使用しているWLS2上のUbuntuをONにする。

設定に困った場合は、こちらを記事を参考にどうぞ。

[WSL2 で Docker Desktop for Windows (Edge) を利用する](https://qiita.com/SakaiYuki/items/e5ab0061ff3273c0e80f#wsl-2-backend)

さて、Ubuntuに戻り、dockerが使えるか確認しましょう。
こんな形でかえってくればOKです。

```console
$ docker version
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea838
 Built:             Wed Nov 13 07:29:52 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:29:19 2019
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
```


では、ネイティブビルドを実行しましょう。
dockerイメージがダウンロードされた後、ネイティブビルドが実行されます。
私の環境ではネイティブビルドに4分10秒ほどかかりました。

以下のように返ってくればOKです。

```console
$ ./docker-build.sh
<中略>
Step 5/9 : RUN native-image --no-server -cp build/libs/hello-*-all.jar
 ---> Running in c11ae367c6fc
[hello:27]    classlist:   4,908.74 ms
[hello:27]        (cap):   1,074.08 ms
[hello:27]        setup:   2,537.79 ms
[hello:27]   (typeflow):  26,959.79 ms
[hello:27]    (objects):  31,552.87 ms
[hello:27]   (features):   3,008.96 ms
[hello:27]     analysis:  63,966.00 ms
[hello:27]     (clinit):     946.54 ms
[hello:27]     universe:   2,507.42 ms
[hello:27]      (parse):   8,784.57 ms
[hello:27]     (inline):  54,129.86 ms
[hello:27]    (compile):  65,871.62 ms
[hello:27]      compile: 131,680.54 ms
[hello:27]        image:   5,230.87 ms
[hello:27]        write:  38,152.82 ms
[hello:27]      [total]: 249,286.14 ms
Removing intermediate container c11ae367c6fc
 ---> a7f2d020f676
Step 6/9 : FROM frolvlad/alpine-glibc
latest: Pulling from frolvlad/alpine-glibc
<中略>
Successfully built 86ea9098050c
Successfully tagged hello:latest


To run the docker container execute:
    $ docker run -p 8080:8080 hello
```

最後に書かれているとおりに実行すれば、dockerコンテナ経由でアプリが起動します。

```console
$ docker run -p 8080:8080 hello
03:02:56.348 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 61ms. Server Running: http://ee9cf971ac1f:8080
```

61ms?? 速っ！！

http://localhost:8080/hello にアクセスしてみてください。
「Hello World!!」が表示されますね？

確認ができたら止めましょう。
docker-build.shがCtl-Cで終了できないようなので、dockerコマンドで終了させましょう。
Ubuntuのターミナルをもう１つ起動して以下のようにします。

```console
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
bd8d7d61c2e5        hello               "./hello"           13 seconds ago      Up 12 seconds       0.0.0.0:8080->8080/tcp   gifted_swartz
$ docker kill bd8d
bd8d
```
※「bd8d」の部分はdocker psで表示されるCONTAINER IDの値に読み替えてください。

docker killすると、docker-build.sh側のコンソールのプロンプトが戻ってきたと思います。

### [B]　Ubuntu上でネイティブビルドする

せっかくなのでDockerを使わずにUbuntu上でネイティブビルドしてみましょう。


まず、Micronautのネイティブビルドに必要なパッケージを入れます。

```console
$ sudo apt install -y build-essential zlib1g-dev
```

SDKMANを使ってGraalVMをインストールします。
※バージョンは要注意です。v19.0.2のGraalVMだとMicronault v1.2.6のネイティブビルドが失敗します。


```console
$ yes y | sdk install java 19.3.0.r8-grl
$ java -version
penjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-20191008104205.buildslave.jdk8u-src-tar--b07)
OpenJDK 64-Bit GraalVM CE 19.3.0 (build 25.232-b07-jvmci-19.3-b05, mixed mode)
```
※デフォルトのJDKにするか聞かれるので、yesコマンドを使ってyを入力しています。

続いてGraalVMのnative-imageツールをインストールします。

```console
$ gu install native-image
```

念のため、JARをビルドしなおしておきましょう。

```console
$ ./gradlew clean assemble
```

準備完了！！ いざ、ネイティブビルド実行！！

```console
$ native-image --no-server -cp build/libs/hello-0.1-all.jar
[hello:7781]    classlist:   8,141.21 ms
[hello:7781]        (cap):   1,692.15 ms
[hello:7781]        setup:   3,379.58 ms
[hello:7781]   (typeflow):  27,399.41 ms
[hello:7781]    (objects):  22,304.20 ms
[hello:7781]   (features):   3,106.65 ms
[hello:7781]     analysis:  55,127.37 ms
[hello:7781]     (clinit):   1,235.15 ms
[hello:7781]     universe:   2,834.11 ms
[hello:7781]      (parse):   4,189.54 ms
[hello:7781]     (inline):   9,390.51 ms
[hello:7781]    (compile):  53,099.58 ms
[hello:7781]      compile:  69,783.42 ms
[hello:7781]        image:   4,836.02 ms
[hello:7781]        write:     840.42 ms
[hello:7781]      [total]: 145,407.37 ms
```

2分半ほどかかりました。
起動直下のディレクトリに「hello」というファイルが出来ているはずです。
これが生成された実行用バイナリファイルです。

あとはこの生成されたバイナリを実行するとアプリが起動します。

```console
$ ./hello
12:28:34.717 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 86ms. Server Running: http://localhost:8080
```

爆速で起動できたことが確認できました。

ブラウザで http://localhost:8080/hello にアクセスすると「Hello World!!」が表示されますね？

### さいごに

ということで、ユーザーガイドを少しだけアレンジしてネイティブビルドを試してみました。
GraalVMを利用したネイティブビルド自体が、まだexperimentalな段階ですが、発想は面白いですよね。

では、今回はこの辺で。

　
　

### 【参考】UbuntuがWSL1からWSL2にならない場合

```console
wsl --set-version Ubuntu-18.04 2
```

を実行しても、VERSIONが2になってくれない場合、

もしくは、

```console
wsl --set-default-version 2
```

を実行した状態で、Ubuntuのインストール後の初回起動が

```console
WslRegisterDistribution failed with error: 0x80370102
```

のエラーになる場合は、Hyper-Vの自動起動がOFFになっているかもしれません。
以下の記事を参考にHyper-Vの自動起動のステータスを確認してみてください。
（bcdeditでhhypervisorlaunchtypeがoffになっていたら、autoにしてWindowsを再起動し、再実行してみましょう）

[Hyper-VのON・OFFを簡単に自動化すっぞ！！](https://qiita.com/dodonki1223/items/75bd5171e2f2193c9977)


※おそらくVirtualBoxをインストールしたときの副作用だと思いますが、かなりハマりました。。。

参考になれば幸いです。