開発環境をGKE運用からサーバーレス運用に変更してみた

# はじめに
本記事は[QualiArts Advent Calendar 2020](https://qiita.com/advent-calendar/2020/qualiarts) 3日目の記事です。
肥大化するゲーム開発の開発環境をサーバーレスにしようと試みている最中の話です。運用してみてやっぱりダメだった場合は戻しているかもしれません。
始まりはGCPの[Cloud Run](https://cloud.google.com/run)（サーバーレス）を使ってみたいという欲求だったため手段が目的化しています。導入理由を説明している記事ですが少し弱いです、ご了承ください。
terraformによるCloud Run関連(SecretManagerやIAM、CloudBuildによるCD等)の導入編は別記事で上げるかもしれません。

- **2020-12-07 追記** -
*[Google Cloud + Gaming Advent Calendar 2020](https://qiita.com/advent-calendar/2020/gcp-gaming) 7日目にCloud Run関連のアドカレを書かせていただきました。*「[Eventarcを使用した非同期でジョブを実行するSlackAppづくり](https://qiita.com/iyu/items/33dd85a2201d159680ac)」

# 背景
ゲーム開発では年々求められる機能の質も数も増えていき、開発規模が大きくなっています。並行して多くの機能を作るため１つのプロジェクトの中に名称はなくともいくつものチームみたいなもの（機能単位でスポットで集まっている等一時的なものも含めて）ができてきます。それに伴いサーバーアプリのコード差分（v1.1,v1.2,feature等）やデータ差分（翌週リリース分、翌々週リリース分,テストデータ等）を組み合わせた様々な環境が求められるようになり、開発環境（dev環境）はdev1,dev2,,dev10,dev11と増えていきました。
しかし、その10を超える開発環境は**業務時間中でもすべてがフルでアクセスされていることはなく、常時起動のVMで運用するのでは無駄にリソースを消費**していました。
常時起動の部分を改善すればいいのですがその部分をどうしようかと悩んでいたそんなときに目をつけたのがCloud Runで、「[Cloud Run は、リクエストがない場合はゼロにスケーリングし、リソースを使用しません。](https://cloud.google.com/blog/ja/products/containers-kubernetes/when-to-use-google-kubernetes-engine-vs-cloud-run-for-containers)」とのことなので使ってみたいと思っていました。

# それまでの開発環境
数年前まではGCEを利用し、１つのVMに1つの環境に必要なもの（DB,Cache,APIApp,AdminApp等）を用意していました。この構成では背景で述べたような利用方法ではリソースの無駄がとても多かったです。
近年では開発環境や本番環境でもGKEを利用しています。移行したきっかけはやはり使ってみたかった(笑)というのが大きく、理由は後付けで移行してみましたが使ってみてやはり便利です。本題から逸れるのでそのあたりは割愛します。

## GKEを開発環境にする上でのリソース管理の難しさ
GCEに比べGKEでは1つのnodeの中に複数の環境を置いておけるのでだいぶリソースの最適化ができるようになったと思います。もしGCEでやろうものなら大きいVMに複数環境を置くことになるのでしょうがポート管理等面倒なことを考えないといけなくなると思うと億劫です。GKEではうまいこと勝手にやってくれるのでどのnodeにpodがいるかなんて普段は気にしなくて済みます。
もし開発環境の利用状況が常にある程度のアクセスがあり、オートスケールするような状態だったらハッピーでした。dev1では現在10pod,dev2では5podのような負荷に合わせてオートスケールしてくれるのであればリソースをうまく活用してくれるでしょう。しかし、現実では1podあれば十分ですしアクセスが0,1で変化するような極端な変化では[GKEの垂直スケール](https://cloud.google.com/kubernetes-engine/docs/concepts/verticalpodautoscaler)も使えなさそうです（試してないです）。
### 余談: ごまかして使ってみた（失敗）
より少ないnodeで複数の環境を動かすためにpodのresource.requests.cpuを10mにしてlimitはつけない構成にしてみました。アクセスされないときはcpu使用率ほぼ0ですし、すべての環境が同時にアクセスされることなんて滅多になかったのでワンチャン行けるかなと・・・。
結果的には失敗です。プリエンプティブnodeによる24時間おきの再起動はすべての環境で初期起動時によるcpu使用を捌けずpod生成のリトライ地獄が起きてしまいました。初期起動がそこまで重いわけではないのです。10mしかリクエストしていないのにすべてのpodが300mとか利用してくるのです。分かりきっていました。（当たり前ですが本番環境ではlimitとrequestは同じ値または調整された値にしてます。）
しかしプリエンプティブ設定を消して毎日起こる事故を減らしていけば意外にも使えるのでたまに起きる事故に対応しながら結構な期間その構成で使っていました。


# サーバーレス
長くなりましたがここまでは前置きでVM運用におけるリソース管理が難しいという話でした。うまく管理するシステムを作れば特に問題はないと思います。しかし、うまい管理システムを考えるよりもマネージドサービスを利用したほうが楽ちんなので考えることをやめました。

## どうしてCloud Runを選んだか
Cloud Runを使いたかったのですって言ってしまうとそこで終わってしまうので理由を今考えていきます。
まずGCPを長らく使ってきているのでGCPサービスにしたいです。本番やステージングでは引き続きGKEを使っていますので。
GCPにおけるサーバーレスサービスには以下のものがあります。
https://cloud.google.com/serverless
- [Cloud Functions](https://cloud.google.com/functions)
- [App Engine](https://cloud.google.com/appengine)
- [Cloud Run](https://cloud.google.com/run)
本番でコンテナを使っているのでアプリ側のコードにサービス依存なコードを書きたくありませんでした。この時点でほぼCloud Runなわけですが加えてgRPCも使いたかったのでやはりCloud Runなのです。
別部分でCloud Functionsは使っていますが、App Engineは使ったことがないので機会があれば使ってみたいです。

## Cloud Runの問題点
現状[Cloud Armor](https://cloud.google.com/armor)や[Cloud IAP](https://cloud.google.com/iap)が使えません。GKEのときは利用していたのでここが一番のネックでした。なのでreverse proxyを作ってCloud Runサービスはフル公開せずproxy経由でアクセスしています。

## 料金面での比較
勘違いしてはいけません。Cloud Runが安いのではなくリソース最適化できていないVM構成に無駄があるのです。無駄がないのであれば逆に割高になるでしょう。
関連料金一覧リンク
https://cloud.google.com/compute/all-pricing
https://cloud.google.com/kubernetes-engine/pricing
https://cloud.google.com/run/pricing
これから行う比較方法は正確ではないです。GKEの方は設定次第ですが仮に１環境で合計1core扱う場合は1coreの料金が1環境になるのに対してCloudRunは１環境の中の1サービス(例えばAPIサーバーとChatサーバーと管理画面の３つのサービスで構成される１環境の中の1サービス)なので単純なCPU利用時間で比較・計算しにくいです。ご了承ください。

#### １ヶ月起動し続けたときの比較
通信料は無視しています。計算方法はものすごくざっくりしたものです。

- GKEの場合:
    - 東京 n1-standard-1(1CPU,3.75GB) **\$31.17/月**
- Cloud Runの場合
    - cpu: \$0.00002400/秒
        - \$0.00002400 * 60秒 * 60分 * 24時間 * 30日 = \$62.208/月
    - mem: \$0.00000250/秒
        - \$0.00000250 * 3.75GB * 60秒 * 60分 * 24時間 * 30日 = \$24.3/月
    - \$62.208 + \$24.3 = **\$86.508/月**

これだけ見ると倍以上の差があります。しかもVMの方は[継続利用割引](https://cloud.google.com/compute/docs/sustained-use-discounts)の適用でさらに安くなります。
ただしCloud Runの方は使ったときだけの計算となると１日２４時間も利用しませんし、１ヶ月で３０日も利用しません。
#### Cloud Runで1日に6時間、1ヶ月に20日間起動した場合
- Cloud Runの場合
    - cpu: \$0.00002400/s
        - \$0.00002400 * 60s * 60m * 6h * 20day = \$10.368/m
    - mem: \$0.00000250/s
        - \$0.00000250 * 3.75GB * 60s * 60m* 6h * 20day = \$4.05/m
    - \$10.368 + \$4.05 = \$14.418/m

急に安くなりました。１環境１日６時間っていうのも多い気がするのでもっとアクセス時間は少ない気がします。（アクセスログからどのぐらい利用してるか計算すればわかりそうではありますが。）まだ試験運用なので本運用時にどのぐらいの時間使うのかわかってないです。むしろ**Cloud Runには無料枠があり、試験運用等であれば実はほぼ無料で動かせます**。

#### GKEの方も深夜や休日はnode_pool削除すれば？
クラスタも有料化されたので今では１ヶ月連続稼働してると\$72はかかります。VM料金だけであればnode_poolの削除と作成の繰り返しだけで済むのですがクラスタごと再作成はなかなか大変です。自動化されている方もいるとは思いますが深夜判定を何時にするのかとか、休日対応時に起動してないと困るだとか、いろいろルールを考えるのが大変なのでサーバーレスを使いたいところです。

# まとめ
まだCloud Runでの開発環境運用は本当に最近導入したばかりでまだなんとも言えない状況です。冒頭でも書いたとおり戻すかもしれません。ただ少しでも開発環境のリソースが最適化されたら嬉しいなと思って見守っています。
また、今回触れてきませんでしたがCloud Runは導入が本当に簡単です。DockerイメージさえあればGCRにpushしてそのイメージを指定するだけです。VMのようなインスタンスタイプの調整等はありません。少し試してみたい等アクセス数が少ないのであればとても安いので使ってみるのも良いと思います。