[Elixir] PhoenixアプリケーションをGoogle Cloudで本番運用する際のインフラ構成を考える

この記事は [Elixir Advent Calendar 2023](https://qiita.com/advent-calendar/2023/elixir) カレンダー1の11日目です。昨日は @koyo-miyamura さんでした！

https://qiita.com/koyo-miyamura/items/173d61e23c7a38d0826b

---

今年もElixirのアドベントカレンダーは活気がありますね :dancers: 

さて、どの言語・フレームワークでも作るための情報は多くありますが、実際に本番運用するとなるといろいろと考えないといけないことが出てきます。そこで、今回はPhoenixアプリケーションをGoogle Cloudで本番運用する際のインフラ構成について考えてみます。

なお、インフラの各構成要素には触れますが、具体的な構築方法は説明しないので悪しからず :pray: 

# 想定するアプリケーション

- LiveViewによるSPA
- RDBへのデータの保存
- ユーザによるファイルアップロード
- バッチ処理の定期実行

といった一般的なWebアプリケーションを想定します。注意点として、LiveViewを使うのでWebSocket接続できる環境が必要になります。

なお、今回はErlangクラスタは不要です。

# インフラ構成

より手軽で楽に運用したいので、Google Cloudのマネージドサービスを活用します。

| リソース | 用途 |
| --- | --- |
| Cloud Run | Phoenixアプリケーションを稼働させる |
| Cloud SQL | RDBにデータを保存する |
| Cloud Storage	| アップロードされたファイルを保存する |
| Cloud Scheduler | 定期的にバッチ処理を起動する |
| Secret Manager | DBのパスワードなど、秘匿情報を管理する |

Cloud RunからCloud SQLのインスタンスに接続するには、[Cloud SQL Auth Proxy](https://cloud.google.com/sql/docs/postgres/connect-instance-cloud-run?hl=ja) を使います。また、Cloud RunからCloud Storageを操作するには、Cloud Storageを操作する権限を持つサービスアカウントを作成し、Cloud Runに紐づけるようにすると安全です。

なお、Cloud Runは [サービス](https://cloud.google.com/run/docs/overview/what-is-cloud-run?hl=ja#services) と [ジョブ](https://cloud.google.com/run/docs/overview/what-is-cloud-run?hl=ja#jobs) の両方を使いますが、それぞれ用途が異なります。

:::note warn
Cloud Runでは**Erlangクラスタを形成することができません**[^erlang-cluster]。

PubSubだけなら [phoenix_pubsub_redis](https://hex.pm/packages/phoenix_pubsub_redis) などを使うことで回避できますが、Erlangクラスタが前提となる仕組みが必要な場合はKubernetesなど別の環境を検討してください。別の記事で [Kubernetes上でErlangクラスタを形成する方法](https://qiita.com/mokichi/items/c3157804faa295ce1574) を解説しています。
:::

[^erlang-cluster]:Cloud Runではインスタンスの情報が隠蔽されているため、同じCloud Runサービスに属する別インスタンスのErlangノードを探すことができないというのが理由です。もし間違っていたり方法があったら教えてください :bow: 

## Cloud Run サービス

用途としては、HTTPリクエストやWebSocket接続を受け付けるWebサーバ兼アプリケーションサーバです。

Cloud Run サービスは標準でWebSocketをサポートしていますが、デフォルトでは5分でタイムアウトして再接続が発生する（再接続はLiveViewがよしなにやってくれます :smirk: ）ので、必要に応じてタイムアウト値を設定しましょう。

## Cloud Run ジョブ

用途としては、以下の2つです :point_down:

- デプロイ時のDBマイグレーション
- 定期実行するバッチ処理

Cloud Run ジョブにはスケジューリング機能はありませんが、[Cloud Schedulerと組み合わせる](https://cloud.google.com/run/docs/execute/jobs-on-schedule?hl=ja) ことでバッチ処理を定期実行できます。

# 継続的デリバリー

長期的な本番運用を考えると、継続的デリバリーは欠かせません。今回はCloud Buildによるデプロイを考えます。

1. Dockerイメージをビルドする
1. DBマイグレーションする
1. Cloud Run サービスを更新する
1. Cloud Run ジョブを更新する（サービスの更新と同時に実行してOK）

この一連の流れを [cloudbuild.yaml](https://cloud.google.com/build/docs/configuring-builds/create-basic-configuration?hl=ja) に記述します。そして、Cloud Buildを実行するタイミングをどうするかですが、

- GitHub Actionsで `gcloud builds submit` コマンドを実行する
- Cloud Buildにリポジトリを接続してトリガーを作成する

のどちらかになると思います。個人的に、アプリケーションのデプロイはGitHub起点で制御したいので、前者の方法が好みです。

# モニタリング

PhoenixアプリケーションはErlang VM上で動くこともあり、長期間安定して稼働します（実際、私はPhoenixアプリケーションがそれ自身が原因で落ちるところを見たことがありません :open_mouth: ）。が、アプリケーションやDBに大きな負荷がかかったり、それによって応答が悪くなるケースはもちろん考えられるので、[Cloud Monitoring](https://cloud.google.com/monitoring/docs/monitoring-overview?hl=ja) でモニタリングするのが良いでしょう。設定した閾値を超えたらSlackに通知する、なんてこともできます。

また、APM（Application Performance Monitoring）として [Cloud Trace](https://cloud.google.com/trace/docs/overview?hl=ja) があります。私はElixirではやったことがないのですが、[OpenTelemetryのライブラリ](https://hex.pm/packages?search=opentelemetry&sort=downloads) が充実しているので、Cloud Traceへのエクスポータさえ用意できれば詳しい情報をトレースできるようになる...はずです。

# 最後に

クラウドインフラというとAWSの事例が多いですが、Google Cloudも充実してますね。AWSにはCloud Runに相当するApp Runnerがありますが、Cloud Run ジョブに相当するものはおそらくない（AWS Batchは使ったことありませんがちょっと違うような？ :thinking: ）ので、手軽で楽に運用したいならGoogle Cloudの採用もアリです。ただ、Google CloudにはAWSのSESのようなメール配信サービスがないので、SendGridなど別のサービスを検討する必要があり要注意です。

今回はPhoenixアプリケーションを対象に考えましたが、コンテナ化できるなら他の言語・フレームワークでも同じインフラ構成を採用できます。運用を楽にできるだけでなくコストメリットもあるので、テンプレ化して広く使っていきたいですね :sparkles: 

---

明日の [Elixir Advent Calendar 2023](https://qiita.com/advent-calendar/2023/elixir) カレンダー1の12日目は @nako_sleep_9h さんです！
お楽しみに :rocket: