自動デプロイCI/CD
ソースコードを更新するたびに、人手でサーバにログインしてアップロードしていては、手間も多くミスも起きます。松葉は、ソースコードを本流に取り込むと、変更のあったサービスだけが自動的に本番へ反映される仕組み(CD。継続的デリバリー)を整えました。しかもこの自動化は、鍵(パスワード相当のファイル)を 1 つも発行しない安全な方式(WIF)で行います。この経路で本番環境への初回デプロイまで疎通を確認しました。
取り込みから本番反映までの流れMerge to production
開発したコードを本流(main)に取り込むと、次の流れが自動で走ります。人が操作するのは「取り込む」ところまでで、その先は全自動です。
変更のあったサービスだけを反映: 4 つのサービスのうち、たとえば画面(web)だけを直したときは、画面だけが再反映され、他のサービスはそのままです。無駄なデプロイをせず、影響範囲を最小に保ちます。データベースの構造変更(マイグレーション)だけは、誤って本番データを壊さないよう自動化から外し、人手で慎重に適用する運用にしています。
鍵を発行しない、という安全策Keyless by design
自動デプロイの最大のリスクは、「クラウドを操作する権限(鍵)」が漏れることです。鍵ファイルを発行して保管すると、それが流出した瞬間に本番を乗っ取られます。松葉はそもそも鍵を発行しません。
WIF(Workload Identity Federation)の考え方: 鍵ファイルを渡す代わりに、GitHub 側が発行するその場限りの一時的な身分証を、Google Cloud 側が検証して権限を貸し出します。さらに「この特定のリポジトリからの実行に限る」という条件を付けているため、他のリポジトリが同じ仕組みを悪用して本番を操作することはできません。保管が必要な鍵ファイルが存在しないので、鍵の流出リスクを減らせます。
matsuba-api)を更新すると、同じコンテナ画像から API サービス本体と、定期処理のバッチ(matsuba-batch)の両方が一度に反映されます。同じコードを使う 2 つの実行先を、1 つのワークフローで揃えて更新できます。
| ワークフロー | 反映する先 |
|---|---|
cd-web | 画面サービス(matsuba-web) |
cd-api | API サービス(matsuba-api)+ 定期バッチ(matsuba-batch) |
cd-sync | Drive 同期サービス(matsuba-sync) |
cd-scanner | ウイルス検査サービス(matsuba-scanner) |
reusable-deploy | 上記 4 本が共通で呼び出す、認証・ビルド・反映の本体 |
秘密情報の置き場所Where secrets live
接続情報や API キーといった秘密情報は、コードに直接書かず、用途に応じて 2 か所に分けて安全に保管します。GitHub 側にはデプロイに必要な最小限を、アプリが使う本番の秘密はクラウドの金庫(Secret Manager)に置きます。
| GitHub に置く設定(4 種) | 用途 |
|---|---|
WIF_PROVIDER | 鍵なし認証の接続先 |
DEPLOY_SA | デプロイ時に借りる権限(サービスアカウント) |
GCP_PROJECT_ID | 反映先の Google Cloud プロジェクト |
GOOGLE_DRIVE_SHARED_DRIVE_ID | 同期先の共有ドライブの ID |
| クラウドの金庫に置く秘密(8 種) | 用途 |
|---|---|
matsuba-database-url | データベース接続情報 |
matsuba-better-auth-secret | ログインセッションの署名鍵 |
matsuba-google-client-id / -secret | Google ログインの認証情報(2 種) |
matsuba-resend-api-key | メール送信の API キー |
matsuba-resend-webhook-secret | メール配信状況の受信検証 |
matsuba-cors-origin | アクセスを許可する正規の接続元 |
matsuba-ops-bypass-token | メンテナンス時の運用用トークン |
正直な注記: 確認したのは、この自動デプロイの経路で本番に初めて送り出し、生存確認(/health)が応答するところまでです。送り出したのは中身が空の「起動雛形」で、業務機能はこれから載せます。なお初回の疎通確認は、公開用ドメイン(portal.example.com)ではなく、各サービスに割り当てられる確認用 URL(*.run.app)で行っています。