HRBrain Advent Calendar 2023 の24日目の記事です。
はじめに
こんにちはー
プラットフォームチームの東島です。
これまでHRBrainのアプリケーション実行環境はGoogle Kubernetes Engine(GKE)を用いて構築してきました。しかし、2023年9月に運用負荷削減、プロダクトエンジニアがオーナーシップを持ってインフラ管理できることを目的にGKEからGoogle Cloud Runへ実行環境を移行しました。この変更に伴い、GKEで構築していた「ブランチデプロイ」と呼ばれるプルリクエストプレビュー(以後ブランチデプロイ)環境もCloud Runへ移行しました。 times.hrbrain.co.jp
この記事では、どのようにCloud Runでブランチデプロイ環境の構築を実現したのかを紹介します!
HRBrainの構成について
まず、HRBrainの構成について簡単に説明します!
HRBrainの構成
HRBrainはモノレポを採用しており、1つのリポジトリに全てのサービスのコードが存在しています。HRBrainではCloud RunをYAMLで管理しているので、そのYAMLも同じリポジトリに存在しています。
全てのサービスはCloud Run上で動いており、ロードバランサーからトラフィックを受け付けるCloud Runサービス以外はサービスごとのプロジェクトに配置しています。
HRBrainのデプロイの流れ
ビルドからデプロイまでの流れを説明します。
イメージビルドは、main ブランチへのマージがトリガーとなります。Cloud Build トリガーでディレクトリ単位での更新差分を検出し、開発(dev)、ステージング(stg)および本番(prd)環境向けのイメージが作成されます。開発とステージング環境では、イメージの作成が完了次第、自動的にデプロイが実行されます。一方、本番環境においては、GitHub Actionsのworkflow_dispatchを用いてリリースワークフローを手動で実行します。
ブランチデプロイ環境はmainにマージする前の検証用にプルリクエストから環境を作成します。
Cloud Runでブランチデプロイ構築
本題である、ブランチデプロイの構築をどのように実現したのかを説明します!
ブランチデプロイ環境は既存のdev環境上でCloud Runにリビジョンにタグを付与することで構築します。 このCloud Runの機能を利用することでトラフィックのないリビジョンに対してもリクエストを送ることができます。
# AAAというサービスの通常のURL https://AAA-abcdefghij-an.a.run.app # pr-12345というタグを付与したリビジョンのURL https://pr-12345---AAA-abcdefghij-an.a.run.app
参考: デプロイ プレビューの構成 | Cloud Run のドキュメント | Google Cloud
ブランチデプロイ環境作成の流れ
ブランチデプロイ環境はビルド、デプロイの実行を含め5分前後で作成できます。
ブランチデプロイ環境の作成手順は以下になります。
- プルリクエストで
/deploy-run
とコメント- コメントをトリガーにGitHub Actionsが実行
- イメージビルド
- プルリクエストとベースブランチの間に差分のあるサービスを検出
- Skaffoldを実行し、Cloud Buildで差分のあるサービスを並列にビルド
- デプロイ
- 環境変数を定義する
- 全てのフロントアプリにタグを付与してデプロイ
- APIゲートウェイにタグを付与してデプロイ
- 差分のあるバックエンドサービスにタグを付与してデプロイ
mainにマージするときはgcloud run service replace
コマンドを使用しているのですが、ブランチデプロイ環境を作成するときにはタグを付与してデプロイするためにgcloud run deploy
を使用しています。
参考までにブランチデプロイ環境でデプロイするときは、下記のコマンドを使用しています。 環境変数とシークレットはプルリクエスト中YAMLのenvセクションから取得しています。こうすることでプルリクエスト中で環境変数の値を更新できます。
# ブランチデプロイ環境作成に使用しているコマンド gcloud run deploy AAA-app --project=<プロジェクトID> --region=asia-northeast1 --tag=pr-12345 --image=asia-northeast1-docker.pkg.dev/projectID/repoName/AAA-app:pr-12345 --env-vars-file=app-env.yaml --update-secrets=<シークレット> --async --no-traffic
環境変数
Cloud Runサービスが各サービスを参照するためのURLを環境変数で管理しています。デプロイ時には、変更があったサービスの環境変数に対し、その値の先頭にpr-<PR番号>---
を追加することで、これらの環境変数を更新しています。この方法により、変更のあるサービスはブランチデプロイ環境に、変更のないサービスはdev環境にルーティングされます。このルーティングのメリットは変更がないサービスを参照するときに常に最新の状態を参照できることです。
リバースプロキシ
HRBrainの開発環境のURLは<テナントID>.<サービスID>.example.com
のような形式にしています。
一方、ブランチデプロイ環境のURLはCloud RunのURLと同じ形式を採用しpr-<PR番号>---
を付与することでアクセスできるようにしてます。つまり、pr-<PR番号>---<テナントID>.<サービスID>.example.com
という形式になります。
この時、https://<テナントID>.<サービスID>.example.com
はhttps://<サービスID>-abcdefghij-an.a.run.app
に、https://pr-<PR番号>---<テナントID>.<サービスID>.example.com
はhttps://pr-<PR番号>---<サービスID>-abcdefghij-an.a.run.app
にルーティングする必要があります。
それを実現するために、開発環境にはロードバランサーとCloud Runの間にLuaで書いたリバースプロキシのCloud Runを作成しています。 未知のプログラミング言語にChatGPTと共に挑む - Speaker Deck
つまづいたポイント
gcloud run service replaceコマンド
HRBrainではCloud RunをYAMLで管理しています。そのため、プルリクエストをmainブランチにマージした時にgcloud run service replace
が実行されデプロイされます。しかし、このコマンドには問題があります。それは、リビジョンのタグが消えてしまうことです。replaceコマンドなので、YAMLに定義した内容でデプロイされるのはそれはそうという感じです。
この問題を解決するために、dev環境のデプロイではでリビジョンのtrafficをサービスから取得、調整した後にmainブランチのYAMLとマージしてリビジョンのtrafficを含めたYAMLをgcloud run service replace
コマンドでデプロイしています。
# trafficを取得するコマンド gcloud run services describe $SERVICE_NAME --format="yaml(spec.traffic)" --project=$PROJECT_ID
GKEのブランチデプロイとの違い
DBはdev環境と共通のものを使用する
GKE時代のブランチデプロイでは、DBもブランチデプロイ環境のサービスごとにGKE内で作成していました。Cloud Runで同じことをするためにCloud SQL インスタンスを作成するにはコスト的に現実的ではないので、断念しdev環境と全ブランチデプロイ環境で共通のDBを参照するようにしています。
プルリクエストからDBのスキーママイグレーションをするためのワークフローも作成しており、プルリクエストからスキーマ変更を伴う開発をできるようにしています。
dev環境のテナントを使用できる
dev環境のDBを利用することで、データをそのまま使用できるため特定の動作確認のために追加でデータを挿入する手間が省けるようになりました。 これにより、資料作成をするCSや動作確認をするQAの負担が少し減りました。
コスト
マイクロサービスの増加により、ブランチデプロイ環境のコストがとても高くなっていました。これをCloud Run化することで、100万/月ほどのコスト削減をすることができました。
おわりに
リビジョンタグというCloud Runの機能を利用することで、プレビュー環境を作成することができました。
最後にプラットフォームチームのラパンさんとVicさん、ブランチデプロイ環境の初期設計を手伝っていただいたHさんとYさん、各サービスのCloud Run移行を手伝っていただいたHRBrainのメンバーに感謝します。
HRBrainでは、一緒に働く仲間を募集しています。プラットフォームチームに興味がある方は、ぜひ私たちと一緒に働きませんか?ご応募をお待ちしております! www.hrbrain.co.jp