【サーバーサイドKotlin】 KotlinにSpring Bootを適用してJibを使ってコンテナイメージ作成

# はじめに

## なぜServer Side Kotlin?

- 言語自体の先進性、生産性の高さ（シンプルな構文、Nullable)
- Java開発環境（特にIntelliJ)があれば、ほとんど障壁なく導入可能
- Javaの資産流用、相互運用が非常に簡単
  + Javaで使っていたフレームワークがそのまま使えるなどJavaのスキルスタックを活用可能
- GoogleがAndoroidアプリの公式開発言語とすることを発表（2017年）
  + →Android アプリ開発とのスキルセット共通化

参考：

- [Kotlin公式サイト](https://kotlinlang.org/)

## Jibってなに？

Googleが公開しているJavaアプリケーションをcontainerizeするツールです。

特長として
- Dockerfile作成などコンテナ化のための開発が大幅に減る
- イメージのビルドにDockerが不要
- ベストプラクティスに沿ったコンテナイメージ

作成されるイメージのベースは、[distoless](https://github.com/GoogleContainerTools/distroless)というGoogleが公開するプロダクション向けのベースイメージが採用されています。

参考：

- [distrolessイメージを使って、ランタイムDockerイメージを作ってみる](https://qiita.com/some-nyan/items/90b624b0f148231748f0)

    昨年私がまとめた内容です。（この時から比べるとjavaとccのイメージが正式にプロダクション向けになりました）
- [Jib github page](https://github.com/GoogleContainerTools/jib)


- [Googleが公開しているJib紹介スライド](https://speakerdeck.com/coollog/build-containers-faster-with-jib-a-google-image-build-tool-for-java-applications)


## この記事で想定している環境

- Mac OS
- IntelliJ IDEA (2019.1)
  + Kotlin plugin (v1.3.31)
- openjdk 1.8.0_201
- Spring Boot 2.1.4
- Jib gradle plugin 1.2.0
- Docker (18.09.2)

※今回の記事ではローカルのDockerイメージとして登録したかったのでDockerインストールしていますが、リモートコンテナレポジトリに登録する場合は不要なはず

# 手順

## [前準備] 1. Kotlin × Spring Bootのプロジェクト作成

Spring Bootを利用したKotlinプロジェクトを作成します。

Spring InitialzrがKotlin対応しているので、極めて簡単に雛形プロジェクトが手に入ります。

https://start.spring.io/
にアクセスして、

- Gradle
- Kotlin
- Spring Boot 2.1.4

を選択します。
Group名、Artifact名は各自の値を入力します。

今回は、以下としました。
- Grouop: com.example
- Artifact: kotlindemo

「Generate Project」を押すと、Zipがダウンロードされるので
解凍して、IntelliJにImportします。

これで、KotlinベースのSpring Boot Applicationのプロジェクトが完成しました。

## [前準備] 2. Controllerを追加

この状態では、APIが1つも存在していないので追加します。

今回はサンプルのレスポンスを返却するControllerを追加してみます。

まずは、REST関係のアノテーションを利用したいので、
`spring-boot-starter-web`
を依存ライブラリに追加しておきます。

```
--- a/build.gradle
+++ b/build.gradle
@@ -20,6 +20,7 @@ dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        implementation 'org.jetbrains.kotlin:kotlin-reflect'
        implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+       implementation 'org.springframework.boot:spring-boot-starter-web'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
 }
```

依存を追加したので、ビルドしておきます。(Command + F9)

次に、サンプルのレスポンスを返却するControllerを追加します。

SampleController.kt

```kotlin
package com.example.kotlindemo

import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.GetMapping


@RestController
class SampleController {

    @GetMapping("sample")
    fun sample(): HashMap<String, String> {
        val sample = HashMap<String, String>()
        sample.put("name", "test_name")
        sample.put("id", "test")
        return sample
    }
}
```

利用するアノテーションはJavaで実装した時と同じですね。

これで /sample というREST APIが完成したはずです。

IntelliJでデバッグして確かめてみます。
私の場合は、`kotlindemo`というArtifact名にしたので、
「KotlindemoApplication」という名前で、デバッグ構成が登録されていました。

これを実行して、curlコマンドで/sample APIを確認します。

```sh
curl localhost:8080/sample
```

実行結果：
```
{"name":"test_name","id":"test"}
```

サンプルREST APIの完成です。

## [本題] 3. Jibの追加、コンテナイメージ作成

build.gradleにJibプラグインを追加します。
ついでに設定も追加します。

```
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@ plugins {
 	id 'org.springframework.boot' version '2.1.4.RELEASE'
 	id 'org.jetbrains.kotlin.jvm' version '1.2.71'
 	id 'org.jetbrains.kotlin.plugin.spring' version '1.2.71'
+	id 'com.google.cloud.tools.jib' version '1.2.0'
 }
 
 apply plugin: 'io.spring.dependency-management'
@@ -12,6 +13,9 @@ group = 'com.example'
 version = '0.0.1-SNAPSHOT'
 sourceCompatibility = '1.8'
 
+// jib configuration
+jib.container.useCurrentTimestamp = true // jibで作成されるイメージの作成時間を現在時刻とする
+
 repositories {
 	mavenCentral()
 }
```

プロジェクトをビルドし、Jibをインストールします。(Autoビルドにしておけばよかった。。)

下記のコマンドを実行して、イメージ作成＆ローカルのDockerイメージとして登録します。

```sh
./gradlew jibDockerBuild
```

なお、リモートレポジトリにプッシュしたい場合は、
https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#quickstart
ここら辺をみて設定するようです。

では最後にイメージを起動して確認してみます。

```sh
docker run --rm -p 8080:8080 kotlindemo:0.0.1-SNAPSHOT
```

コンテナのログ：

```

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)

2019-05-15 02:26:04.665  INFO 1 --- [           main] c.e.kotlindemo.KotlindemoApplicationKt   : Starting KotlindemoApplicationKt on c6df41f5d4a3 with PID 1 (/app/classes started by root in /)
2019-05-15 02:26:04.671  INFO 1 --- [           main] c.e.kotlindemo.KotlindemoApplicationKt   : No active profile set, falling back to default profiles: default
2019-05-15 02:26:05.154  WARN 1 --- [kground-preinit] o.s.h.c.j.Jackson2ObjectMapperBuilder    : For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
2019-05-15 02:26:06.928  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-05-15 02:26:06.998  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-05-15 02:26:06.998  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.17]
2019-05-15 02:26:07.244  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-05-15 02:26:07.244  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2494 ms
2019-05-15 02:26:08.528  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-05-15 02:26:08.965  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-05-15 02:26:08.973  INFO 1 --- [           main] c.e.kotlindemo.KotlindemoApplicationKt   : Started KotlindemoApplicationKt in 4.958 seconds (JVM running for 5.682)
2019-05-15 02:26:40.980  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-05-15 02:26:40.980  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2019-05-15 02:26:40.993  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 13 ms
```

起動成功！、curlも通るようになっていました。

## 終わりに

KotlinプロジェクトにSpring Bootを適用して、REST APIのアプリケーションを作成、
そのアプリケーションのコンテナイメージ化にJibを利用するやり方を試してみました。

国内でもサーバーサイドのKotlin採用事例があるようですが、
導入も非常に簡単で、確かにとうなづける感覚はしました。