GCP
indx が GCP でどう動くか。1 つの Terraform スタック、手動起動のワークフロー、Cloud Armor を持つグローバルロードバランサーの背後に閉じた 1 つの Cloud Run サービス、DNS で証明する Google 管理の証明書、そして変数としての Vertex AI レーン。
英語版が原文です。
1 つの Cloud Run サービスがイメージを、その run.app の URL を
閉じたまま動かします。そのため Cloudflare のエッジだけを受け入れる Cloud Armor のポリシーを持つ
グローバルロードバランサーが唯一の入り口であり、プロキシされた 1 つの Cloudflare レコードが
公開ホスト名です。ここにあるものはすべて infra/gcp/ と
.github/workflows/deploy-gcp-prod.yml です。後者は AWS のものと
同じ形の手動起動のワークフローで、変数もシークレットもアイデンティティもステートも、名前において
AWS と何も共有しません
(ADR-0054)。
このスタックを手で適用することは引き続き可能で、どちらでも同じスタックです。
サービスにはデータベースもバケットもなく、シークレットも一切ありません。Vertex AI には LiteLLM を
通じてインスタンス自身のサービスアカウントで到達するので、持つべき鍵がありません。重みは
イメージの中にあり、オフラインで読まれます。モデルのレーンに到達するのは下のスイッチが有効な
ときだけで、そうでなければ自分を利用不可と報告し、ランタイムのアイデンティティは aiplatform の
ロールを持ちません。
サービスはバイト列をインラインで受け取り、それ以外は受け取りません。INDX_URI_SCHEMES=none
がすべての URI ソースを無効にするので、呼び出し元はコンテナのディスク上のファイルも
プロジェクト内の URL も名指しできません。第二の層として INDX_LOADER_FILE_ROOTS は
サンプルのディレクトリに閉じています(ADR-0042)。AWS のタスクが AWS_REGION と並べて持つのと
同じ 3 行の固定の環境変数です。
| スタック | 適用するのは | 持つもの |
|---|---|---|
infra/gcp/terraform |
ワークフローの deploy、または運用者が手で |
有効化する API、Artifact Registry、ランタイムのサービスアカウント、Cloud Run サービス、ロードバランサーとその Cloud Armor ポリシー、DNS 認可とその Google 管理の証明書、2 つの Cloudflare レコード、予算 |
ステートは運用者が先に作る Cloud Storage のバケットにあり、部分バックエンドで名指しされるので、 それについての情報はリポジトリにありません。
bucket = "indx-everything-prod-tfstate"prefix = "indx-everything/gcp"このステートに秘密は入りません。オリジン証明書は Google 管理で、その鍵は Google の外に出ないので、
バケットが持つのはリソースのアドレスとロックだけです。just infra::terraform::validate は資格情報なしでこのスタックをフォーマット、
バックエンドなしで初期化、検証し、.github/workflows/infra.yml がすべてのプルリクエストで
それを走らせます。
境界は Cloud Armor のポリシーの 2 つのルールで、優先度の昇順に評価されます。優先度 900 の
ルールは Host ヘッダーが domain_name に一致しないとき 403 で拒否します。先頭と末尾を固定し、
大文字小文字を区別せず、ポートは任意です。これが、別のゾーンがこのアドレスを指して自分のものと
して配信するのを止めます。優先度 1000 からのルールが Cloudflare の公開 IPv4 レンジを許可します。
1 ルールあたり 10 レンジなのは、それが Cloud Armor の 1 ルールの上限だからです。最後のルールが
それ以外のすべてを拒否します。それは境界であって認証ではありません。API はその背後で認証なしで
あり、Cloudflare の顧客は誰もがそのレンジの内側にいます(ADR-0039)。ホスト名の Cloudflare
Access が、運用者が有効にする門です。
サービスの allUsers の invoker 付与はアプリを公開するものではありません。バランサーは認証なしで
転送するので、サービスは匿名の呼び出し元を受け入れる必要があるだけです。門は
ingress = INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER で、これがバランサーだけを受け入れます。
-
運用者が所有するプロジェクトと
gcloud。スタックはプロジェクトを素の変数として受け取り、 それについて何も仮定しません。ここでは専用のプロジェクトが正解です。下の 2 つの設定は プロジェクト全体に及びます。 -
Terraform
>= 1.9, < 2.0。 -
ステート用のバケットを一度だけ作ります。
Terminal window gcloud storage buckets create gs://indx-everything-prod-tfstate \--project=<project id> --location=asia-northeast1 --uniform-bucket-level-accessgcloud storage buckets update gs://indx-everything-prod-tfstate --versioning -
1 つの Cloudflare の資格情報。環境から読まれます。ゾーンの DNS を編集する
CLOUDFLARE_API_TOKENです。このスタックが Cloudflare に求めるのは 2 つのレコードだけで、 証明書は Google のものです。
スタックは必要な API を自分で有効にします。run、artifactregistry、compute、
certificatemanager、iam、logging、billingbudgets、そしてスイッチが有効なら aiplatform と cloudresourcemanager
です。新しいプロジェクトでは、そうしないとどのリソースも「有効になっていない」というエラーを
返し、それは読み解くのが難しいエラーだからです。destroy では無効化しません。プロジェクトは
運用者のもので、このスタック以外を持っているかもしれないからです。
Vertex AI のクォータ自体はここでは申請しません。それはテンプレートではなくプロジェクトの 所有者のものです(ADR-0053)。
Vertex AI スイッチ
Section titled “Vertex AI スイッチ”API は公開されており、Vertex AI を背後に持つレーンへの呼び出しはすべてプロジェクトに課金される
ので、それらのレーンは enable_vertex_ai が true でない限り無効です。無効とは、コンテナが
モデル変数を持たないことです。llm、ner-llm、enrich-llm は宣伝されず、それを名指しする
リクエストは 422 で拒否され、generic-vlm と hosted-text は自分を利用不可と報告します。
スイッチが無効のままモデルを指定すると、無視されるのではなく terraform plan が失敗します。
請求先アカウントや通知先のアドレスなしでスイッチを有効にした場合も同じです。
有効にすると、これらの変数がコンテナの次の行になります。
| 変数 | なるもの |
|---|---|
gcp_project_id |
VERTEXAI_PROJECT |
vertex_location |
VERTEXAI_LOCATION、既定では asia-northeast1。モデルの提供はリージョンごとで、サービスが動くリージョンとは別なので、独立した変数です |
vertex_llm_model |
INDX_CLASSIFIER_LLM_MODEL、INDX_NER_LLM_MODEL、INDX_ENRICH_LLM_MODEL、INDX_VLM_MODEL。いずれも vertex_ai/<model> |
vertex_embed_model |
INDX_EMBED_MODEL、vertex_ai/<model> |
vertex_embed_dimension |
INDX_EMBED_DIMENSION |
鍵もシークレットもありません。スイッチはランタイムのサービスアカウントに
roles/aiplatform.user を加え、aiplatform.googleapis.com を有効にし、LiteLLM は資格情報を
インスタンスから取ります。次元は発見されるのではなく宣言されます。次元のないホスト型の埋め込み
空間はそもそも宣伝されず、それも黙って行われるので、次元のない埋め込みモデルはプランが拒否します。
スイッチは billing_account 上の月次の Cloud Billing 予算
(vertex_monthly_budget_usd、既定では 50、整数の USD)も作り、アドレスごとに 1 つの
モニタリングチャネルを通じて、予測 80 パーセントと実績 100 パーセントで alert_emails に
通知します。それはブレーキではなく警笛です。Cloud Billing の予算は AWS Budgets のように
ロールを外すことができないので
(ADR-0040
は AWS だけのものです)、支出を止めるのは運用者がスイッチを無効にすることであり、超過は警告から
その apply までに課金された分です。予算のスコープは Vertex AI ではなくプロジェクト全体です。
Cloud Run のインスタンス、ロードバランサー、レジストリがモデルの呼び出しと一緒に数えられます。
GitHub、一度だけ
Section titled “GitHub、一度だけ”ワークフローは鍵を持ちません。GitHub はジョブが走る Environment に対して OIDC トークンを発行し、
プロジェクトの Workload Identity Federation のプロバイダーがそれを 2 つのサービスアカウントの
どちらかのトークンと交換します。それを作るのは gcloud による一度きりのコマンド列で、上の
ステート用のバケットと同じく手で行います。
プールが 1 つ、プロバイダーが 1 つ、GitHub の issuer 上にあり、このワークフローが走る 2 つの subject だけを受け入れます。subject は名前ではなく不変のオーナー ID とリポジトリ ID を持つので、 リポジトリ名を変更しても信頼は移りません。AWS のロールが信頼するのと同じ形です。
repo="repo:INDXDev@209891251/indx-everything@1301159823"
gcloud iam workload-identity-pools create github \ --project=<project id> --location=global --display-name="GitHub Actions"
gcloud iam workload-identity-pools providers create-oidc github \ --project=<project id> --location=global --workload-identity-pool=github \ --issuer-uri=https://token.actions.githubusercontent.com \ --attribute-mapping=google.subject=assertion.sub \ --attribute-condition="assertion.sub in ['${repo}:environment:production-gcp','${repo}:environment:production-gcp-plan']"サービスアカウントは 2 つです。plan のアイデンティティは読み取り専用で、deploy のそれはそうでは ないからです。それぞれ自分の subject の principal だけに紐づくので、plan の Environment のために 発行されたトークンで deploy のアカウントになりすますことはできません。
gcloud iam service-accounts create indx-everything-prod-deploy --project=<project id>gcloud iam service-accounts create indx-everything-prod-plan --project=<project id>
number=$(gcloud projects describe <project id> --format='value(projectNumber)')principal="principal://iam.googleapis.com/projects/${number}/locations/global/workloadIdentityPools/github/subject"
gcloud iam service-accounts add-iam-policy-binding \ indx-everything-prod-deploy@<project id>.iam.gserviceaccount.com \ --project=<project id> --role=roles/iam.workloadIdentityUser \ --member="${principal}/${repo}:environment:production-gcp"
gcloud iam service-accounts add-iam-policy-binding \ indx-everything-prod-plan@<project id>.iam.gserviceaccount.com \ --project=<project id> --role=roles/iam.workloadIdentityUser \ --member="${principal}/${repo}:environment:production-gcp-plan"deploy のアカウントのロールは、スタックが作るものから導いたものです。
roles/serviceusage.serviceUsageAdmin が API を有効にし、roles/artifactregistry.admin が
リポジトリを作り、その IAM メンバーを設定し、イメージを push します。
roles/iam.serviceAccountAdmin がランタイムのアカウントを作り、Cloud Run のサービスがそれとして
動けるようにするのが roles/iam.serviceAccountUser です。roles/run.admin がサービスを作り、
allUsers に invoker のロールを与えます。roles/compute.loadBalancerAdmin が NEG、バックエンド
サービス、URL マップ、HTTPS プロキシ、グローバルアドレス、転送ルールを、
roles/certificatemanager.editor が DNS 認可、証明書、プロキシがそれを読むマップを、
roles/compute.securityAdmin が Cloud Armor のポリシーを、roles/logging.admin が _Default
バケットの保持期間を、roles/browser が予算のフィルターが行うプロジェクトの読み取りを
受け持ちます。最後の 2 行が要るのは Vertex AI のスイッチが有効なときだけです。ランタイムの
アカウントへの roles/aiplatform.user の付与のための roles/resourcemanager.projectIamAdmin と、
予算が通知に使うチャネルのための roles/monitoring.notificationChannelEditor です。
for role in \ roles/serviceusage.serviceUsageAdmin \ roles/artifactregistry.admin \ roles/iam.serviceAccountAdmin \ roles/iam.serviceAccountUser \ roles/run.admin \ roles/compute.loadBalancerAdmin \ roles/certificatemanager.editor \ roles/compute.securityAdmin \ roles/logging.admin \ roles/browser \ roles/resourcemanager.projectIamAdmin \ roles/monitoring.notificationChannelEditor; do gcloud projects add-iam-policy-binding <project id> \ --member="serviceAccount:indx-everything-prod-deploy@<project id>.iam.gserviceaccount.com" \ --role="$role" --condition=Nonedone予算はプロジェクトではなく請求先アカウントのリソースなので、そのロールはアカウントに付けます。 スイッチが有効なときだけです。
gcloud billing accounts add-iam-policy-binding <billing account> \ --member="serviceAccount:indx-everything-prod-deploy@<project id>.iam.gserviceaccount.com" \ --role=roles/billing.costsManagerplan のアカウントが持つのは roles/viewer だけです。ステート用のバケットには両方のアカウントに
roles/storage.objectAdmin を与えます。GCS のバックエンドは plan でもロックのオブジェクトを
書くからです。
gcloud projects add-iam-policy-binding <project id> \ --member="serviceAccount:indx-everything-prod-plan@<project id>.iam.gserviceaccount.com" \ --role=roles/viewer --condition=None
for account in indx-everything-prod-deploy indx-everything-prod-plan; do gcloud storage buckets add-iam-policy-binding gs://indx-everything-prod-tfstate \ --member="serviceAccount:${account}@<project id>.iam.gserviceaccount.com" \ --role=roles/storage.objectAdmindoneこの一覧は上のリソースから導いたものであり、最初の実行までは検証されていません。最初のデプロイでの 拒否は、壊れたスタックではなく足りないロールとして読んでください。スタックはこれについて何も 仮定しません。リソースの系統ごとのカスタムロールという、より狭いポリシーを書くのは運用者です。
次に Environment を 2 つ作ります。production-gcp-plan はレビュアーなしでデプロイブランチの
ルールは main のみ、production-gcp は必須レビュアーつきで同じブランチのルールです。その
レビュアーが、すべての変更操作の承認です。
次にリポジトリの変数とシークレットを設定します。ワークフローが読むのはこの名前だけです。
| 名前 | 種類 | 値 |
|---|---|---|
GCP_PROD_DOMAIN_NAME |
変数、任意 | 公開ホスト名。indx.jp 配下の任意の名前。既定は gcp.indx.jp |
GCP_PROD_PROJECT_ID |
変数 | すべてのリソースを作るプロジェクト。既定値なし |
GCP_PROD_WORKLOAD_IDENTITY_PROVIDER |
変数 | プロバイダーの完全なリソース名 projects/<number>/locations/global/workloadIdentityPools/github/providers/github。既定値なし |
GCP_PROD_SERVICE_ACCOUNT |
変数 | deploy のアカウントのメールアドレス。既定値なし |
GCP_PROD_PLAN_SERVICE_ACCOUNT |
変数 | plan のアカウントのメールアドレス。既定値なし |
GCP_PROD_PROJECT_NAME |
変数、任意 | すべてのリソースの接頭辞であり、レジストリのリポジトリ名。既定は indx-everything-prod |
GCP_PROD_REGION |
変数、任意 | サービスとレジストリが置かれるリージョン。既定は asia-northeast1 |
GCP_PROD_CLOUDFLARE_ZONE_ID |
変数、任意 | レコードを書くゾーン。既定は indx.jp のゾーン |
GCP_PROD_TF_BACKEND_BUCKET |
変数、任意 | ステート用のバケット。既定は indx-everything-prod-tfstate |
GCP_PROD_TF_BACKEND_PREFIX |
変数、任意 | その中のプレフィックス。既定は indx-everything/gcp |
GCP_PROD_MIN_INSTANCES |
変数、任意 | 0 か 1。既定は 0 |
GCP_PROD_VERTEX_ENABLED |
変数、任意 | true で Vertex AI のレーンを有効化。既定は false |
GCP_PROD_VERTEX_LLM_MODEL |
変数、任意 | 例 gemini-2.5-flash。既定値なし。スイッチが無効のままならプランが拒否します |
GCP_PROD_VERTEX_EMBED_MODEL |
変数、任意 | 例 text-multilingual-embedding-002。既定値なし |
GCP_PROD_VERTEX_EMBED_DIMENSION |
変数、埋め込みモデル指定時は必須 | モデルのベクトル次元数。既定は 0 で、モデルと並ぶとプランが拒否します |
GCP_PROD_VERTEX_LOCATION |
変数、任意 | Vertex AI のリージョン。サービスが動くリージョンとは別です。既定は asia-northeast1 |
GCP_PROD_BILLING_ACCOUNT |
変数、スイッチ有効時は必須 | 予算を作る請求先アカウント。例 012345-6789AB-CDEF01。既定値なし |
GCP_PROD_VERTEX_MONTHLY_BUDGET_USD |
変数、任意 | 予算が通知する整数の USD。既定は 50 |
GCP_PROD_ALERT_EMAILS |
変数、スイッチ有効時は必須 | 予算が通知するアドレス。JSON のリストで、例 ["ops@example.com"]。既定は [] |
GCP_PROD_CLOUDFLARE_API_TOKEN |
シークレット、任意 | ゾーンの DNS を編集できるトークン。未設定ならワークフローは PROD_CLOUDFLARE_API_TOKEN、次に AWS のワークフローが持つ CLOUDFLARE_API_TOKEN を読みます。ゾーンは 1 つのゾーンだからです |
この一覧にモデルの鍵はなく、証明書の鍵もありません。Vertex AI にはインスタンス自身のサービス アカウントで到達し、証明書は Google のものなので、このデプロイが持つシークレットは Cloudflare の DNS トークン 1 つだけです。
GCP_PROD_DOMAIN_NAME は indx.jp 配下の任意の名前で、ホスト名がどのクラウドで動いているかを
語る必要はありません。3 つのスタックは 1 つのゾーンにレコードを書くので、どのジョブも何よりも先に、
ほかの 2 つのクラウドのホスト名である PROD_DOMAIN_NAME や AZURE_PROD_DOMAIN_NAME と等しい名前を
拒否します。2 つのワークフローが 1 つのレコードを名指さないのは、その 1 つの検査のおかげです。
deploy は運用者が confirm にその名前を打ち込んだときにだけ走ります。
すべての操作は main 上の Deploy to GCP (production) を手動で起動します。push はデプロイ
しません。cutover はありません。Cloudflare のレコードはスタックの中にあるので、ホスト名を
生かすのは最初のデプロイです。ロールバックのジョブもありません。ロールバックとは、レジストリが
すでに持つタグのデプロイだからです。
action |
Environment | 起きること |
|---|---|---|
plan |
production-gcp-plan |
スタックの読み取り専用プラン。その要約が、デプロイが名指しすべきアーティファクトです。 |
deploy |
production-gcp |
タグがなければイメージを Artifact Registry へビルドし、再プランして承認済みの実行と差があれば拒否し、適用し、サービスを待ち、公開ホストを証明します。 |
どの実行も静的検証のジョブから始まります。スタックを検証し、共有のスクリプトを lint し、
ワークフローに actionlint を走らせます。本番の資格情報は使いません。続いて action が名指しする
1 つのジョブが走ります。
production-gcp-plan で走るのでレビュアーはいませんが、main からだけ、読み取りはできて書き込みは
できない GCP_PROD_PLAN_SERVICE_ACCOUNT としてだけ走ります。
- ホスト名と、
image_tagが渡されていればその形を守ります。 - Environment の OIDC トークンをアプリケーションのデフォルト資格情報と交換します。google プロバイダーと GCS バックエンドが読むのはそれで、そのためにほかは何も渡されません。
GCP_PROD_TF_BACKEND_BUCKETとGCP_PROD_TF_BACKEND_PREFIXに対して初期化し、イメージ参照<region>-docker.pkg.dev/<project id>/<project name>/indx-everything:sha-<commit>でプランします。 プランは Cloudflare の IP 範囲とゾーンのレコードを読むので、DNS トークンを持つのはそのためです。core-plan-summary.jsonをgcp-prod-plan-summary-<run id>として 7 日保持でアップロードします。 中身はリソースのアドレスとアクション、それにプランしたコミット、ref、リポジトリで、秘密は ありません。
実行 ID を控えます。それがデプロイの名指しすべき plan_run_id で、そのコミットに対してだけ
有効です。
deploy
Section titled “deploy”confirm にホスト名、plan_run_id にそのプランの実行を渡して起動します。レビュアーが
production-gcp Environment を承認すると、次が走ります。
- ref、ホスト名、
confirm、数値のplan_run_id、image_tagの形を守り、その実行から承認済みの プラン要約をダウンロードします。 google_artifact_registry_repository.mainだけを適用し、push の前にリポジトリを存在させます。 必要な API の有効化は-targetが引き連れてきます。- ランナーのディスクを空け、GHCR にログインして重みのイメージを確保し、レジストリにサインインし、
Dockerfileをlinux/amd64でビルドしてsha-<commit>を push します。そのタグがすでに レジストリにあればスキップします。 - もう一度プランし、承認済みの要約と比べます。出どころはこのリポジトリ、
main、このコミットで なければならず、変更の集合は、レジストリとステップ 2 のブートストラップが有効にした API を 除いて同一でなければなりません。それ以外の差は、何も適用する前に実行を失敗させます。 - そのプランを適用し、Cloud Run のサービスが
Readyを報告するのを最大 900 秒待ちます。apply は_acme-challengeのレコードも書き、Google は apply が返ったあとにそれに対して証明書を発行する ので、その数分のあいだロードバランサーは 443 で何も応答しません。 - Cloudflare 経由で公開スモークを走らせます。
/health、各ホスト型レーンが持つべきケイパビリティ、file:の URI が拒否されること、2 つのページが配信されること、そしてロードバランサーの アドレスに対するよそのHostが 403 であることを確かめます。最後のものが、Cloud Armor の Host の式に対する唯一の証明です。min_instances = 0では最初のリクエストはインスタンスが 起きる間のプロキシの 524 に、初回のデプロイでは証明書の発行中の 5xx になることが想定される ので、スモークは何かを確かめる前に/healthを最大 900 秒再試行します。 - ホスト名に対して
just test::bdd::deployedを走らせます。運用者が手で走らせるのと同じ@deployedのシナリオです。 - ホスト名に対して
just frontend::e2e-deployedを走らせます。UI がデモを実行できないデプロイは、 緑ではなく赤いワークフローになります。
その実行自身のプラン要約は、成否によらず gcp-prod-deploy-summary-<run id> としてアップロード
されます。
再デプロイとロールバック
Section titled “再デプロイとロールバック”image_tag に、レジストリがすでに持つイメージの sha-<commit> を、plan とその後の deploy の
両方で設定します。プランはそのイメージのプランになり、レビュアーが読むのはそれです。deploy は
レジストリが持たないタグを名指しされれば、このコミットを以前のコミットの名前でビルドするのでは
なく拒否します。それがロールバックです。以前のイメージを同じ承認を通して再デプロイするだけで、
レコードも境界も動きません。どちらも apply が合わせるスタックの中にあるからです。
手で適用する
Section titled “手で適用する”これはワークフローが適用するのと同じスタックで、上の GitHub の設定を持たない運用者のための
手順です。サインインし(Terraform はアプリケーションのデフォルト資格情報を読み、gcloud auth login だけでは
それは書かれません)、Cloudflare のトークンを環境に置きます。
gcloud auth logingcloud auth application-default logingcloud config set project <project id>export CLOUDFLARE_API_TOKEN=<ゾーンの DNS を編集できるトークン>2 つの例からバックエンドと変数を書き、初期化します。
cd infra/gcp/terraformcp backend.hcl.example backend.hclcp terraform.tfvars.example terraform.tfvarsterraform init -backend-config=backend.hclイメージを push する前にレジストリが存在していなければならず、Cloud Run のサービスはイメージ
なしでは起動できないので、レジストリだけを先に適用します。必要な API の有効化は -target が
引き連れてきます。
terraform apply -target=google_artifact_registry_repository.mainイメージをビルドし、terraform.tfvars が名指しするタグで push します。
just infra::image::buildtag="sha-$(git rev-parse HEAD)"repo="asia-northeast1-docker.pkg.dev/<project id>/indx-everything-prod/indx-everything"docker tag indx-everything:local "$repo:$tag"gcloud auth configure-docker asia-northeast1-docker.pkg.devdocker push "$repo:$tag"残りをスイッチ無効のまま適用します。最初のデプロイが境界を証明し、モデルには何も課金されません。
terraform applyterraform outputapply は証明書が存在する前に返ります。Google は apply が書いた _acme-challenge のレコードを読み、
その後の数分で発行するので、それまでロードバランサーは 443 で何も応答しません。ACTIVE になるのを
見届けます。
gcloud certificate-manager certificates describe indx-everything-prod-origin \ --project=<project id> --format='value(managed.state)'Cloud Armor の Host の式は apply の前には何も証明できないので、apply の直後にすることは 2 つの
curl です。ホスト名は応答しなければならず、同じアドレスに対するそれ以外の Host は 403 で
なければなりません。
curl -sf https://<hostname>/health | jq -e '.status == "ok"'curl -ks -o /dev/null -w '%{http_code}\n' \ --resolve "other.example:443:$(terraform output -raw load_balancer_ip)" \ https://other.example/health続いてホストの残りを確かめます。
curl -sf https://<hostname>/ | grep -q '<html'curl -sf https://<hostname>/en/deploy/ | grep -q '<html'そしてサービスを一度温め、実ホストを叩く 2 つのスイートを走らせます。
curl -sf https://<hostname>/health >/dev/nulljust test::bdd::deployed https://<hostname>just frontend::e2e-deployed https://<hostname>この温めは儀式ではありません。下の節を見てください。最後にスイッチを有効にしてもう一度適用します。
terraform.tfvars には enable_vertex_ai = true、billing_account、モデル、次元、alert_emails
を設定します。
terraform applyスイッチをどちらの向きに切り替えても、コンテナの環境が変わるので新しいリビジョンになります。
ゼロへのスケール
Section titled “ゼロへのスケール”min_instances は既定で 0、検証で 0 か 1 に限られます。max_instance_count はどちらの場合も 1
です。0 はアイドルの間に何も費やしません。1 はインスタンスを 1 つ温めたままにし、24 時間
課金されます。
0 に代償がないわけではありません。コールドスタートはイメージの pull とエンジンの import であり、
起動プローブはそれに 240 秒(10 秒 × 24 回、Cloud Run の上限)を与えますが、Cloudflare の
プロキシは 100 秒で 524 を返して諦めます。Cloud Run 自身のリクエストのタイムアウト
(request_timeout_seconds、既定では 120)はリクエストが届いた時点から測られるので、冷えた
インスタンスを待つ時間も区切ります。それを超える起動は、プローブにまだ時間が残っていても 504 に
なります。したがってアイドルの後の最初のリクエストはプロキシで失敗することが想定され、1 分後の
リクエストは、それが起こしたインスタンスに対して成功します。スクリプトから呼ぶクライアントは
その最初の失敗を許容しなければならず、許容できないなら min_instances = 1 がその設定です。
コールドスタートは実際のデプロイではまだ計測していません。最初のデプロイの後にここへ数字を 書き入れてください。
- Cloud Armor の Host の式を証明するのはその 2 つの curl だけ。 受け付けられない CEL の式は
plan ではなく apply での 400 であり、間違った式はすべての呼び出し元を拒否します。最初の apply の
後と、
domain_nameを変えたあとに両方の curl を走らせてください。アドレスへ直接の curl は Host のルールだけでなく送信元レンジのルールにも拒否されるので、それが確かめるのはポリシーが 効いていることであって 1 つのルールを切り分けることではありません。切り分けるのは、このアドレスを 指す同じゾーンのもう 1 つのプロキシされたレコードです。 - 証明書は apply が返ったあとに発行される。 DNS 認可は非同期です。apply は
_acme-challenge.<hostname>を書いて返り、Google はそのレコードを読んでから、通常は数分で 発行します。見るべきは証明書のmanaged.stateで、ワークフローのスモークはその間を再試行で しのぎます。そのレコードはその名前で唯一のレコードでなければならず、Google が解決するので プロキシしません。indx.jpに CAA レコードがあればpki.googを許可しなければなりません。 今はありません。 run.appの URL は意図して閉じている。ingress = INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCERこそが Cloud Armor を境界たらしめています。何かを試すために開けることは、それがあるから安全なallUsersの invoker 付与も含め、境界全体を迂回することです。_Defaultのログバケットの保持期間はプロジェクト全体に及ぶ。log_retention_daysの行き先は ほかにありません。Cloud Run は Cloud Logging に書き、サービスごとの保管場所はないからです。 共有プロジェクトでは、これが黙って別のワークロードの保持期間を変えます。専用のプロジェクトが 本当の答えです。cpu_idle = falseはリクエストではなくインスタンスの生存時間全体に課金する。 エンジンが 初回利用時にキャッシュを温め、絞られたインスタンスではそれが際限なく遅くなるため、CPU は リクエストの間も割り当てられたままです。min_instances = 1では、それがcontainer_cpu(既定では 2 vCPU)を 24 時間課金することになります。- 予算が見ているのはプロジェクトであってモデルではない。 Vertex AI のサービスに絞るのは
budget_filter.servicesの項目であり、このテンプレートがしない仕事です。 monitoring.googleapis.comは有効化の一覧にない。 新しいプロジェクトでは既定で有効です。 無効になっているプロジェクトでは、スイッチを有効にしたときに通知チャネルで失敗します。cloudresourcemanager.googleapis.comは一覧にありますが、スイッチを有効にしたときだけです。 予算のフィルターはプロジェクト番号を受け取り、それは Cloud Resource Manager の読み取りです。 その読み取りのcountが、スイッチが無効の間は読み取り自体をなくし、depends_onがそれを apply まで留めるので、スイッチを有効にした最初の plan が、まだ有効化されていない API を呼ぶ ことはありません。deletion_protectionはfalse。 google プロバイダーの既定は true で、そのままだとterraform destroyは、それを消すための apply を挟むまで失敗します。1 人の運用者が手で適用し 手で壊すスタックでは、それは守りではなく罠です。ここで destroy を止めるのは運用者だけです。- 境界は IPv4 のみで、ALB のセキュリティグループと同じです。IPv6 の訪問者は Cloudflare が 受け、オリジンへは IPv4 で到達します。