以下の内容はhttps://shinyorke.hatenablog.com/entry/cloud-run-sidecar-patternより取得しました。


Cloud Runで手っ取り早く認証入れるならOAuth2 Proxyがいいぞ - AI AgentのPoCにサクッと認証を入れる技術

本業は生成AIとAzureをよく使うSRE, 個人的にはGoogle Cloudで野球AI Agent開発をしている者です.

昨年末〜今年の年初にかけて, Pythonのmarimoという「リアクティブノートブック的なFWで野球AI AgentのPoCやってます」というブログを書きました.

shinyorke.hatenablog.com

shinyorke.hatenablog.com

こちらのアプリ, 生成AIのお陰で開発がガンガン進み.

そろそろ外で登壇したり外部公開するのにクラウド使うかー, でも認証どうしよう?

というフェーズになりました(これは進捗している証拠なので良いこと).

  • すでに運用しているデータ基盤との兼ね合い(と自分の好み)でGoogle Cloud & Cloud Runを使い
  • Basic認証ではなく, OAuth2など真っ当な方法で認証をかけ
  • かつ, TerraformでIaC管理しつつCI/CDも入れる

という事をClaude Code先生の協力の元, 2日ぐらいでやったのでその学びを公開します.

ちなみにサムネイルになってる絵はこのアプリの構成を示したものです(Nano Banana Pro作).

今回紹介する構成

本ブログのコンテンツは以下のとおりです.

TL;DR

Cloud Runで手っ取り早く認証入れるならOAuth2 Proxyがいいぞ(とClaude Codeが教えてくれました&実際良かった).

免責事項

本ブログは実験的な試みとして「Claude Cowork」で執筆したものを私shinyorkeが加筆修正した上で執筆しています.

詳細な執筆方法は私のnoteで近日中に公開できるように頑張ります.

なお, 本件の設計と実装は私がClaude Codeと壁打ちしdebugしながらやったものとなります.

背景

前述の通り, Pythonのリアクティブノートブック「marimo」で実装したMLBの対戦傾向分析アプリをCloud Runにホスティングすることにしました.

アプリケーションはVertex AI(Gemini)を使って選手間の対戦データを分析するもの(前回ブログを参照)で, 個人的な野球観戦のお供として開発しています.

そろそろ外部登壇で使ったり外部公開も視野に入れ(かつ, 自分もスマホから使いたい)等々の理由で公開できるものが欲しくなりこの営みに手をつけました.

唯一にして最大の要件は「認証なしで公開するわけにはいかない」こと.

誰でも使えるようにするフェーズではまだないのでというのに加えて.

  • PoC的なアレで限られた人に公開
  • 「誰に公開するか」をハンドリングしたい

という要件の元, 「認証付きのCloud Runをどうにかするぞ!」という開発に着手しました.

認証方式の比較と選定

というわけで選択肢を考えました. 選択肢は5つあり, 採用したOAuth2 ProxyはClaude Codeが出したアイデア, ほかは私が最初から持っていたアイデアとなります.

まともに検討したのは以下3つです.

  • Cloud Runで直接Identity-Aware Proxy(IAP)を使う
  • 王道パターンである「LB + IAP」構成
  • OAuth2 ProxyをCloud Runのsidecarでホストし認証する

アイデアとしてあったが最初から見送ったのは以下となります.

  • marimoアプリにBasic認証をかける. 「誰に公開するか」をハンドリングしたい要件を満たせないため.
  • marimoアプリ内にOAuthなりなんなりの認証をかける. 複雑になるだけでいいことが無い.

選択肢1: Cloud Run直接IAP

Cloud Runには2024年からIdentity-Aware Proxy(IAP)を直接設定できる機能がプレビューで追加されています.

docs.cloud.google.com

Load Balancerなしで認証をかけられる素晴らしい機能...なのですが, Organization(組織)が必須なので見送りました.

組織をつけてもいいのですが, AIの力を借りても1日仕事じゃ済まないっぽい, そして何より個人アカウントでそこまでやるか...という気持ちになりこのアイデアは捨てました.

