Goodbye Bastion ── 踏み台を使わずに Terraform で AlloyDB を管理する

Goodbye Bastion ── 踏み台を使わずに Terraform で AlloyDB を管理する

こんにちは。RightTouchの基盤チームでエンジニアをしている塩澤 (@hshiozawa) です。

RightTouchは主要なデータベースとしてGoogle CloudのAlloyDBを採用しています。AlloyDBはPostgreSQL互換のフルマネージドデータベースで、コンピュート層とストレージ層を分離したクラウドネイティブなアーキテクチャが特徴です。

AlloyDBは素晴らしいデータベースですが、1つ厄介な問題があります。VPC上で構築するため基本的にはPublic IPを持ちません。我々は個人情報を扱うサービスの特性上、Public IPを持たせることもできません。

よくとられる手段はVPC内に踏み台サーバー(Bastion)を立てる方法ですが、この方法にはいくつかの問題があります。本記事では、Cloud Run Worker PoolをGitHubのSelf-hosted runnerとして利用し、踏み台VMなしでAlloyDBをTerraformで管理する方法を紹介します。

課題

AlloyDBのTerraform管理は、providerのレイヤで切り分ける必要があります。クラスタ・インスタンス・IAMユーザーはGoogleのproviderで管理できますが、databaseの作成・GRANT・DB Roleは cyrilgdn/postgresql のproviderが必要です。このproviderはAlloyDBにTCPで直接届く場所からしか動きません。

AlloyDB 管理レイヤの図。ORM ライブラリ・terraform postgresql provider・terraform google provider がそれぞれテーブル・スキーマ管理、権限管理・データベース管理、クラスター・ユーザー管理を担う

AlloyDBはPublic IPを持たないため、到達経路はPrivate Service Connect (PSC) かVPC内アクセスに限られます。つまりTerraformの実行環境自体をVPC内に置く必要があります。ただ、踏み台VM方式では運用面で次の課題があります。

  • メンテナンスコスト:使わないときはVMを落とすことで金銭的コストは減らせる。しかし、OSパッチ適用やイメージ更新、SSH管理といった継続的な運用コストは残る。
  • セキュリティ:VMを落とすことで侵入リスクは減らせる。ただ、永続ディスクには状態が残る。作業で生じた認証情報や一時ファイルの消し忘れがリスクになる。
  • CI との相性が悪い:手元SSHを前提とした構成はCIのJob Stepとして表現しづらく、applyの実行者や日時をPRから追えない。

解決

これらの課題を解決するために、下記のような仕組みがほしいです。

  • VPCで動作する環境
  • ジョブの時だけ立ち上がる(Ephemeral)
  • GitHub Actionsと相性がいい

採用したのは、Cloud Run Worker Pool上にGitHub Self-hosted runnerを載せる構成です。Cloud Run Worker PoolはHTTPリクエスト駆動ではなく、長時間プロセスを回すためのCloud Runの系統です。VPC上で動作してワーカー数を0まで縮められるのが特徴です。Google CloudにもCloud Run Worker PoolをSelf-hosted runnerとして利用するチュートリアルがあるため参考にしました。

他の選択肢も比較しました。

  • Compute Engine:踏み台VMと同じ運用負荷が残る。
  • Google Kubernetes Engine:runnerのためだけにK8sクラスタを抱えるのは固定費とメンテが重い。
  • Cloud Build Private Pool:VPC内でTerraformを回す手段としては成立する。ただし、GitHub Actionsと統合しづらい。
  • GitHub-hosted runner + IAP トンネル:トンネルの終端として踏み台が中間に挟まる構図へ戻る。

Cloud Run Worker Poolは0 instanceまで縮められる・VPCで動作(Direct VPC egress)・コンテナイメージで運用できるの3条件を同時に満たします。これが他の選択肢にない決め手でした。固定費はほぼコンテナイメージの保管料のみ、計算リソースはジョブが来た瞬間だけ掴む構造です。

アーキテクチャ

全体像はつぎのとおりです。

全体アーキテクチャ図。GitHub(Workflow・Pull Request)から Google Cloud の github-ops-vpc 内の Cloud Run Worker Pools と Self-hosted Runner に接続し、PSC endpoint 経由で app-vpc 内の AlloyDB に Private Service Connect でアクセスする構成

