早速GAE/Go go1.11の次世代ランタイム試してみた

Betaが公開されたので早速触ってみたのでサラッと書いてみた

## go1.9ランタイムからgo1.11ランタイム（2nd gen）に移行するためには最低限やることと気をつけること

中身的には [このへん](https://cloud.google.com/appengine/docs/standard/go111/go-differences) の内容です。

### app.yamlで `go111` ランタイムを指定する

今までは多分 `api_version` で指定していたと思いますが、 `api_version` は **Deprecated** になります。  
そのため、 `runtime` へ指定します。  

また、以下のタグも同様に **Deprecated** になります

- application_readable
- builtins
- login
- libraries
- threadsafe
- skip_files

Deprecatedのフィールドを利用している場合、deploy時にエラーになるようです。

### サービス単位でmainパッケージが必要

旧世代のランタイムだとinitで初期化できていましたが、今後はmainパッケージが必要になります。  

### appengine ビルドタグが廃止されます

~~なんだっけ？これ。~~

**追記 2018/10/16**

`// +build !appengine` みたいなやつですね。  
GoのBuild Constraintsという仕組みを利用しており、[こちら](https://qiita.com/hnw/items/7dd4d10e837158d6156a) に説明があります。  
appengineの場合はtenntennさんからコメントで頂いているように、unsafeパッケージなどをライブラリで利用していた場合にappengineでは利用できないため指定されている場合などがあったようです。  

実際、unsafeが利用できるようになったので以下のようなコードをappengineにデプロイすることができました。  
byteをStringにする際に最速の変換方法です。

```golang
b := []byte("ハローワールド")
w := *(*string)(unsafe.Pointer(&b)) // []byte -> string
fmt.Println(w)  // ハローワールド
```

### 依存関係をプロジェクトに取り込む方法が変わって以下の2パターン

- アプリケーションのコードと依存関係をGOPATHに配置する
- go.modファイルを利用して定義する

### Appengine固有のAPIではなく、Google Cloudクライアントライブラリを使って！

~~GAE/FEやGKEと同様にGoogleCloudクライアントライブラリを利用する必要があります。~~  
[このへん](https://cloud.google.com/appengine/docs/standard/go111/go-differences#migrating-appengine-sdk) にまとまっています。  
~~TQ、Datastore、AppengineMemcacheなどを利用していた方はもれなく移行が必要です。~~

appengineパッケージが必要な場合には、google.golang.org/appengineパッケージが利用できます。

**追記 2018/10/16**

Appengine SDKからCloud SDKに絶対移行する必要がある。という感じで書いてしまっていましたが、訂正です。

- CloudSDKへの移行は `strongly recommend` であって必須ではありません。
- google.golang.org/appengineパッケージ(Appengine SDK)を引き続き利用できますが、その際にはmainパッケージで`appengine.Main()` を実行する必要があります。
    - appengine.Main内で `internal.Main()` を呼んでおり、内部で `http.ListenAndServe` を行っているので、main内に自分で実装する必要はありません。（下にMainの実装など書いていますが。）

apstndbさんご指摘ありがとうございます。

### go1.11ランタイムを利用する場合にはgcloud app deployコマンドを利用する必要があります

goapp コマンドはもう卒業できます。  
gcloudコマンドでのデプロイも依存をきれいに解決してくれているように見えるので（vendorなど）もうgcloudで特にトリッキーなgopath設定などしなくても大丈夫なはず。（ちゃんと確認してない）

## mainってどうすればいい？

mainのサンプルは [ここ](https://github.com/GoogleCloudPlatform/golang-samples/blob/master/appengine/go11x/helloworld/helloworld.go) にあります。  

```go
http.HandleFunc("/", indexHandler)

// [START setting_port]
port := os.Getenv("PORT")
if port == "" {
	port = "8080"
	log.Printf("Defaulting to port %s", port)
}

log.Printf("Listening on port %s", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
// [END setting_port]
```
こんな感じにすると良いようです。  
~~START-ENDのところを `appengine.Main()` を呼んでも問題なく動いているように見えます（推奨されるのか不明だけど）~~

**追記 2018/10/16**

上で書いたように、Appengine SDKを利用する場合には `appengine.Main()` を実行しておく必要があります。

## httpハンドラを登録する

ハンドラを登録するには以下のいずれかを利用する必要があります

- http.HandleFunc()の呼び出しをすべてmainに持ってくる
- http.HandleFunc()を含むinit関数が起動時に呼び出されるようにパッケージをmainパッケージにインポートする

後者の場合、以下のコマンドが便利

```shell
gp=$(go env GOPATH) && p=$(pwd) && pkg=${p#"$gp/src/"} && find . -name "*.go" | xargs grep "http.HandleFunc" --files-with-matches | grep -v vendor/ | grep -v '/main.go' | sed "s#\./\(.*\)/[^/]\+\.go#\t_ \"$pkg/\1\"#" | sort | uniq
```

## 気づいた点

- ログが旧ランタイムのときのようにリクエストコンテキストでまとまって出力がされなくなっている
  - 現状のFE、GKE、GAE 2nd-gen ランタイム は共通してそのようになっているようなのでしょうがない。
- uploadしたくないファイルは `.gcloudignore` ファイルで指定できるっぽい（.gitignoreと同じ記述で指定できる）  

サラッと触ってみた結果はこんな感じ。
改めてもう少し触ってみて記事を書こうと思う