GoogleAppEngineを開発で使う場合はversionを指定しよう

最近会社でGCPの費用が異常にあがったタイミングがあり、ざわついたことがありました。

結局のところGAEのdeploy周りが原因だったことが判明したのですが、GCPをカジュアルに使う方法としてGAEはよく使われるところだと思うので、状況と対策方法を共有しようと思います。

# 背景

会社ではいくつかのプロダクトがあって、多くのチームでGCPを使っています。
開発・staging・本番それぞれでプロジェクトを分けていたのですが、あるときたまたま開発の費用が増えていたことに気づきました。

<img width="1124" alt="gcp_cost.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5964/903849b1-850e-b44f-fe87-2b78c8e5d89f.png">

（青線のプロジェクトは開発環境で、赤線の本番プロジェクトを優に超えていた）

開発も多少費用は使っていましたが、さすがにこの超え方はおかしいということで調査することに。

# 原因

## その1

内訳を見てみると費用のほとんどはGAE(Google App Engine)だったのですが、開発環境だしそんなアクセス来てるわけないだろうと思ったら、GAEの過去のインスタンスがめちゃくちゃ残っていました。

GAEの場合はインスタンスさえ残っていれば、トラフィック割り当てされていなくてもお金が発生します。サーバー起動で費用がかかってる感じですね。
このときは数十台規模で残っていました。

<img width="882" alt="gae_instances.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5964/57bc3af2-3374-bb27-f402-793b4eef2cf9.png">

## その2

GAEのデプロイの設定では `promote` というオプションでdeployと同時に最新に更新するようにしているし、`stop-previous-version` というオプションがデフォルトで効くため以前のバージョンは停止されるはずでした。（実際に急増以前は停止されていた。）

よくよく見ると、deployの間隔が短いタイミングでインスタンスが残ってしまっていることがわかりました。

例えば現在`ver1`が動いている状態とします。

|version|
|:--|
|ver1|

この状態で`gcloud app deploy`すると`ver2`が作成され、previous-versionである`ver1`が消えることになります。

|version|
|:--|
|ver2|
|~~ver1~~|

この後`gcloud app deploy`すると`ver3`が作成されることになるのですが、`ver2`がまだdeploy中の場合は消えてくれないようです。

|version||
|:--|:--|
|ver3||
|ver2|←本来はこいつが消えてほしい|
|~~ver1~~||

ということで、本来消えてほしいはずの`ver2`が残ってしまいます。
ちなみにログを見ると以下のようなエラーを吐いています。

```:失敗時のエラー
Step #4: Stopping version [xxx-project/default/20191010t060217].
Step #4: WARNING: Version [xxx-project/default/20191010t060217] is still running and you must stop or delete it yourself in order to turn it off. (If you do not, you may be charged.)
Step #4: WARNING: Error stopping version [xxx-project/default/20191010t060217]: ABORTED: Cannot operate on apps/xxx-project/services/default/versions/20191010t060217 because an operation is already in progress for xxxxxxxxxxxxxxxxxxxxxxx by yyyyyyyyyyyyyyyyyy.
```

ただ、`gcloud app deploy`コマンド自体は成功して終了するので、更新失敗したことに気づきづらいです。
（ログには自分で前のバージョン消せよと書いてある、、）

更新頻度の高い開発現場だと数分の間に何度もdeployが走ることは良くあるかと思いますので、`gcloud app deploy`の取り扱いには注意が必要そうです。

## 原因その3

ちなみに、費用（とインスタンス数）が急激に上昇した理由はもう一つありました。

CDでCloud Buildを利用していたのですが、deployブランチを指定する欄で「正規表現を反転する」というオプションにチェックがついていました。

<img width="469" alt="cloudbuild.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5964/753f3afc-5264-d440-76a9-b6f0dfe4e282.png">

通常masterが更新されたタイミングでしかdeployされないはずだったので、更新頻度が高いといってもこの事象にハマるケースは限られていました。

ところが、正規表現が判定されたことで細かいfeatureブランチまでdeployされるという非常に残念なことに。

地味にこのオプション、クリックの判定が広いためにブランチ名を編集するときに気づかずにチェック入れてる可能性あるかもしれません。

deploy頻度高いなと思ったら、ここも一応チェックしてみると良さそうです。

# 対策

取りうる対策はこんなところかと思います。

1. deploy時間を短くする
2. version指定のdeployにする
3. Cloud Runなどに乗り換える

## 1. deploy時間を短くする

これは単純にdeploy時間を短くすると、deployがかぶる確率が下がります。

docker自体を軽くするとかCIとCDのタイミングを分けるとかして、deploy時間を短くしてみましょう。

根本解決にはならないかと思いますが、これはこれで価値があると思います。

## 2. version指定のdeployにする

`gcloud app deploy`には`version`を指定してdeployするオプションがあります。

https://cloud.google.com/sdk/gcloud/reference/app/deploy#--version

これを指定しておくことで、新しいバージョンを作って差し替えるのではなく、今のバージョンを更新することができます。
これによってインスタンスを増やすことなく新しいコードを反映させることができます。

一つ注意点としては、`version`を指定したdeployコマンドが連続した場合、新しい方のコマンドは以下のエラーが出て失敗してしまいます。

```
ERROR: (gcloud.app.deploy) ABORTED: Cannot operate on apps/xxx-project/services/default/versions/hogehogeversion because an operation is already in progress for ...
```

コケることを回避したいか、インスタンスが増殖することを回避したいかによって取りうるオプションは変わってくると思います。
が、開発環境はあまりお金をかけたくないと思うので、基本的には`version`を指定しておくことをおすすめします。(そしてdeployがコケたら再実行する)

## 3. Cloud Runなどに乗り換える

そもそも開発環境のように更新頻度高い環境では、GAEはあまり向いてないかもしれません。
Cloud Runは比較的その用途に向いているかと思いますので、そちらに移行するのも手だと思われます。

# おわりに

調査して色々学びがありましたが、何よりも調査するきっかけとなったコストデータの可視化が非常に重要だなと思いました。
可視化しておかないと、こういった事象が起きていることも検知できないですしね。