図の左はGitHubを表しています。右のGoogle Cloud側には2つのVPCが存在します。app-vpcでは我々のアプリケーションが動作しており、PSC endpointを介してAlloyDBへアクセスしています。もう一方のgithub-ops-vpcが本構成の主役で、runner専用に独立させたVPCです。

runnerをapp-vpcへ直結しない理由は、runnerからinternal APIなどAlloyDB以外のリソースへ到達できないよう、権限境界をopsで必要となる領域に絞るためです。github-ops-vpc内にはCloud Run Worker Poolと、そこで動くSelf-hosted runnerが存在し、PSC endpoint経由でAlloyDBに接続します。

次の4ステップで動作します。

  • ① コンテナイメージのデプロイ(事前準備):開発者はSelf-hosted runnerが動作するイメージをビルドし、Cloud Run Worker Poolにデプロイする。これはランタイム時の起動ではなく、runnerの実行環境をあらかじめ定義しておくステップである。
  • ② start-runner:Pull Requestやworkflow_dispatchをトリガーに、GitHub Workflowがワーカー数を変更するコマンドを実行し、Cloud Run Worker Poolを0 → 1インスタンスに起動する。起動したコンテナが自身をephemeral self-hosted runnerとしてGitHubに登録するまで、次のジョブは待機する。
  • ③ tf-run:GitHub Workflowが runs-on: { group: ... } でSelf-hosted runnerを掴み、terraform plan / apply を実行する。runnerはgithub-ops-vpc内に存在するため、PSC endpoint経由でAlloyDBに直接届く。(より詳細にはAlloyDB Auth Proxyを経由する)
  • ④ stop-runner:ワーカー数を0にするコマンドを実行してWorker Poolを停止する。runnerは自動的にGitHubから登録解除される。

データベースへのTerraform実行は頻度が低いため、本構成では次の2点について意図的に目を瞑っています。

  • 直列実行:並列実行は想定せずconcurrency groupでワークフローを直列化して実行させている。
  • コールドスタートによる apply 時間の増加:ジョブのたびにWorker Poolがゼロから起動するため、常時稼働している構成と比べるとapply完了までの時間は長くなる。

Tips: runner を ephemeral にする

runnerコンテナは ghcr.io/actions/actions-runner をベースにしています。起動時のエントリーポイント(start.sh)では、Secret Managerに保存されたGitHub Appの秘密鍵からregistration tokenを取得し、--ephemeral フラグを付けてrunnerを登録します。

--ephemeral を使う理由は、1ジョブを処理したらrunnerが自動的に登録解除される点にあります。ジョブをまたいで認証情報・環境変数・ファイルシステムの残骸が次のジョブに漏れる心配がなくなります。

また、Worker Poolの再起動時、前回のrunnerがGitHub側へ残留しているケースに備えて、start.sh の冒頭で同名のrunnerをGitHub API経由で削除するフォールバック処理も入れています。

成果

この構成で、AlloyDBのTerraform管理を踏み台VMゼロで運用しています。

Worker Poolは通常0 instanceのため、固定費がほぼ発生しません。GitHub Workflowは runs-on.group を1行切り替えるだけでSelf-hosted runnerに乗り換えられるため、GitHub-hosted runnerと同じ書き味をほぼ維持できています。また、ephemeral化によりジョブをまたいだstateの漏洩がなく、Terraformの扱う認証情報や環境変数も別ジョブへ流れ込みません。

そもそも、このようにTerraformでの管理が可能になったことで、誰がいつデータベースに変更を加えたかGitHubのWorkflow履歴とPRから簡単に追えるようになりました。

おわりに

この構成には、本記事でまだ書いていない重要な点があります。

この構成ではSelf-hosted runnerを使いGitHub Workflowを実行する権限があれば、誰でもVPC内のAlloyDBに接続できてしまいます。これを解決するため、実際の構成ではGoogle CloudのWorkload Identity FederationとPrivileged Access Management(PAM)を組み合わせています。必要な場合に特権昇格申請をして管理者の承認を得ることで、plan/apply実行の権限が得られる仕組みです。

この仕組みは、また別の機会に記事化する予定です。

採用情報

RightTouchでは、Product Engineerをはじめ、プロダクト価値を一緒に育てる仲間を積極採用中です。カジュアル面談も歓迎しています。ご興味があれば、ぜひ採用ページをご覧ください。

righttouch.co.jp

righttouch.co.jp

speakerdeck.com