CloudRunでNativeImageのJavaアプリを起動する

# CloudRunでNativeImageのJavaアプリを起動する

CloudRunは未使用時はノード数が０まで落ちるGCPのサーバーレス環境のひとつです。
気が付けは東京リージョンでも使えるようになっていたので、久しぶりに使うがてらNativeImageとmicronautを触ってみました。

今回の技術要素は以下

- GraalVM(NativeImage)
- Micronaut
- gcloud
- ContainerRegistory
- CloudRun

## MacにGraalVMをインストール

##### GraalVMをインストール

https://github.com/oracle/graal/releases

graalvm-ce-darwin-amd64-19.2.0.tar.gz をダウンロード。（バージョンはその時の最新で）

展開して、JavaVirtualMachinesに設定

```
tar xvzf graalvm-ce-darwin-amd64-19.2.0.tar.gz
sudo mv graalvm-ce-19.2.0 /Library/Java/JavaVirtualMachines/.
```

GraalVMが認識されていることを確認

```
yosistamp:tool katsuhiro$ /usr/libexec/java_home -V
Matching Java Virtual Machines (3):
    11.0.2, x86_64:	"OpenJDK 11.0.2"	/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
    1.8.0_221, x86_64:	"GraalVM CE 19.2.0"	/Library/Java/JavaVirtualMachines/graalvm-ce-19.2.0/Contents/Home
    1.8.0_101, x86_64:	"Java SE 8"	/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home
```

環境変数を設定

```
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
export PATH=$JAVA_HOME/bin:$PATH
```

バージョン確認

```
yosistamp:tool katsuhiro$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-20190711112007.graal.jdk8u-src-tar-gz-b08)
OpenJDK 64-Bit GraalVM CE 19.2.0 (build 25.222-b08-jvmci-19.2-b02, mixed mode)
```

NativeImageのコンポーネントをインストール

```
gu install native-image
```

### アプリ開発の準備（sdkmanとmicronautのインストール）

sdkman

```
curl -s "https://get.sdkman.io" | bash
source .bash_profile
```

※インストールしたら、.bash_profileの最後に設定が追加されるので、.bash_profileをリロードしている。


##### gradleとmicronautをインストール

2019/9/7時点で、gradle5.6.2、micronaut1.2.1がインストールされます。
バージョンを指定する場合は、最後にバージョンを指定する。（sdk install micronaut 1.2.1 etc）

```
sdk install gradle
sdk install micronaut
```

### micronautアプリケーションの作成

プロジェクトフォルダを作って、そこでプロジェクトを作成

```
mn create-app <プロジェクト名> --features graal-native-image
```

##### サンプルアプリ

demo/controller/DemoController.javaを作成

```
package micronaut.demo.controller;

import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller("/usagisan")
public class DemoController {
    @Get("/")
    public String index() {
        
        return "Hello Usagisan!";
    }
}
```

実行してみる

```
./gradlew run
```

ブラウザでアクセス

http://localhost:8080/usagisan

→ Hello Usagisan! と表示されたらOK

### イメージの作成

create-appで --features graal-native-imageをつけておくと、Dockerfileにnative-image作成の手順が自動で入ります。

create-appで --features graal-native-imageをつけない場合

```
FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim
COPY build/libs/demoapp-*-all.jar demoapp.jar
EXPOSE 8080
CMD java -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar demoapp.jar
```

create-appで --features graal-native-imageをつけた場合

```
FROM oracle/graalvm-ce:19.0.0 as graalvm
COPY . /home/app/micronaut-demo
WORKDIR /home/app/micronaut-demo
RUN gu install native-image
RUN native-image --no-server -cp build/libs/micronaut-demo-*-all.jar

FROM frolvlad/alpine-glibc
EXPOSE 8080
COPY --from=graalvm /home/app/micronaut-demo .
ENTRYPOINT ["./micronaut-demo"]

```

### CloudRepository(GCP)にアップ

#### ビルドして、dockerイメージ作成

```
./gradlew build
./docker-build.sh
```

10分ほど待つと、micronaut-demoというイメージが出来上がります。（長い。。。）

```
yosistamp:micronaut-demo katsuhiro$ docker images
REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE
micronaut-demo                               latest              ea22c441c083        13 minutes ago      124MB
<none>                                       <none>              706b1ee85c04        13 minutes ago      1.91GB
```

#### ContainerRegistoryにアップ

```
gcloud auth configure-docker
docker tag micronaut-demo asia.gcr.io/<プロジェクトID>/micronaut-demo
docker tag micronaut-demo asia.gcr.io/<プロジェクトID>/micronaut-demo:latest
docker push asia.gcr.io/<プロジェクトID>/micronaut-demo:latest
```

### CloudRunにデプロイ

```
gcloud beta run deploy sample-app --allow-unauthenticated --memory 128Mi --region asia-northeast1 --timeout 30 --image <イメージ名>
```

※イメージ名はContainerRegistoryで確認

デプロイが完了したら、URLが払い出されます。

```
yosistamp:micronaut-demo katsuhiro$ gcloud beta run deploy sample-app --allow-unauthenticated --memory 128Mi --region asia-northeast1 --timeout 30 --image <イメージ名>
Deploying container to Cloud Run service [sample-app] in project [プロジェクトID] region [asia-northeast1]
✓ Deploying new service... Done.                                                                                                         
  ✓ Creating Revision...                                                                                                                 
  ✓ Routing traffic...                                                                                                                   
  ✓ Setting IAM Policy...                                                                                                                
Done.                                                                                                                                    
Service [sample-app] revision [sample-app-00001] has been deployed and is serving traffic at https://sample-app-xxxxxx.run.app
```

あとはブラウザでURLにアクセスすればHello Usagisan!と表示されるはず。

## 補足

ローカルでのイメージ作成ではなく、CloudSourceRepository→CloudBuild→ContainerRepositoryと流す場合は、基本的には以下のような手順になると思います。

ほんとはそうしたかったが、実行したところメモリ不足でイメージ作成が最後まで進みませんでした。。。
（なので、ローカルでイメージを作る方法に切り替えた）
CloudBuildでワーカーのメモリ増やす方法ってあるのかな。。。

#### Gradle5のイメージ作成

gradleでビルドするのでGradleのイメージが必要ですが、公式のCloud-BuilderはGradle4.6のため、micronautのビルドに失敗します。（5.0+）
そのため、Gradle5のカスタムイメージを準備して、ビルドします。

私は、下記のDockerfileを参考にしました。

https://github.com/keeganwitt/docker-gradle

これをCloudSourceRepositoryに入れてCloudBuildでビルドすると、Gradle5のカスタムイメージができます。

#### cloudbuild.yamlの用意

```
steps:
  - name: 'gcr.io/$PROJECT_ID/<イメージ>:<タグ>'
    args: ['./gradlew', 'build']
  - name: 'gcr.io/cloud-builders/docker'
    args: [ 'build', '-t', 'gcr.io/$PROJECT_ID/micronaut-demo', '.' ]
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/$PROJECT_ID/micronaut-demo']
images:
  - 'gcr.io/$PROJECT_ID/micronaut-demo'
```

これで、CloudBuildでcloudbuild.yamlを指定してビルドするようにすれば、手順としてはいけるはずなんだけど。。。