emahiro/b.log

Drastically Repeat Yourself !!!!

AppEngineのGo1.11.0のランタイムについて浅く調べてみた

GAE/GoでGo1.11のランタイムがBetaになっていたので調べた内容をまとめてみます。
なお、基本的にはドキュメントに書いてあることを舐めてるだけの内容になります。

参考

先に参考を明示しておきます。ここだけ見れば、GAE/Goを現在使ってる人ならその差分がざっくりわかるはず。

変更点と影響

Go1.9 -> Go1.11について

※ 上記の参考のURLを元に書き出したのみです。なので正しい情報は本家のマイグレーションのドキュメントまで。

You specify the Go 1.11 runtime in your app.yaml file by using the line runtime: go111. For more information, see Changes to the app.yaml file.

app.yamlのファイルに runtime:go111 と書く。
これはそのうち go:1 とだけ書いてあったら自動的にgo1.11でデプロイされるようになったりしそうな気もします。 ただ、後述する1st -> 2nd への世代交代に置いて下位互換はほぼ切られてると言っても過言でない状態なのでこれはこれでされたら困るから多分正式リリースに当たって何かしらアナウンスがあると思われます。

またいくつかの app.yaml の設定がduprecatedになっている。コアに使うところなものは少ないが影響がありそうなのが skip_files が非推奨になり、代わりに .gcloudignore を使わないといけなくなります。
.gcloudignore ファイルについてはこの辺を参照するといいと思います。 -> gcloud topic gcloudignore  |  Cloud SDK  |  Google Cloud

記載方法は .gitignore と変わらない書式だからそんなに難しくないと思います。
なお、.gcloudignore ファイルがないとそもそもデプロイ時にコケます。

Each of your services must include a main package. For more information, see Creating a main package and Structuring your files.

initではなく、 第二世代AppEngineでは 各パッケージの初期化にはmainが必要
[追記] main.go以外のpackageの初期化は init で可能でした。

main.goにはmain関数が必須になる。
またマイグレーション方法にも記載されてるディレクトリ構成にする必要がある(らしい)。

app/
  hoge.go
  app.yaml

では動かずに

app/
  web/
    main.go
    app.yaml
  hoge.go

のように従来の第1世代とは異なりmain packageと同階層にyamlがあることが求められます。
これ自体はディレクトリ構造を変えればいいものなんですが、依存関係も含めてdeploy時にアップロードされるのかというところは既存のプロジェクトから移行するときに注視しないといけないかもしれません。 (deploy時にmain pachageとyamlファイルが別階層に置いてたらvendorとかが正常にアップロードされなかったりという話が聞こえてきたり...)

また気になったのは、マイグレーションドキュメントに

How you import dependencies into your project has changed. For the Go 1.11 runtime, specify dependencies either by: - Putting your application and related code in your GOPATH. - Or, creating a go.mod file to define your module.

とあって、そもそも依存関係を取り込む方法が異なってる可能性があります。
go1.11に移行した場合にlocalパッケージと同様に外部パッケージもGOPATH配下に直置きしないといけないような気がします。

実際にドキュメントの中でも

Move all relevant files and imports to your GOPATH. If using relative imports, such as import ./guestbook, update your imports to use the full path: import github.com/example/myapp/guestbook.

という記載あるように GOPATH 配下に関連するファイルやimport対象を配置することとimportするときも相対pathでなくGOPATHから見たときの絶対path指定になっているのでちゃんと配置しないとそもそもimportが参照してくれないのでは?と想定してます。

そのためおそらくですが、

GOPATH
 src
   hoge.go
   vendor
    github
      fuga.go

みたいなディレクトリ構成だとうまくいかず、

GOPATH
 src
   hoge.go
   github
    fuga.go

