初めに
イベントドリブンなバックエンドをGCP上にデプロイする際にCloud Runを使用するか、Cloud Functionsを使用するか迷うことがあるので、それぞれのメリットデメリットを比較して判断できるように知識を整理します。永遠にWIPなので随時更新します。意見や間違いの指摘お待ちしております。
memo
一旦休憩
Trigger
Cloud Functions
対応しているトリガーは以下の通り。
- HTTP(S)
- PubSub
- Google Cloud Storage
- Firestore (CRUD)
- Firebase Authentication (UserのCreate, Delete)
- Firebase Analytics
- Firebase Realtime Database (CRUD)
今までPubSubとHTTP(S)しか使ってなかったけど、データベースやストレージ、認証処理と連携させるためのTriggerが充実してるなと思った。さすがイベントドリブンのために作られたプロダクト。
Cloud Run
対応しているトリガーは以下の通り。
- HTTP(S)
- WebSocket
- gRPC
- PubSub
PubSubをトリガーに使用できるのはメリットの一つだが、Cloud Functionsと比べると、トリガーの種類が少ないなと思う。
ただ、WebSocketやgRPCをトリガーに受け入れられるのはCloud Functionsにはない。websocketに関しては、GAEはセッションアフィニティもサポートしているので、さらに比較する必要がある。Cloud Runはセッションアフィニティをサポートしていないので、Websocketを使用する場合は、Redis PubSubなどを使用してコンテナ間でデータを共有させる必要がある。
※ GAE SEはWebsocketをサポートしていないので、HTTPのロングポーリングを使用する必要がある。GAE FEの方はWebsocketをサポートしている。
Cloud StorageのトリガーはCloud Runには存在していないが、Cloud Storageには変更をPubSubに通知する機能があるので、それを使用することでCloud Storage トリガーを実現できる。てか、PubSubを使えばある程度なんでもトリガーにできそうな気はしてる。
実行環境
Max Concurrent Request
Cloud Runには、MaxConcurrentRequestという機能があり、一つのコンテナで同時に実行できるリクエスト数を制御できる機能がある。
デフォルトで80 Requestsまで1個のコンテナで処理できるが、最大1000 Requestsまで増やすことができる。MaxConcurrentRequestをうまく設定することで動作するインスタンスの数を制御でき、コストを抑えることができるようになる。
Cloud Functionsでは、MaxConcurrentRequestは1に固定されているため、インスタンスがたくさん立ち上がり、コールドスタートが発生しやすいので、レイテンシが重視される処理には向かない。レイテンシが気にならない非同期処理やバッチ処理に向いている感じ。
Runtime
Cloud Functionsではランタイムが制限される。現在(2021.8.6時点)でサポートされているランタイムは以下の通り。
- Node.js
- Python
- Golang
- Java
- C# .NET
- Ruby
- PHP
これ以外のランタイムを使用したい場合は、Cloud Runを使用する。Cloud Runは、Dockerfile経由でランタイムをいじることができるので、柔軟にランタイムを制御したい場合は、Cloud Runを使用することになる。
Webhook Timeout
Cloud Functionsは、リクエストの最大タイムアウト時間が9分なのに対して、Cloud Runの場合は、最大15分まで延長できる。
通常のリクエストのタイムアウト時間は60分まで延長でき、Cloud Tasksから実行する場合は、Cloud Tasks側の仕様で30分でタイムアウトする。
※ 通常のリクエストは60分までいけるのになんでwebhookは15分なのか分からないので、わかる人がいればコメント欄で教えてください。。。(通常のリクエストもWebhookもほとんど同じじゃないの?と思った)
https://cloud.google.com/run/docs/triggering/webhooks?hl=ja#vs
インスタンス数の管理
Cloud Runでは、最小インスタンス(0まで)、最大インスタンスを指定できるのに対して、Cloud Functionsは指定できない。よって、リクエストが途切れると、次回のリクエスト開始時にコールドスタートが発生してしまう。Cloud Runでは、idleインスタンスを用意して置けるのでこのような心配はなくなる。
また、Cloud Runは、MaxConcurrentRequestを設定できるのでスケーリングする回数を減少させることができ、コールドスタートが発生する確率をさらに低くすることができる。Cloud Functionsは1で固定されるので、スケーリングが起きやすい環境だと言える。
Traffic Management
トラフィック分割
Cloud Runには、GAEと同じようにトラフィックを分割する機能がある。Revision1 に90%、Revision2に10%のような設定をし、カナリアテストを実現できる。Cloud Functionにはそのような機能はない。Cloud Runはイベントドリブンにも対応した、GAEのような位置付けのサービスになっている。
rollback
Cloud Runでは、rollbackも容易にできる。GAEと同じ。
認証
認証はほとんど同じ仕様になっているので、差はない。
Cloud Run
デフォルトでは非公開でデプロイされ、リクエストで認証情報を提供しないとサービスにはアクセスできない。
設定は以下の中から選択できる。
- 公開アクセスを許可:未承認のアクセスも可能になる
- デベロッパーからのアクセスを許可 [1]: Authorizationヘッダに認証情報をくっつけてリクエストを送る。
- サービスからのアクセスを許可 [2]:サービスアカウントを付与して、署名つきIDトークンをAuthorizationsヘッダにくっつけてリクエストを送る
- エンドユーザーを認証: FirebaseAuthenticationなどの認証サービスを使うか、
roles/run.invoker
IAMロールをユーザーに付与する
[1] https://cloud.google.com/run/docs/authenticating/developers?hl=ja
[2] https://cloud.google.com/run/docs/authenticating/service-to-service?hl=ja
Cloud Functions
関数のデプロイ時に、allow-unauthenticated
フラグをつけると、未認証アクセスが可能になる。つけないと認証されたアクセスしか受け付けなくなる。
設定は以下の通り。
- デベロッパーからのアクセスを許可:
cloudfunctions.functions.invoke
権限を持ったIAMロールを持ったサービスアカウントを使用して、Authorizationヘッダにトークンをつけて、リクエストを送信する。 - 関数からのアクセスを許可: 権限を持ったサービスアカウントのIDトークンを使用して、AuthorizationヘッダにIDトークンをくっつけて送信する。受信側関数はどのサービスアカウントを許可するかを設定することができる。
Inbound Traffic の制御
InboundのトラフィックをCloud RunもCloud Functionsも制御することができる。ちなみにGAEにもある。どちらにも存在する機能なので差別化には繋がらないが、個人的に隠れ優れ機能だと思ったのでかく。
https://cloud.google.com/run/docs/securing/ingress?hl=ja#settings
以下の中からInbound Trafficを制御することができる。
- どんなトラフィックでもOKよ
- LoadBalancer経由もしくはVPC内からじゃないとダメよ
- VPC内からじゃないとダメよ
まとめ
Cloud Functionsはデータベースの更新をトリガーにして動作させることができるし、Dockerfileを書く必要もない。
「軽量の簡単な関数をデプロイしたい」、「データベースの更新をトリガーに動作させたい」などの用件がある場合は、Cloud Functionsが推奨される。
Cloud Runは、Webサービスのホスティングもできる機能がたくさん用意されていて、イベントドリブンで動作させる機能はおまけって感じだった。だが、インスタンス数の制御や柔軟なランタイム制御、Timeoutの観点などから見ると、Cloud Functionsより優秀だと思った。
「Cloud Functionsではどうしても物足りない」や「コンテナを使いたい」ってときに、Cloud Runを検討するのがいいと思う。