選択肢2: Load Balancer + IAP

GA(正式版)として安定している構成かつ, Google Cloud好きマン的には親の顔と同じくらいに見ている鉄板構成.

  • External Application Load Balancer(ELB)にIAPを設定
  • Internalに閉じ込めたCloud Runをバックエンドとして接続

というわかりやすい構成です.

じゃあこれを採用したらいいじゃん!...となる所ですが.

AIは「コストがちょっと...」という事で不採用をリコメンドしました, 理由は以下のとおりです(AIの作文・ADRをそのまま転機).

  • Forwarding Ruleの基本料金(固定費): 約$18/月〜
  • 処理データ量に応じた従量課金

24/365で使う訳では無い個人開発プロジェクト(しかもCloud Runはコールドスタンバイ)でLBのために毎月お金を払うかと言われると払わないなと...

加えてAIが言及していない部分での背景を上げると.

  • アプリを公開するときには検討していいが今の時点でELBは大げさすぎる.
  • PoCが終わった後, 「フロントエンドアプリとBFF」みたいな構成にしたいので登場人物を増やしたくない.

というのもあったりします.

選択肢3: OAuth2 Proxy(採用)

Claude Codeが提案, 検討し採用となったアイデアです. 最終的にはこれを採用しました.

Claude Code先生「OAuth2 ProxyというOSS認証プロキシでいい感じにしちゃえ!」

IdPはGoogleで考えてたのでわたりに船なアイデアでした, メリデメは以下のとおりです.

メリット:

  • ELB不要
  • Organizationもいらない
  • OSSなので無料

デメリット(というよりやる前のお気持ち):

  • 個人的にCloud Runのsicecarは知ってたけど未知の領域(初めて触る).
  • ややこしい構成になりそうだができるのか...!?

実際Claude Codeも「Cloud Run IAPでやれるに越したことはない」ぐらいに推してきましたが, 未知領域もややこしい構成もAIに丸投げでどうにかなりそう(小声)という感覚があり, 採用しました.

選択肢のまとめはこんな感じです.

方式 ELB必要 Org必要 月額コスト
Cloud Run直接IAP Cloud Runだけ
LB + IAP Cloud Run + ELBでプラス$18以上
OAuth2 Proxy Cloud Runだけ

アーキテクチャ設計

というわけで実際やってみました.やるときにはこの順序でやりました(ちなみにアプリのDocker化&GARへのpushは既に実現済み).

  • Cloud Run + 必要なNWとIAMのterraformを書く
  • OAuth2クライアントをGoogle Cloud側で用意
  • ひたすらterraform apply打ってTry and Error

このブログでは主に詳細な設計についてフォーカスを絞って紹介します.

サイドカーパターンの採用

Cloud Runは2023年からマルチコンテナデプロイ(サイドカーパターン)をサポートしています.

1つのCloud Runサービス内に複数のコンテナを配置し, localhostで通信できます.

今回の構成では,

  • OAuth2 Proxyコンテナをメインコンテナ(ポート8080)として外部からのリクエストを受ける
  • 認証後にmarimoアプリコンテナ(ポート2718)へプロキシ

という流れで組んでいます.

┌─────────────────────────────────────────────────────────────┐
│                   Cloud Run Service                          │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  OAuth2 Proxy Container (Port 8080) ← 外部公開       │    │
│  │  - Google OAuth認証                                  │    │
│  │  - 認証後、match-appコンテナへプロキシ               │    │
│  └─────────────────────────────────────────────────────┘    │
│                              │                               │
│                              ▼ localhost:2718                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Match App Container (Port 2718) ← 内部のみ          │    │
│  │  - Marimo App                                        │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

なぜサイドカーパターンか

理由は自明な気もしますが一応触れます.

別々のCloud Runサービスとして立てる方法も考えられますが, サイドカーパターンには以下のメリットがあります.

  1. ネットワーク分離: marimoアプリは外部に公開されず、OAuth2 Proxy経由でのみアクセス可能
  2. コスト効率: 1サービスなのでインスタンス数が抑えられる
  3. デプロイの原子性: 認証プロキシとアプリが常に同じバージョンで動作

