Edited at

GAE Go 1.11 ランタイムが公式には 2nd gen ではなくなった件について

Google Cloud Platform(以下 GCP) におけるサーバーレスは Google Cloud Next '19 会期中の 2019年4月10日に Cloud Run が発表されたことで新たな局面を迎えました。その裏で、歴史ある App Engine の Go 1.11 ランタイムの説明が Google の公式な説明がないまま変化したことが GCP コミュニティを騒がせそうなので、経緯と共に私の理解を書いておきます。

なおこの記事では Flexible Environment と明記している部分を除き Standard Environment について書いています。また、 Java 8 ランタイムについても同様のことが起こっていますが、今回は Go ランタイムについて言及します。


TL;DR


  • second generation(以下 2nd gen) ランタイムとしてリリースされた Go 1.11 ランタイムが突然 first generation(以下 1st gen) ランタイム扱いに変更

  • これからは gVisor ベースかどうかとは無関係に App Engine API のサポートがあるランタイムは 1st gen

  • 今後 App Engine ランタイムの 1st/2nd gen の区別について触れる時は新旧定義で混乱しないように注意


    • gVisor ベースでサンドボックスの制限がないにも関わらず App Engine API も利用可能なランタイムの扱いを明示しなければしばらく混乱する




経緯


App Engine 2nd gen ランタイム発表以前

2018年6月に発表した「Java 8 ランタイム以降のサンドボックスと gVisor」に記録されている通り、下記のことが 2nd gen ランタイム という名称が発表される前から認識されていました。


  • 従来の App Engine のランタイムはサンドボックス由来のライブラリ、スレッド、ネットワーク、ファイルアクセスへの制限があり App Engine 以外で動くようなアプリケーションの移植が困難


    • 特に gRPC コネクションを維持できないため GCP クライアントライブラリの正式なサポートもなく GCP の他サービスとの連携も困難



  • Java 8 ランタイムは既上の問題を gVisor ベースのサンドボックスに置き換えることで解決し、 App Engine API に加え GCP クライアントライブラリをサポート

  • Google I/O '18 で発表された Node.js ランタイムも gVisor ベースであり、今後は gVisor ベースのランタイムが増えていくこと


App Engine 2nd gen ランタイムの発表

Google Cloud Next '18 の会期中の2018年7月、 App Engine 2nd gen ランタイムという名称が発表されました。当時の記事には次のように書かれています。


Second generation runtimes provide developers idiomatic, open-source language runtimes capable of running any framework, library, or binary. Based on gVisor technology, these new runtimes enable faster deployments and increased application performance.


2nd gen ランタイムとは gVisor をベースとし、フレームワークやライブラリやバイナリの制限なく、オープンソースの処理系で動く慣用的(idiomatic)なアプリケーションが動くランタイム であると説明されています。これにより、以前から認識されていた gVisor ベースのランタイム2nd gen ランタイム が同一のものとして認識され、Go でも 2nd gen ランタイムが発表されることが期待されていました。


Go 1.11 ランタイム発表

そして Google Cloud Next London '18 会期中の2018年10月10日、 Go 1.11 ランタイムがベータリリースされました。Go ランタイムの Tech Lead, 後に PM となる Steven Buss による Google Groups へのアナウンス([google-appengine-go] Go 1.11 is now available on App Engine!) から引用します。


This is a "second-generation" runtime, meaning that we're now running stock Go in the gVisor sandbox. We've removed all of the restrictions present in the old runtime, like limited socket and file access. You can even import "unsafe"!


期待通り、 1st gen であった Go 1.9 ランタイム以前のサンドボックスの制約がなくなった 2nd gen ランタイムであると書かれています。更に次のように続きます。


Learn how to migrate from the Go 1.9 runtime to the new Go 1.11 runtime with this migration guide. For the time being, you can still use the legacy App Engine APIs with the Go 1.11 runtime, but you should start migrating to the Google Cloud client libraries.


サンドボックスの制限がなくなって GCP クライアントライブラリが使えるようになっても App Engine 固有の API を引き続きサポートする点は Java 8 と同様です。しかし、App Engine API からの移行を強く推奨する記述により Go 1.11 ランタイムは App Engine API をサポートしない という誤解が広まったこともあり、既存の App Engine ユーザーに混乱を生みました。現在は Migrating from the App Engine Go SDK (Optional) として任意の手順であることが明記されています。当時の App Engine API を取り巻く状況の説明については Hacker News: App Engine’s New Go 1.11 Runtime で Tech Lead の Steven Buss(buss) と PM の Steren Giannini(steren) の発言を読むのが良いでしょう。将来には App Engine API は廃止されるため可能なものは移行を推奨するが、 Go 1.11 ランタイムが廃止されるまではサポートする ことや、 Go 1.11 ランタイムは Go 1.9 ランタイムを置き換えるため、 Go 1.9 ランタイムにとどまり続ける選択肢はなくなる ことが書かれています。

