GCP
indx が GCP でどう動くか。1 つの Terraform スタック、手動起動のワークフロー、公開のオリジンであるグローバルロードバランサーの背後に閉じた 1 つの Cloud Run サービス、DNS で証明する Google 管理の証明書、そして変数としての Vertex AI レーン。
英語版が原文です。
1 つの Cloud Run サービスがイメージを、その run.app の URL を
閉じたまま動かします。そのためグローバルロードバランサーが唯一の入り口であり、運用者が手で作る
DNS のみの 1 つの A レコードが公開ホスト名です。ここにあるものはすべて infra/gcp/ と
.github/workflows/deploy-gcp-prod.yml です。後者は AWS のものと
同じ形の手動起動のワークフローで、変数もシークレットもアイデンティティもステートも、名前において
AWS と何も共有しません
(ADR-0054)。
このスタックを手で適用することは引き続き可能で、どちらでも同じスタックです。
2026-09-18 に everything.g.indx.jp で実証済みです。デプロイの実行は、公開スモーク、@deployed のシナリオ、Web アプリのスペックまで緑です。
サービスにはデータベースもバケットもなく、シークレットも一切ありません。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 サービス、ロードバランサー、DNS 認可とその Google 管理の証明書、予算 |
ステートは運用者が先に作る Cloud Storage のバケットにあり、部分バックエンドで名指しされるので、 それについての情報はリポジトリにありません。
bucket = "indx-everything-prod-tfstate"prefix = "indx-everything/gcp"このステートに秘密は入りません。オリジン証明書は Google 管理で、その鍵は Google の外に出ないので、
バケットが持つのはリソースのアドレスとロックだけです。just infra::terraform::validate は資格情報なしでこのスタックをフォーマット、
バックエンドなしで初期化、検証し、.github/workflows/infra.yml がすべてのプルリクエストで
それを走らせます。
境界はありません。A レコードは DNS のみなので、訪問者はバランサーに直接到達し、バランサーは 誰もを受け入れます。Cloudflare のプロキシの背後にいるもう 1 つのクラウドは AWS で、こちらは そうではありません。ホスト名がゾーンから 2 ラベル下で、Cloudflare の無料の証明書が覆うのは 1 ラベルだからであり、運用者が Cloudflare のトークンを持たないからです。このスタックが必要と する 2 つのレコードは手で作ります (ADR-0065)。 スタックは DNS のプロバイダーを何も知りません。API は認証なしで(ADR-0039)、DNS のみのレコードでは Cloudflare Access が使えません。認証は、来るとすればアプリか Google のものです。
サービスの invoker チェックは無効です(invoker_iam_disabled)。それが、バランサーに認証なしで
転送させるものです。allUsers への invoker 付与ではありません。組織のドメイン制限共有のポリシーが
どの IAM ポリシーでもそのメンバーを拒み、このスイッチはメンバーを一切名指ししないからです。
ingress = INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER が run.app の URL を閉じたままにするので、
バランサーが証明する 1 つのホスト名が、サービスが応答する唯一の名前です。
-
運用者が所有するプロジェクトと
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 -
ゾーンの DNS への書き込み権限。ダッシュボードで行います。資格情報は読みません。ホスト名が 必要とする 2 つのレコード、バランサーを指す A レコードと、Google が証明書を発行する対象の
_acme-challengeの CNAME は手で作り、その値はスタックができて初めて存在するので、最初の デプロイはそれを印字して止まります。
スタックは必要な 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 が invoker チェックを
無効にしたサービスを作ります。roles/compute.loadBalancerAdmin が NEG、バックエンド
サービス、URL マップ、HTTPS プロキシ、グローバルアドレス、転送ルールを、
roles/certificatemanager.editor が DNS 認可、証明書、プロキシがそれを読むマップを、
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/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_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"]。既定は [] |
この一覧にシークレットは 1 つもありません。Vertex AI にはインスタンス自身のサービスアカウントで 到達し、証明書は Google のもので、このワークフローは DNS レコードを何も書きません。
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 はありません。レコードは運用者のもので、動かされることがないからです。
ロールバックのジョブもありません。ロールバックとは、レジストリがすでに持つタグのデプロイだから
です。
action |
Environment | 起きること |
|---|---|---|
plan |
production-gcp-plan |
スタックの読み取り専用プラン。その要約が、デプロイが名指しすべきアーティファクトです。 |
deploy |
production-gcp |
タグがなければイメージを Artifact Registry へビルドし、再プランして承認済みの実行と差があれば拒否し、適用し、サービスを待ち、2 つの DNS レコードを検査して証明書を待ち、公開ホストを証明します。 |
どの実行も静的検証のジョブから始まります。スタックを検証し、共有のスクリプトを 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>でプランします。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 秒待ちます。 - ホスト名の A レコードと
_acme-challengeの CNAME を解決し、バランサーのアドレスと DNS 認可の レコードと突き合わせます。違っていれば、作るべき 2 つのレコードをジョブの要約に書いて失敗 します。そこが最初のdeployの終わりです。一致していれば、証明書がACTIVEになるのを最大 30 分待ちます。Google はチャレンジのレコードを読んだ時点で、通常は数分で発行し、オリジンには 決して到達しません。 - 公開スモークを走らせます。
/health、各ホスト型レーンが持つべきケイパビリティ、file:の URI が拒否されること、2 つのページが配信されること、そしてバランサーのアドレスが、ホスト名を名指す 直接のクライアントに 200 を返すことを確かめます。それが公開のオリジンだからです。min_instances = 0では最初のリクエストはインスタンスが起きる間に失敗することが想定されるので、 スモークは何かを確かめる前に/healthを最大 900 秒再試行します。 - ホスト名に対して
just test::bdd::deployedを走らせます。運用者が手で走らせるのと同じ@deployedのシナリオです。 - ホスト名に対して
just frontend::e2e-deployedを走らせます。UI がデモを実行できないデプロイは、 緑ではなく赤いワークフローになります。
最初の deploy は 2 サイクルです。バランサーのアドレスもチャレンジのレコードもスタックができて
初めて存在するので、最初の実行はスタックを適用し、作るべき A レコードと CNAME を自分の要約に
印字して、ステップ 6 で失敗します。それらを DNS のみで作り、もう一度 plan と deploy を走らせて
ください。その 2 回目の実行が証明書を待ち、3 つのスイートを走らせます。
その実行自身のプラン要約は、成否によらず gcp-prod-deploy-summary-<run id> としてアップロード
されます。
再デプロイとロールバック
Section titled “再デプロイとロールバック”image_tag に、レジストリがすでに持つイメージの sha-<commit> を、plan とその後の deploy の
両方で設定します。プランはそのイメージのプランになり、レビュアーが読むのはそれです。deploy は
レジストリが持たないタグを名指しされれば、このコミットを以前のコミットの名前でビルドするのでは
なく拒否します。それがロールバックです。以前のイメージを同じ承認を通して再デプロイするだけで、
レコードは決して動きません。それが指すアドレスは apply が合わせるスタックの中にあるからです。
手で適用する
Section titled “手で適用する”これはワークフローが適用するのと同じスタックで、上の GitHub の設定を持たない運用者のための
手順です。サインインします(Terraform はアプリケーションのデフォルト資格情報を読み、gcloud auth login だけでは
それは書かれません)。
gcloud auth logingcloud auth application-default logingcloud config set project <project id>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"残りをスイッチ無効のまま適用して、モデルには何も課金されないようにし、レコードに必要な 2 つの値を 読みます。
terraform applyterraform output -raw load_balancer_ipterraform output -json acme_challenge_recordゾーンにその 2 つのレコードを DNS のみで作ります。ホスト名に、アドレスを内容とする A レコードと、
チャレンジのレコードの name に、その data を内容とする CNAME です。
Google は _acme-challenge のレコードを読み、その後の数分でそれに対して証明書を発行するので、
それまでロードバランサーは 443 で何も応答しません。ACTIVE になるのを見届けます。
gcloud certificate-manager certificates describe indx-everything-prod-origin \ --project=<project id> --format='value(managed.state)'そのうえで、ホスト名は応答しなければならず、クライアントがホスト名を名指すならアドレス自身も 応答しなければなりません。バランサーが公開のオリジンだからです。
curl -sf https://<hostname>/health | jq -e '.status == "ok"'curl -s -o /dev/null -w '%{http_code}\n' \ --resolve "<hostname>:443:$(terraform output -raw load_balancer_ip)" \ https://<hostname>/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 の上限)を与えます。Cloud Run 自身の
リクエストのタイムアウト(request_timeout_seconds、既定では 120)はリクエストが届いた時点から
測られるので、冷えたインスタンスを待つ時間も区切ります。それを超える起動は、プローブにまだ時間が
残っていても 504 になります。したがってアイドルの後の最初のリクエストは失敗することが想定され、
1 分後のリクエストは、それが起こしたインスタンスに対して成功します。スクリプトから呼ぶクライアントは
その最初の失敗を許容しなければならず、許容できないなら min_instances = 1 がその設定です。
コールドスタートは実際のデプロイではまだ計測していません。最初のデプロイの後にここへ数字を 書き入れてください。
- どちらのレコードもプロキシしないままにする。 Google は証明書を発行し更新するために
_acme-challenge.<hostname>を解決し、そのレコードはその名前で唯一のレコードでなければ なりません。プロキシされた A レコードは、無料の証明書が覆わないホスト名の前に Cloudflare の エッジを置くことになり、ブラウザはバランサーに届く前にハンドシェイクで失敗します。indx.jpに CAA レコードがあればpki.googを許可しなければなりません。今はありません。 - 証明書はレコードができたあとに発行される。 DNS 認可は非同期です。Google はチャレンジの
レコードを読んでから、通常は数分で発行します。見るべきは証明書の
managed.stateで、 ワークフローはスモークの前にそれを待ちます。それまでロードバランサーは 443 で何も応答しません。 - レコードを突き合わせるステップは DNS をランナーのリゾルバーで読む。 実行の少し前に作った レコードはそれでもそこにないことがあり、そのときステップは、レコードが存在しないかのように それを印字して失敗します。1 分待ってもう一度起動してください。直すものはありません。
_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 を止めるのは運用者だけです。