【Kotlin for Server-Side】KtorのプロジェクトをDockerで起動する

## [Kotlin for Server-Side](https://kotlinlang.org/docs/reference/server-overview.html)

ServerSideの現状を以下に軽くまとめてみました。
### Frameworks for Server-side Development with Kotlin

- [Spring](https://spring.io/)
    - Javaでの有名なWebアプリケーションFramework
- [Vert.x](https://vertx.io/)
    - JVM上でReactiveアプリケーションを作成する為のToolkit
- [Ktor](https://github.com/ktorio/ktor)
    - JetBrains社が作成しているWebアプリケーションFramework
- [kotlinx.html](https://github.com/kotlin/kotlinx.html)
    - Kotlin DSL for HTML

今回は上記の中でも **Ktor** を試して見たいと思います。

###  :computer:環境構築
****
[Ktor Project Generator](https://start.ktor.io/) で空のプロジェクトを作成し、ダウンロードします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299271/969d6b9f-5747-3f93-6ea1-e6d883fb61b5.png" width="500" />

何も変更しない状態で「Build」を選択しプロジェクトをダウンロードします。
ダウンロードしたプロジェクトのディレクトリ構成は以下の様になってました。

```
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── resources
│   ├── application.conf
│   └── logback.xml
├── settings.gradle
└── src
    └── Application.kt
```
※ ダウンロードしたプロジェクトのGradleのVerが `4.10` となっており、後述する Gradle Shadow を使用する際に
      Versionが古いと怒られるので `gradle-wrapper.properties` のgradleのversionを `5.6.4` に変更してます。

また、`build.gradle` と`Application.kt` は以下の様になってました。

```gradle:build.gradle
buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin'
apply plugin: 'application'

group 'com.example'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
    testCompile "io.ktor:ktor-server-tests:$ktor_version"
}
```

```kotlin
package com.example

import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@Suppress("unused") // Referenced in application.conf
@kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
}
```
### :pencil: Dockerで起動できる様にする
****
[こちら](https://github.com/ktorio/ktor-samples/tree/master/deployment/docker)を参考に環境構築を行います。

#### [Gradle Shadow](https://github.com/johnrengelman/shadow)
Jarを生成してくれる、GradleのPlugin。これを使ってjarを生成しDocker内で起動します。

`build.gradle` を以下に修正

```gradle
buildscript {
    repositories {
        jcenter()
        maven {
        url 'https://plugins.gradle.org/m2/'
        }
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.github.jengelman.gradle.plugins:shadow:5.2.0"
    }
}

apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'

group 'com.example'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
    maven { url "https://dl.bintray.com/kotlin/ktor" }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
    testCompile "io.ktor:ktor-server-tests:$ktor_version"
}

shadowJar {
    baseName = 'my-application'
    classifier = null
    version = null
}
```
以下のDockerfileをプロジェクト直下に追加

```dockerfile
FROM openjdk:8-jdk

RUN mkdir /app
WORKDIR /app

ADD . $WORKDIR
RUN ./gradlew wrapper # gradle wrapper関連を諸々アップデート
```

起動時にkickするshellを作成 `bin/start_server.sh`

```bash
#!/bin/bash
set -e
source ~/.bashrc

./gradlew build
cp ./build/libs/my-application.jar .
java -server -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:InitialRAMFraction=2 -XX:MinRAMFraction=2 -XX:MaxRAMFraction=2 -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+UseStringDeduplication -jar my-application.jar
```

`docker-compose.yml` を以下の内容で作成する。

```yml
version: '3'

services:
  app:
    build:
      context: .
      dockerfile: ./Dockerfile
    ports:
      - '8080:8080'
    volumes:
      - .:/app
    command:
      bin/start_server.sh
```

あとは `docker-compose up` で起動し、`http://0.0.0.0:8080` にアクセスできればOKです。
作成したプロジェクトは[こちら](https://github.com/Slowhand0309/ktor-demo)にアップしてます。

###  :bomb: バッドノウハウ
****

```
* What went wrong:
A problem occurred evaluating root project 'ktor-demo'.
> Failed to apply plugin [class 'com.github.jengelman.gradle.plugins.shadow.ShadowBasePlugin']
   > This version of Shadow supports Gradle 5.0+ only. Please upgrade.
```
gradleのバージョンが古い。[こちら](https://qiita.com/nobuoka/items/09cbdcd4716b930abdc4)を参考に、
`gradle/wrapper/gradle-wrapper.properties` のversionを変更し、`./gradlew wrapper` を実行する
gradleのversionは[こちら](https://gradle.org/releases/)で確認できる。

---
```
* What went wrong:
Execution failed for task ':compileKotlin'.
> Kotlin could not find the required JDK tools in the Java installation '/usr/lib/jvm/java-1.8-openjdk/jre' used by Gradle. Make sure Gradle is running on a JDK, not JRE.
```
エラーメッセージの通りで、JREではなくJDKを指定する。
参考のリポジトリのDockerfileが `ROM openjdk:8-jre-alpine` になっている為、`openjdk:8-jdk` に修正する。
### :link: 参考になったURL 
****
- [Docker - Quick Start - Ktor](https://ktor.io/quickstart/quickstart/docker.html)
- [KtorでWebAPI - Qiita](https://qiita.com/kalzit/items/2ea4e8b83e2e1cceeadb)