Go 1.11 ランタイムはサンドボックスの制限がない 2nd gen でありながら App Engine API も使えるため、Go 1.9 以前の App Engine API を使うアプリケーションを最低限の変更で移行してから、置き換えが可能になったものからインクリメンタルに App Engine API を置き換えていくという将来的な廃止に備えるという過渡期のランタイムとして適していると考えられます。


Go 1.11 ランタイムの変遷

長くなりましたが、ここからが本題です。

Go 1.11 ランタイム発表直後の 2018年10月12日に確認した App Engine Standard Environment Runtimes では 1st gen と 2nd gen はサンドボックスの特性をベースにランタイムが2つの世代に明確に分類されていました。

image.png

その後、 2019年3月20日に Go 1.11 が GA となり、同年3月22日に Go 1.12 ランタイムがベータリリースされた直後の2019年3月27日には Go 1.11 と Go 1.12 の両方のランタイムが 2nd gen でした。 Go 1.12 ランタイムは示唆されていた通り App Engine API をサポートたバージョンであり、 Go 1.11 ランタイムは最後の App Engine API をサポートするバージョンとなりました。

image.png

これが Google Cloud Next '19 を控えた時期(最終更新日によると2019年4月4日頃?) に突然下のように変わりました。

上と比較すると Go 1.11 が 1st gen に変更されたことが分かります。

image.png

しかし、 Go 1.11 のリリース時のアナウンスを見ても、Isolation mechanismgVisor-based container sandbox であり、 Language runtimeUnmodified, open source runtime で他の 1st gen のものとは明確に異なるはずです。

この件について Google Cloud Platform Community Slack で質問したところ、 Go ランタイムの Product Manager の Steven Buss から Legacy API(App Engine API) のサポートがある Go 1.11 ランタイムは 2nd gen stack(gVisor ベース) に乗っているが 1st gen に分類されるという回答がありました。

スクリーンショット 2019-04-29 00.25.42.png


考察

整理するために App Engine Standard Environment Runtimes

の表を新旧両方の定義を合わせて見てみましょう。

App Engine API 対応
(新 1st gen)
App Engine API 非対応
(新 2nd gen)

サンドボックスの制限あり
(旧 1st gen)

Python 2.7
PHP 5.5
Go 1.9

サンドボックスの制限なし
(旧 2nd gen, gVisor based)

Java 8
Go 1.11
Python 3.7
PHP 7.2
Node.js
Go 1.12

(注: 厳密には Python 2.7 ランタイムは2018年10月15日には gVisor のサンドボックスに乗っているが、従来のサンドボックスの制限から十分に解放されてはいない。)

こう見ると、 サンドボックスの制限あり かつ App Engine API 非対応 のランタイムはないため



  • サンドボックスの制限あり かつ App Engine API 対応純粋な 1st gen ランタイム


  • サンドボックスの制限なし かつ App Engine API 対応過渡期のランタイム


  • サンドボックスの制限なし かつ App Engine API 非対応純粋な 2nd gen ランタイム

の3つに分けることができます。そして 過渡期のランタイム が古い定義では区別せずに 2nd gen と呼ばれていたのに対し、新しい定義では区別せずに 1st gen と呼ばれています。

現在の公式の表では 過渡期のランタイム に他と区別可能な名前をつけないことで、 First generation の列が混沌としたように見えます。

なぜ、このような混沌を呼んでまで Legacy API である App Engine API に対応しているだけで 2nd gen と呼んでいたランタイムを 1st gen と呼びたくなったのでしょうか?現時点ではあまり納得できる答えが得られていないですが、私は 2nd gen の特徴である idiomatic の解釈の変化ではないかと考えています。従来は、 idiomatic なコードは App Engine で動かすことができる という片方向の意味であったのが、 App Engine で動くコードは idiomatic であり(Cloud Run や GKE などの)他の実行基盤でも動かすことができる という双方向の意味に変わったことで、 App Engine でしか動かない App Engine API をサポートする Go 1.11 ランタイムは条件を満たさなくなり排除された という解釈です。しつこく聞いたら真意を聞くことができるかもしれません。

さて、 Go 1.11 ランタイムが 1st gen に分類されるようになってどのような実害があるでしょうか。

そもそも App Engine API は中長期的には廃止される予定であり、純粋な 2nd gen ランタイムでも古いバージョンのランタイムのサポートは廃止されていくと考えられるため、 1st gen と分類されることで App Engine のランタイムとしての変化はないと考えられます。しかし、コミュニティベースではいくつか問題があります。

まず、「Google App Engineの2nd Generationとは?」の著者が困っていたように、半年ほどの期間で書かれた以前の定義に基づいて書かれた下記のような記述が今の定義だと誤りになります。わざわざ修正する人は少ないでしょうから、混乱が続くと考えられます。


  • 「Go 1.11 は 2nd gen ランタイムである」

  • 「1st gen ランタイムにはサンドボックス由来の制限がある」