こんな感じだとうまくいくんだろうか...?と思ってます。
この点はまだ試してませんが、これだとGOPATH配下のプロジェクトのsrc配下に大量に依存パッケージのディレクトリできることになるけど、そういうもんなんだろうか?
deploy時にコツコツ go get するか、さもなくば glide とかでinstallしてきたものをアプリケーションの起動前に全部 GOPATH/src 配下に移すとかそういう力技に頼る他なさそう?超めんどそう....こういう時のための 1.11 以降入った go.mod が効いてくるのだろうか?
ただ、go.modについては僕も使い方よくわかってないので割愛。

The appengine build tag is deprecated and will not be used when building your app for deployment. Ensure your code still functions correctly without it being set

ビルドタグなくなります。後述しますが、urlfetch使ってるユースケースもなくなるからまぁgo1.11にしても困らないはず...。

App Engine no longer modifies the Go toolchain to include the appengine package. We strongly recommend using the Google Cloud client library or third party libraries instead of the App Engine-specific APIs. For more information, see Migrating from the App Engine Go SDK.

後述。google.golang.org/appengine が非推奨になります。 cloud.google.com/go 配下のライブラリを使え、とのこと。

Services using the Go 1.11 runtime must be deployed with the gcloud app deploy command. The appcfg.py commands can still be used with the Go 1.9 runtime, but the gcloud command-line tool is preferred.

デプロイは gcloud app deploy のみ。 appcfg は使えなくなる。

Go1.11に対応したことで何が変わるのか?

gVisor対応

以前から今年中に来ると言われていたGoogle AppEngineの2nd GenerationがGoのランタイムでも使えるようになります。
gVisorはなんぞや、という人は以下の資料とかを目を通してみるといいかもしれません。
refs: gVisor と GCP

1st GenerationのApp Engine、GoのランタイムにはGAEならではの様々な制約が存在し、それゆえに融通が効かないことがいくつか存在しました。
しかし、2nd Generationになることでいくつかの制約がなくなります。一方で今まで使えていたものが使えなくなります(※1)

※1 いくつかは仮説なので実際に検証しきれていないところがありますので「ふーん」程度に思っていただければと思います。

AppEngine Context が作られなくなる。

GAE/GoでHTTPリクエストを実装したりするときには、AppEngine Context を使うことが必須でした。

ex. 従来のGoのランタイムで一番簡易的なHTTPの実装

func handler(w http.ResponseWriter, r *http.Request) {
        ctx := appengine.NewContext(r)
        client := urlfetch.Client(ctx)
        resp, err := client.Get("https://www.sample.com")
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
        fmt.Fprintf(w, "HTTP GET returned status %v", resp.Status)
}

refs. Issuing HTTP(S) Requests  |  App Engine standard environment for Go  |  Google Cloud

httpのリクエストからhandlerで appengine.NewContext によりApp EngineのContextを作って、それを使ってHTTPのリクエストを組み立てていくことが一般的なGAE/Go におけるHTTPの実装のパターンではありましたが、Go111になると appengine.NewContext でAppEngineのコンテキストが作られなくなります。

App Engine packageが非推奨になる

[20181105更新]
cloud.google.com/go が推奨されてるだけで、appengineパッケージ自体が使えなくなる訳ではないです。そのため「〇〇は使えない」と記載してる箇所は使い続けられます。

google.golang.org/appengine から、Goの標準ライブラリ、cloud.google.com/go を使うことが推奨されます。そのため推奨ライブラリを使った場合、幾つのかの今まで使えていたライブラリが使えなくなります。
(AppEngineのcontextが使えなくなることに起因していることがほとんどですが。。。)

urlfetchが使えなくなる

appengine.NewContext でAppEngineのContextが作られなくなるのとほぼ同義ですが、urlfetchも合わせて使えなくなります。
サービス開発に置いてHTTPリクエストをしないサービスというのは皆無だと思うので、1st GenerationのAppEngineのGoのRuntimeで動いていたサービスはほぼこのurlfetchを使ってAppEngine外部の外界との通信を実装していると思われますが、それが使えなくなります。
代わりにGAE/Goでもgoの標準のHTTPパッケージである net/http パッケージを使えるようになります。