サイドカーの利点そのものですね.

VPC Connectorの設計

ちょっと話が脱線しますがmarimoアプリ内でVertex AI(を介してGemini)を使っているので経路もちょっと工夫しました.

Cloud RunからVertex AIへのアクセスにはServerless VPC Accessを使用することで, プライベート接続を確立しました.

vpc_access {
  connector = google_vpc_access_connector.connector.id
  egress    = "PRIVATE_RANGES_ONLY"
}

PRIVATE_RANGES_ONLYを指定することで, 内部IPへのトラフィックのみVPC Connector経由とし, 外部API(MLB Stats APIなど)へのアクセスは通常のインターネット経由にしています.

Terraformによる実装

Terraformの実装を雰囲気だけお伝えします.

Cloud Runサービスの定義

Terraformでの実装ポイントを解説します. Cloud Runサービスの定義はこんな感じ.

resource "google_cloud_run_v2_service" "match_app" {
  name     = "match-app"
  location = var.region

  template {
    service_account = google_service_account.cloud_run.email

    # OAuth2 Proxyコンテナ(メインコンテナ、ポート8080)
    containers {
      name  = "oauth2-proxy"
      image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.repository_name}/oauth2-proxy:v7.6.0-amd64"

      ports {
        container_port = 8080  # これが外部に公開されるポート
      }

      env {
        name  = "OAUTH2_PROXY_UPSTREAMS"
        value = "http://localhost:2718"  # サイドカーへのプロキシ先
      }
      # ... その他の設定
    }

    # Match Appコンテナ(サイドカー、ポート2718)
    containers {
      name  = "match-app"
      image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.repository_name}/match-app:latest"
      # portsを指定しない = 外部非公開
    }
  }

  ingress = "INGRESS_TRAFFIC_ALL"
}

ポイント: - portsを指定するのはメインコンテナ(OAuth2 Proxy)のみ - サイドカーコンテナはポートを公開しない(localhostでのみ到達可能) - OAUTH2_PROXY_UPSTREAMSでサイドカーへのプロキシ先を指定

OAuth2 Proxyの設定

OAuth2 Proxyの設定は環境変数で行いました.

重要な設定項目を抜粋した結果がこれです.

env {
  name  = "OAUTH2_PROXY_PROVIDER"
  value = "google"
}
env {
  name  = "OAUTH2_PROXY_CLIENT_ID"
  value = var.oauth2_client_id
}
env {
  name  = "OAUTH2_PROXY_CLIENT_SECRET"
  value = var.oauth2_client_secret
}
env {
  name  = "OAUTH2_PROXY_COOKIE_SECRET"
  value = var.oauth2_cookie_secret  # 32バイトのランダム文字列
}
env {
  name  = "OAUTH2_PROXY_REDIRECT_URL"
  value = "https://${var.custom_domain}/oauth2/callback"
}
env {
  name  = "OAUTH2_PROXY_SKIP_PROVIDER_BUTTON"
  value = "true"  # Google直接リダイレクト
}

OAUTH2_PROXY_SKIP_PROVIDER_BUTTONtrueにすると, プロバイダー選択画面をスキップしてGoogle認証画面に直接遷移します.

Google単一のIdPを使う場合は設定推奨です.

未認証アクセスの許可(重要)

OAuth2 Proxyで認証を行うため、Cloud Run自体は未認証アクセスを許可する必要があります.

resource "google_cloud_run_v2_service_iam_member" "allow_unauthenticated" {
  project  = var.project_id
  location = var.region
  name     = google_cloud_run_v2_service.match_app.name
  role     = "roles/run.invoker"
  member   = "allUsers"
}

OAuth2 Proxyがメインコンテナとして認証を担当するため, 認証されていないリクエストはアプリに到達しません.

未認証で開くとこうなる.

色々苦労しましたがこれでやりたいことができました, めでたしめでたし.

ハマりポイント

実装中に遭遇した問題と解決方法を共有します.

1. Cloud Runはquay.ioをサポートしていない

