Spring-Boot + KotlinのWebアプリケーションをGoogleAppEngine(GAE)のスタンダード環境へデプロイする

# 概要
GAE環境にSpringBootのアプリケーションをデプロイするのが簡単になっていたので試しました。
いくつかのサイトで手順が紹介してありますが、どうもわかりづらかったり古かったりしたので2018年1月時点で最新の方法を試してみます。

# 参考
githubに公開されている「getting-started-java」の手順通りやるとできます。
それでは味気ないので、今回はSPRING INITIALIZRで作ったプロジェクトをGAE環境へデプロイしました。

※「getting-started-java」には、他にもいろんなサンプルがありますが今回はkotlin-springboot-appengine-standardを参考にしました。

https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/appengine-standard-java8/kotlin-springboot-appengine-standard

# 手順

## Spring Bootの雛形作成

作業用のディレクトリを用意し、そこに移動して作業します。
まずはSpringBootの雛形をSPRING INITIALIZRのサイトをcurlで叩いて作成します。
GAE環境にデプロイするためにはwarプロジェクトにしないといけないようです。

- dependencies: web
- language: kotlin
- packaging: war

```
$ curl -s https://start.spring.io/starter.tgz -d dependencies=web -d language=kotlin -d packaging=war | tar -xzvf -
x mvnw
x .mvn/
x .mvn/wrapper/
x src/
x src/main/
x src/main/kotlin/
x src/main/kotlin/com/
x src/main/kotlin/com/example/
x src/main/kotlin/com/example/demo/
x src/main/resources/
x src/main/resources/static/
x src/main/resources/templates/
x src/test/
x src/test/kotlin/
x src/test/kotlin/com/
x src/test/kotlin/com/example/
x src/test/kotlin/com/example/demo/
x .gitignore
x .mvn/wrapper/maven-wrapper.jar
x .mvn/wrapper/maven-wrapper.properties
x mvnw.cmd
x pom.xml
x src/main/kotlin/com/example/demo/DemoApplication.kt
x src/main/kotlin/com/example/demo/ServletInitializer.kt
x src/main/resources/application.properties
x src/test/kotlin/com/example/demo/DemoApplicationTests.kt
```

## pom.xmlの編集

雛形で作成されたpom.xmlを以下のように修正します。

- spring-boot-starter-webからjul-to-slf4jとspring-boot-starter-tomcatを除外
- javax.servlet-apiのバージョンを3.1.0に固定
- spring-boot-starter-tomcatの依存を削除
- pluginにappengine-maven-pluginを追加

```
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- ココを追加 -->
        <exclusions>
                <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>jul-to-slf4j</artifactId>
                </exclusion>
                <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
        </exclusions>
        <!-- 追加ココまで -->
</dependency>
```

```
<!-- ココを追加 -->
<dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <type>jar</type>
</dependency>
<!-- 追加ココまで -->
```

```
<!-- 消す
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
</dependency>
-->
```

```
<plugins>
    ...
    <!-- ココを追加 -->
    <plugin>
           <groupId>com.google.cloud.tools</groupId>
           <artifactId>appengine-maven-plugin</artifactId>
           <version>1.3.1</version>
           <configuration>
           </configuration>
    </plugin>
    <!-- 追加ココまで -->
</plugins>
```

## gcloud initで初期化