urlfetchが使えなくなるデメリットはもちろんなるんですが、メリットとしては1st GenerationのGAE/Goではurlfetchをするためにだけにすべてのリクエストに対して毎回20ms程度の通信コストが発生していましたので、これがなくなることによりその通信コストも多分丸っとなくなります。

appengine/logが使えなくなる。

同じですが、appengineのcontextが作られなくなるので、func Debugf(ctx context.Context, format string, args ...interface{}) というシグネチャにおいて ctx = appengineのcontextを受け取ってるこのライブラリが使えなくなります。

これはstackdriver loggingでログを見たときにうまい具合にリクエストごとのログをネストしてくれていたので結構痛い...。
ちなみに、標準のログパッケージを使うといい感じにネストしてくれません。なのでログが非常に見づらくなります。

cloud.google.com/go/logging を使えば解消したりしないのかなぁ。。。

appengin/datastore、memcacheが使えなくなる

これもappengineのcontextが作られなくことに起因してますが、

の二つが使えなくなります。この二つが使えないことにより、いい感じにdatastoreの結果をmemcacheに乗っけてくれる便利なライブラリ goon が使えなくなります。

その他

appengine/aetest とかも使えなくなりますね。

とりあえず普段仕事でメインで使ってる google.golang.org/appengine 配下のライブラリで使えなくなるものをまとめて見ました。代替手段は全く思いついてません。
仕事でも同僚とgo1.11がβになってから話してましたが、 goon 依存が大きいのでこれほんとどうしよう...と思ってます。
通常のmanagedなmemcacheを使うか、それともGAE SEからCloud MemoryStore(フルマネージドredis)が叩けるようになるのを待つか、、、など課題が山積みだったりします。

まとめ

App Engineの世代交代に合わせて結構大幅に下位互換が切られてました。

go1.11にするメリットはもちろんあれど、すでに第1世代で動いているシステムのマイグレーションについてはいい打ち手が現段階では思いついてません。

またプラットフォーム依存の課題もあると思っています。
というのも、去年の10月ごろに正式リリースになったAppEngineのgo1.8のランタイムですら。すでに今月末でサポートは打ち切られて1.9以上にしないといけなくなっていますし、いつ1.11に強制移行させられるかわからないのではないか?っと割と戦々恐々としてます。 もちろん 1.8 -> 1.9への以降については下位互換が担保されたままだった上に、プロダクション環境でほとんど影響があるような変更はなかったので特にサポートする必要もないだろうってことで打ち切られたんだと思います。
しかし今回の1.11には大きめな変更が加えられているので、そうそう第1世代のサポートが打ち切りになるということはないような気もしてますが、プラットフォームの上で仕事してる以上、プラットフォームの決定が絶対なので本当にそうなったらどうしよう..っと思ってる次第です。

本エントリーでは公式のドキュメントを読み漁って現行環境との違いを抽出、および影響がありそうなところをざっと書き出してみた程度なので、特にデプロイ周りで依存関係を取り込んだり、App Engine Specific Libraryから標準package、cloud.google.com/go への置き換えでうまくいくかどうか、あたりはちゃんと調べてみようと思います。

[追記]

浅くしか調べてなかったので appengine のpackageを最新にした状態で appengine.Main() を使えば 1.9以前と同様に1.11の環境も使えることを知りました。google.golang.org/appengine のパッケージを使えないというわけではなく、あくまでこれから使うなら他のやつ使ってね、くらいの温度感だと理解して、既存の環境どうしようか戦々恐々する必要ないということはこのエントリー書いたあとにも色々調べてわかりました。

[追記] updated_at 20181126

GCPUGのnouhauにマイグレーションに関する資料が上がっていたので追記します。すごくわかりやすい。

https://github.com/gcpug/nouhau/tree/feature/id/76/app-engine/note/gaego19-migration-gaego111github.com