OAuth2 Proxyの公式イメージはquay.io/oauth2-proxy/oauth2-proxyで配布されていますが一発で持ってこれませんでした.

# これはエラーになる
image = "quay.io/oauth2-proxy/oauth2-proxy:v7.6.0"

Terraform applyすると以下のエラーが発生します.

Error: Error waiting for Creating CloudRunV2Service:
Expected an image path like [host/]repo-path[:tag]

【参考】Cloud Runがサポートしているコンテナレジストリ:

  • Google Artifact Registry (GAR, 今回使ったもの)
  • Container Registry (gcr.io) ※もう廃止されています
  • Docker Hub

上記の通りquay.ioはサポートされていません.

解決策: OAuth2 ProxyイメージをGARにコピーする事で解決.

docker pull quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
docker tag quay.io/oauth2-proxy/oauth2-proxy:v7.6.0 \
  asia-northeast1-docker.pkg.dev/${PROJECT_ID}/betts-poc/oauth2-proxy:v7.6.0
docker push asia-northeast1-docker.pkg.dev/${PROJECT_ID}/betts-poc/oauth2-proxy:v7.6.0

2. メアドによる認証失敗 -> Google Authで解決

特定のメールアドレスのみアクセスを許可したいと思い, 以下の環境変数を設定しました.

env {
  name  = "OAUTH2_PROXY_AUTHENTICATED_EMAILS"
  value = "myemail@gmail.com"
}

しかし、この環境変数は存在しません.

OAuth2 Proxyのドキュメントにも記載がなく, 設定しても無視されます.

解決策: Google OAuth側でアクセス制限を行う.

  1. GCP Console → APIs & Services → OAuth consent screen
  2. User Type: Externalを選択している場合は「Test users」でユーザーを制限
  3. または、OAuth consent screenを「Internal」に設定(Organization必要)

個人プロジェクトの場合, Test usersに自分のメールアドレスを追加することで指定したユーザーのみアクセス可能になります.

コストと運用

あくまで概算・見積もりですが想定はこんな感じ.

リソース 月額(概算)
Cloud Run $0-15(使用量依存)
VPC Connector $7-10
カスタムドメイン 無料
合計 約$7-25/月

Cloud Runは最小0インスタンス設定(所謂コールドスタンバイ)なので, 使わなければほぼコストがかかりません.

VPC Connectorの固定費が主な支出です, これは運用してから外すかどうか検討でもいいかな...

ELBが無いだけでも良しとしましょう.

CI/CD

GitHub ActionsでTerraform applyを実行しています.

Workload Identity Federationを使ってサービスアカウントキーなしで認証し, OAuth2関連のシークレットはGitHub Actions Secretsで管理しています.

env:
  TF_VAR_oauth2_client_id: ${{ secrets.OAUTH2_CLIENT_ID }}
  TF_VAR_oauth2_client_secret: ${{ secrets.OAUTH2_CLIENT_SECRET }}
  TF_VAR_oauth2_cookie_secret: ${{ secrets.OAUTH2_COOKIE_SECRET }}

結び

「個人プロジェクトのCloud Runに認証をつける方法として, OAuth2 Proxyをサイドカーパターンで実装しました」的な紹介をしました.

学んだこと: - Cloud Run直接IAPはOrganization必須. - ELBのコストを避けたいならOAuth2 Proxyが有力な選択肢 - Cloud Runのサイドカーパターンは認証プロキシとの相性が良い

この設計パターンはGoogle Cloudじゃなくても「sidecarが実現できるContainer系サービス」で行けると思います.

試してはいませんが, Azure Container Appsでもほぼ同様の構成で行けるのではと.

learn.microsoft.com

marimoに限らず, streamlitやdashなどのローコード系アプリFWにシュッと認証を付けるのにこの方法は良さげです.

AI AgentのUIを限定公開などしたい方はぜひやると良いのではないでしょうか.

最後までお読みいただきありがとうございました.

参考リンク




以上の内容はhttps://shinyorke.hatenablog.com/entry/cloud-run-sidecar-patternより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14