事前に[CLOUD SDK](https://cloud.google.com/sdk/?hl=ja)を導入してください。

`configuration name`と`Project ID`を`spring-boot-demo-20180122`にしています。
`Project ID`は入力するタイミングがわかりづらいので注意です。

```
$ gcloud init
Welcome! This command will take you through the configuration of gcloud.

Settings from your current configuration [api-project-sample-test02] are:
core:
  account: example@gmail.com
  disable_usage_reporting: 'False'
  project: api-project-sample-test02

Pick configuration to use:
 [1] Re-initialize this configuration [api-project-sample-test02] with new settings 
 [2] Create a new configuration
 [3] Switch to and re-initialize existing configuration: [default]
Please enter your numeric choice:  2

Enter configuration name. Names start with a lower case letter and 
contain only lower case letters a-z, digits 0-9, and hyphens '-':  spring-boot-demo-20180122
Your current configuration has been set to: [spring-boot-demo-20180122]

You can skip diagnostics next time by using the following flag:
  gcloud init --skip-diagnostics

Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.                                                                                                                                                                        
Reachability Check passed.
Network diagnostic (1/1 checks) passed.

Choose the account you would like to use to perform operations for 
this configuration:
 [1] example@gmail.com
 [2] Log in with a new account
Please enter your numeric choice:  1

You are logged in as: [example@gmail.com].

Pick cloud project to use: 
 [1] api-project-sample-test
 [2] api-project-sample-test01
 [3] api-project-sample-test02
 [4] Create a new project
Please enter numeric choice or text value (must exactly match list 
item):  4

Enter a Project ID. Note that a Project ID CANNOT be changed later.
Project IDs must be 6-30 characters (lowercase ASCII, digits, or
hyphens) in length and start with a lowercase letter. spring-boot-demo-20180122
Your current project has been set to: [spring-boot-demo-20180122].

Not setting default zone/region (this feature makes it easier to use
[gcloud compute] by setting an appropriate default value for the
--zone and --region flag).
See https://cloud.google.com/compute/docs/gcloud-compute section on how to set
default compute region and zone manually. If you would like [gcloud init] to be
able to do this for you the next time you run it, make sure the
Compute Engine API is enabled for your project on the
https://console.developers.google.com/apis page.

Your Google Cloud SDK is configured and ready to use!

* Commands that require authentication will use example@gmail.com by default
* Commands will reference project `spring-boot-demo-20180122` by default
Run `gcloud help config` to learn how to change individual settings

This gcloud configuration is called [spring-boot-demo-20180122]. You can create additional configurations if you work with multiple accounts and/or projects.
Run `gcloud topic configurations` to learn more.

Some things to try next:

* Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command.
* Run `gcloud topic -h` to learn about advanced features of the SDK like arg files and output formatting
```

## gcloud app createでアプリケーションを作成

`region`は、asia-northeast1([locations](https://cloud.google.com/appengine/docs/locations))が東京なので、7番を指定しています。

```
$ gcloud app create
You are creating an app for project [spring-boot-demo-20180122].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.

Please choose the region where you want your App Engine application 
located:

 [1] europe-west2  (supports standard and flexible)
 [2] us-central    (supports standard and flexible)
 [3] europe-west   (supports standard and flexible)
 [4] europe-west3  (supports standard and flexible)
 [5] us-east1      (supports standard and flexible)
 [6] us-east4      (supports standard and flexible)
 [7] asia-northeast1 (supports standard and flexible)
 [8] asia-south1   (supports standard and flexible)
 [9] australia-southeast1 (supports standard and flexible)
 [10] southamerica-east1 (supports standard and flexible)
 [11] northamerica-northeast1 (supports standard and flexible)
 [12] cancel
Please enter your numeric choice:  7

Creating App Engine application in project [spring-boot-demo-20180122] and region [asia-northeast1]....done.                                                                                               
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.
```
## ローカル環境でAppEngineを実行

```
mvn appengine:run
```

ただし、途中で`on project demo: Dev App Server does not support App Engine Flexible Environment applications.`というエラーがでます。

```
...(省略)...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-war-plugin:2.6:war (default-war) @ demo ---
[INFO] Packaging webapp
[INFO] Assembling webapp [demo] in [/Users/tgoto/example/target/demo-0.0.1-SNAPSHOT]
[INFO] Processing war project
[INFO] Webapp assembled in [342 msecs]
[INFO] Building war: /Users/tgoto/example/target/demo-0.0.1-SNAPSHOT.war
[INFO] 
[INFO] --- spring-boot-maven-plugin:1.5.9.RELEASE:repackage (default) @ demo ---
[INFO] 
[INFO] <<< appengine-maven-plugin:1.3.1:run (default-cli) < package @ demo <<<
[INFO] 
[INFO] 
[INFO] --- appengine-maven-plugin:1.3.1:run (default-cli) @ demo ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12.241 s
[INFO] Finished at: 2018-01-21T20:24:57+09:00
[INFO] Final Memory: 51M/732M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.google.cloud.tools:appengine-maven-plugin:1.3.1:run (default-cli) on project demo: Dev App Server does not support App Engine Flexible Environment applications. -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
```

これに対応するため以下を実行します。

### appengineの設定ファイルの作成

ここで、[サンプル](https://github.com/GoogleCloudPlatform/getting-started-java/tree/master/appengine-standard-java8/kotlin-springboot-appengine-standard/src/main/webapp/WEB-INF)の２つのファイルを持ってきます。

- ディレクトリを作成します

```
mkdir -p src/main/webapp/WEB-INF
```

- 作ったディレクトリに移動します

```
cd src/main/webapp/WEB-INF
```

- appengine-web.xmlを取得します。

```
curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/getting-started-java/master/appengine-standard-java8/kotlin-springboot-appengine-standard/src/main/webapp/WEB-INF/appengine-web.xml
```

- logging.propertiesを取得します。

```
curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/getting-started-java/master/appengine-standard-java8/kotlin-springboot-appengine-standard/src/main/webapp/WEB-INF/logging.properties
```

#### 取得結果

```
$ ls -l
total 16
-rw-r--r--  1 tgoto  713033059   885  1 21 20:26 appengine-web.xml
-rw-r--r--  1 tgoto  713033059  1027  1 21 20:27 logging.properties
```

### もう一度実行

```
mvn appengine:run
```

起動します。

```
...(省略)...
[INFO] GCLOUD: 2018-01-21 20:28:40.503:INFO:oejs.Server:main: Started @4831ms
[INFO] GCLOUD: 1 21, 2018 11:28:40 午前 com.google.appengine.tools.development.AbstractModule startup
[INFO] GCLOUD: 情報: Module instance default is running at http://localhost:8080/
[INFO] GCLOUD: 1 21, 2018 11:28:40 午前 com.google.appengine.tools.development.AbstractModule startup
[INFO] GCLOUD: 情報: The admin console is running at http://localhost:8080/_ah/admin
[INFO] GCLOUD: 1 21, 2018 8:28:40 午後 com.google.appengine.tools.development.DevAppServerImpl doStart
[INFO] GCLOUD: 情報: Dev App Server is now running
```

## GAE環境へデプロイ

```
$ mvn appengine:deploy
```

デプロイできました。

```
...(省略)...
[INFO] GCLOUD: #============================================================#
[INFO] GCLOUD: #= Uploading 42 files to Google Cloud Storage               =#
[INFO] GCLOUD: #============================================================#
[INFO] GCLOUD: File upload done.
[INFO] GCLOUD: Updating service [default]...
[INFO] GCLOUD: .............done.
[INFO] GCLOUD: Updating service [default]...
[INFO] GCLOUD: Waiting for operation [apps/spring-boot-demo-20180122/operations/b92507ce-91de-4d88-9bbf-0ec08abca807] to complete...
[INFO] GCLOUD: ...done.
[INFO] GCLOUD: done.
[INFO] GCLOUD: Deployed service [default] to [https://spring-boot-demo-20180122.appspot.com]
[INFO] GCLOUD: 
[INFO] GCLOUD: You can stream logs from the command line by running:
[INFO] GCLOUD:   $ gcloud app logs tail -s default
[INFO] GCLOUD: 
[INFO] GCLOUD: To view your application in the web browser run:
[INFO] GCLOUD:   $ gcloud app browse
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:02 min
[INFO] Finished at: 2018-01-21T21:20:21+09:00
[INFO] Final Memory: 31M/581M
[INFO] ------------------------------------------------------------------------
```