Google App EngineのJava 11アプリをGradleでビルドする（Ktor + Gradle Kotlin DSL）

*姉妹記事: [Spring Boot版](https://qiita.com/orangain/items/7a072ce53e931c7f6ee0)*

この記事では、Google App EngineのJava 11ランタイムを使うアプリケーションをGradleでビルド・デプロイする方法を解説します。

この構成は意外と正確な情報がありません。App Engine [Java 11ランタイムのサンプルコード](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java11)はMavenが使われています。[Gradleを使うドキュメント](https://cloud.google.com/appengine/docs/standard/java11/using-gradle)もあるものの、プラグインのバージョンが古かったり、不要なはずのwarの設定があったりします。

さらにGradle Kotlin DSLを使う場合の詰まりどころもあるので、Ktor + Gradle Kotlin DSLのGradleプロジェクトをデプロイするまでをまとめておきます。

## 前提

### 環境

* macOS: 10.14.6
* Ktor: 1.2.4
* Gradle: 5.6.2
* Kotlin: 1.3.50

### 古い情報で混乱しないために知っておくと良いこと

* Google App EngineのJava 11はStandard環境第2世代のランタイム。
  - 設定には `appengine-web.xml` ではなく `app.yaml` を使う。
  - Webアプリケーションコンテナではなく、jarとしてビルドしたものを実行するので、war関連の設定は不要。
* Gradle 4.0以降では、Google App Engine Gradle plugin 2.xを使う。

## 手順

### 1. Ktor Project Generatorでプロジェクトをダウンロード

[Ktor Project Generator](https://start.ktor.io/)を開き、次の設定で「Build」してzipファイルをダウンロードします。

* **Gradle with Kotlin DSL project**
* **Server Engine: Nett**（デフォルト）
* **Ktor 1.2.4**（デフォルト）

### 2. Gradleを5.xにアップグレード

ダウンロードしたZIPファイルを展開してみると、Gradleのバージョンが4.xとやや古いです。Kotlin DSLサポートはGradle 5.xで改善しているので、次のコマンドで最新版にアップグレードしておきます。

```
./gradlew wrapper --gradle-version 5.6.2
```

### 3. routingを作成して動作確認

動作確認のため `src/Application.kt` に次のようにroutingを追記します。

```kotlin:Application.kt
package com.example

import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.routing.get // 追加
import io.ktor.routing.routing // 追加

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) {
    routing { // 追加
        get("/") {
            call.respondText("Hello, Ktor!")
        }
    }
}
```

次のコマンドで起動します。

```
./gradlew run
```

`http://localhost:8080` にアクセスして、 `Hello, Ktor!` と返ってくることを確認します。

### 4. Gradleで実行可能なFat Jarを作る

App EngineのJava 11ランタイムでは、`java -jar <jarファイル>`のような形で実行するので、生成するjarファイルには`Main-Class`のマニフェストと依存ライブラリを含める必要があります。デフォルトでは実行できないjarファイルが生成されてしまうため、[Gradle Shadowプラグイン](https://github.com/johnrengelman/shadow)を使ってfat jarを作ります[^1]。

[^1]: Spring Bootプロジェクトでは、Spring Bootプラグインの`bootJar`タスクがやってくれるので気にしなくて良いです。

`build.gradle.kts` に次の設定を追記します。

```kotlin:build.gradle.kts
plugins {
    application
    kotlin("jvm") version "1.3.50"
    id("com.github.johnrengelman.shadow") version "5.1.0" // 追加
}

// ...

tasks.assemble {
    dependsOn(tasks.shadowJar) // assembleの依存タスクとしてshadowJarを実行する。
}

tasks.shadowJar {
    archiveClassifier.set("") // fat jarを単独のjarと同じ名前にして上書きする。
}
```

`./gradlew build`を実行した後に、次のコマンドで起動できることを確認しておきます。

```
java -jar ./build/libs/ktor-demo-0.0.1-SNAPSHOT.jar
```

### 5. Google App Engine Gradle pluginを追加

[Google App Engine Gradle plugin](https://github.com/GoogleCloudPlatform/app-gradle-plugin)を使うために、`build.gradle.kts` に次の設定を追加します。

```kotlin:build.gradle.kts
plugins {
    application
    kotlin("jvm") version "1.3.50"
    id("com.github.johnrengelman.shadow") version "5.1.0"
    id("com.google.cloud.tools.appengine") version "2.1.0" // 追加
}
```

これだけだとプラグインをロードできないので、 `settings.gradle.kts` に次の設定を追加します。この設定については、 https://stackoverflow.com/a/48510049 を参照してください[^2]。

[^2]: https://github.com/GoogleCloudPlatform/app-gradle-plugin#how-to-use や https://stackoverflow.com/a/55075378 のように`buildscript`と`apply`を使う方法もありますが、Kotlin DSLでは`appengine {...}`と書けなくなってしまい、代わりに `configure<AppEngineAppYamlExtension> {...}` と書く必要があります。

```kotlin:settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
    }
    resolutionStrategy {
        eachPlugin {
            if (requested.id.id == "com.google.cloud.tools.appengine") {
                useModule("com.google.cloud.tools:appengine-gradle-plugin:${requested.version}")
            }
        }
    }
}
```

### 6. app.yamlを追加

トップディレクトリに`app.yaml` を次の内容で作成します。この設定は[サンプルコードのapp.yaml](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/master/appengine-java11/kotlin-ktor/app.yaml)からjarファイルの名前だけ変更してます。

```yaml:app.yaml
runtime: java11
instance_class: F2
entrypoint: 'java -Xmx32m -jar *.jar'
```

`build.gradle.kts` に次の設定を追記します。

```kotlin:build.gradle.kts
appengine {
    stage {
        setAppEngineDirectory(".") // app.yamlをトップディレクトリに置く
    }
}
```

次のコマンドで、デプロイ用の成果物を作成できることを確認します。

```
./gradlew appengineStage
```

次のように `build/staged-app` に `app.yaml` と jarファイルができていればOKです。

```
$ ls build/staged-app/
app.yaml			ktor-demo-0.0.1-SNAPSHOT.jar
```

### 7. Google App Engineにデプロイ

`build.gradle.kts` に次の設定を追加します。これらの設定は、プロジェクトIDをgcloudコマンドで設定済みのものにし、バージョンを自動生成するという意味なので、明示的に他の値にしても構いません。

設定については[Google App Engine Gradle pluginのUser Guide](https://github.com/GoogleCloudPlatform/app-gradle-plugin/blob/master/USER_GUIDE.md#deploy-1)を参照してください。

```kotlin:build.gradle.kts
appengine {
    stage {
        setAppEngineDirectory(".") // app.yamlをトップディレクトリに置く
    }
    deploy { // 追加
        projectId = "GCLOUD_CONFIG"
        version = "GCLOUD_CONFIG"
    }
}
```

次のコマンドでデプロイします。

```
./gradlew appengineDeploy
```

`https://<プロジェクトID>.appspot.com` にアクセスして、 `Hello, Ktor!` と表示されたら成功です。

なお `gcloud` コマンドでデプロイしたい場合は、 `./gradlew appengineStage && cd build/staged-app/ && gcloud app deploy` とすればOKです。

## まとめ

以上でデプロイまで完了です。この記事の実行結果のコードは次のリポジトリにあります。
https://github.com/orangain/appengine-java11-gradle-ktor


## 参考文献

* [Using Gradle and the App Engine Plugin  |  App Engine standard environment for Java 11 docs  |  Google Cloud](https://cloud.google.com/appengine/docs/standard/java11/using-gradle)
  * 公式の解説だが、Gradleプラグインのバージョンが1.xで古い点と、不要なはずのwarの設定がある点は注意。
* [Ktor Application on Google App Engine Standard with Kotlin](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java11/kotlin-ktor)
  * Ktor + Java 11のサンプルコード。Maven (not Gradle) である点がちょっと違う。
* [google app engine - How to configure AppEngine Gradle plugin using Kotlin DSL - Stack Overflow](https://stackoverflow.com/questions/48502220/how-to-configure-appengine-gradle-plugin-using-kotlin-dsl)
  * build.gradle.ktsでGoogle App Engine Gradle pluginを使う方法。
* [app-gradle-plugin/USER_GUIDE.md at master · GoogleCloudPlatform/app-gradle-plugin](https://github.com/GoogleCloudPlatform/app-gradle-plugin/blob/master/USER_GUIDE.md#app-engine-appyaml-based-projects)
  * App Engine Gradle Pluginのリファレンス。Java 11ランタイムの場合は「App Engine app.yaml based projects」を読むことに注意[^3]。

[^3]: https://cloud.google.com/appengine/docs/standard/java11/gradle-reference にもリファレンスがありますが、`projectId` が古い `project` のままになっていたして、やや古いようです。