また、 2019年4月29日現在 Go 1.11 が gVisor ベースではない側に分類されているなど、公式ドキュメントに技術的に正しい定義は存在していません。この記事で書いた 1st gen と 2nd gen の定義が変わったという事実そのものも説明されていません。これでは 1st gen と 2nd gen に言及すること自体が困難です。

ここまでくると 過渡期のランタイム に GCP 公式の名称をつけてほしいところですが、 1st gen, 2nd gen について言及する時は、 Go 1.11, Java 8 を(含む|除く) 1st genGo 1.11, Java 8 を含む 2nd gen stack, Go 1.11, Java 8 を除く 2nd gen などのように、 過渡期のランタイムをどこに含めるのか を明示した方が無難だと考えられます。


追記

この記事ではこう書いてきましたが、App Engine API をサポートするかどうか、gVisor ベースかどうか、サンドボックスの制約があるかどうかのような属性を使って言及するのであれば、 2nd gen というエイリアスを使う必要がないため混乱を避けることができます。

私自身が 1st gen, 2nd gen という言葉にこだわっているのは、この言葉が生まれる前から App Engine と gVisor との関係を追っていて有用性を感じているからです。gVisor ベースというのは実装詳細であって、一般の App Engine のユーザーである開発者が意識するべき事項ではありません。そこで、 2nd gen という言葉によって、従来の App Engine の制限から解放されたランタイムを指し示すことに確かな有用性がありました。

現在の 2nd gen の定義を活用するとすれば、「新規開発では(App Engine API をサポートせず) App Engine にロックインされることのない 2nd gen ランタイムを推奨する」というようなことを言うことができます。しかし、 Go には純粋な 2nd gen ランタイムである Go 1.12 ランタイムがありますが、Java 8 が 2nd gen ランタイムではないため、 App Engine での新規開発に Java は推奨できないと解釈されてしまいます。将来的に純粋な 2nd gen ランタイムとしてリリースされると予想できる Java 11 ランタイムがパブリックリリースされてからであれば、 App Engine API をサポートするランタイムの選択肢を完全に排除するための言葉として有用になるかもしれませんが、現在の 2nd gen の定義は混乱を生みやすく、以前の定義よりも有用性に欠けるように感じます。


(2019年7月9日)追記2 Go 1.9 ランタイムの deprecation 発表後の状況

Go 1.11 ランタイム発表時に示唆されたとおり、 6月27日に Go 1.9 ランタイムの deprecation が発表されました。10月1日からは Go 1.9 ランタイムを使った App Engine Version はデプロイ不可能になるため、利用可能な 1st gen の Go のランタイムは gVisor ベースである Go 1.11 ランタイムだけになります。

純粋な 2nd gen ランタイムである Java 11 ランタイムも6月5日にベータリリースされたため、 Go 1.9 ランタイムがデプロイできなくなった後の分類は下記のようになります。

App Engine API 対応
(新 1st gen)
App Engine API 非対応
(新 2nd gen)

サンドボックスの制限あり
(旧 1st gen)

Python 2.7
PHP 5.5

サンドボックスの制限なし
(旧 2nd gen, gVisor based)

Java 8
Go 1.11
Python 3.7
PHP 7.2
Node.js
Go 1.12
Java 11


(2019年11月11日)追記3 App Engine Long-term support

Long-term support for standard environment runtimes のドキュメント(2019年11月7日更新)にて、現在残っている 1st gen ランタイムである Go 1.11, Java 8, Python 2.7, PHP 5.5 の4つのランタイムは Long-term support であることが明記されました。 Go 1.11, Java 8 以外も無変更の OSS への回帰(サンドボックスとしての gVisor の利用)が進んでいるため、 gVisor とは関係なく App Engine API を使えるものが 1st gen であるという形になりました。

1st gen のランタイムは Google によってセキュリティアップデートも行われ、ただちには廃止されることもありませんが、既に言語のコミュニティからはサポートされなくなったバージョンではあるため、新規採用は避け、必要に応じて移行を進めていく必要はあるでしょう。


付録: App Engine API を使えるとはどういうことか

App Engine API を使えるということは google.golang.org/appengine/internal/api.go の Call の定義によると、フロントエンドから渡された X-AppEngine-API-Ticket を使って ${API_HOST}:${API_PORT}/rpc_http(デフォルトでappengine.googleapis.internal:10001/rpc_http)を叩くことができるということであると考えられます。Go 1.12 ランタイムのように実際に App Engine API を使用できないランタイムでは「X-AppEngine-API-Ticket が渡ってこない」か「appengine.googleapis.internal:10001 と疎通できない」のどちらかもしくは両方であることが確認できます。


各 Go ランタイムと App Engine API サポートの関係図

各 Go ランタイムが App Engine API を使用する際の呼び出し経路は App Engine Flexible Environment(env: flex) および App Engine Flexible Environment 以前の Managed VM(vm: true) を加えて図にすると下記のようになっていると考えられます。

appengine.png

Go 1.11 では App Engine API 対応部分は単なるライブラリであって処理系への変更はなくなっています。