コンテンツにスキップ

Azure

indx が Azure でどう動くか。1 つの Terraform スタック、手動起動のワークフロー、マネージド証明書で自分のホスト名を直接配信する 1 つの Container App、そして変数としての Azure OpenAI レーン。

英語版が原文です。

1 つの Container App がイメージを Consumption の環境で動かし、 そのイングレスが公開のオリジンであり、運用者が手で作る DNS のみの 1 つの CNAME が公開ホスト名です。 ここにあるものはすべて infra/azure/.github/workflows/deploy-azure-prod.yml です。後者は手動起動のワークフローで、 AWS のものと同じ形をしており、変数もシークレットも アイデンティティもステートも、名前の上で AWS と共有しません (ADR-0054)。 同じスタックを手で適用することも引き続きできます。

コンテナにはデータベースもバケットもありません。重みはイメージの中にあり、オフラインで 読まれます。Azure OpenAI には LiteLLM を通じて、Container App のシークレットとして持つ API キーで、下のスイッチが有効なときだけ到達します。そうでなければ LLM レーンは自分を利用不可と 報告し、シークレットは存在しません。

コンテナはバイト列をインラインで受け取り、それ以外は受け取りません。INDX_URI_SCHEMES=none がすべての URI ソースを無効にするので、呼び出し元はコンテナのディスク上のファイルも サブスクリプション内の URL も名指しできません。第二の層として INDX_LOADER_FILE_ROOTS は サンプルのディレクトリに閉じています(ADR-0042)。AWS のタスクが AWS_REGION と並べて持つのと 同じ 3 行の固定の環境変数です。

スタック 適用するのは 持つもの
infra/azure/terraform ワークフローの deploy、または運用者が手で リソースグループ、ACR、アプリのアイデンティティ、Log Analytics、Consumption の環境、Container App、予算

ステートは運用者が先に作る Azure Storage のコンテナーにあり、部分バックエンドで名指しされるので、 それについての情報はリポジトリにありません。

resource_group_name = "indx-everything-prod-tfstate"
storage_account_name = "indxeverythingtfstate"
container_name = "tfstate"
key = "indx-everything/azure/terraform.tfstate"

そのステートに秘密は入りません。証明書は Azure のもので、Azure が発行し更新し、その鍵が プラットフォームの外へ出ることはありません。スタックは DNS のプロバイダーを何も知りません。 ホスト名とその証明書を束ねるのは、運用者が手で作る 2 つのレコードが解決するようになった時点の、 ワークフローの deployaz containerapp hostname bind です。just infra::terraform::validate は資格情報なしで このスタックをフォーマット、バックエンドなしで初期化、検証し、.github/workflows/infra.yml が すべてのプルリクエストでそれを走らせます。

境界はありません。レコードは DNS のみなので、訪問者はイングレスに直接届き、 イングレスは誰でも受け入れます。Container Apps がマネージド証明書を発行し更新するのは、アプリの FQDN を直接指す CNAME に対してだけで、しかも DigiCert が HTTP でアプリに到達できるときだけです。 間に挟まるプロキシや許可リストは、それを禁じてしまいます。このアプリに束ねられていない Host を 拒否するのは、Container Apps 自身のホスト名によるルーティングで、カスタムドメインとアプリの 既定の FQDN だけを提供します。ほかの 2 つのクラウドは Cloudflare のプロキシの背後にいますが、 ここはそうではありません。ホスト名がゾーンから 2 ラベル下にあり、Cloudflare の無料の証明書が 覆うのは 1 ラベルまでだからであり、その形が必要とする Origin CA 証明書が SSL and Certificates / Edit を持つ Cloudflare のトークンを要し、運用者がそれを、ひいてはトークンそのものを断ったからです。 2 つのレコードは手で作ります (ADR-0064)。 API は認証なしであり(ADR-0039)、DNS のみのレコードでは Cloudflare Access が使えません。 認証は、来るとすればアプリか Azure のものです。

  • 運用者が所有するサブスクリプションと az。スタックはサブスクリプションを素の変数として受け取り、 それについて何も仮定しません。

  • Terraform >= 1.9, < 2.0

  • ステート用のアカウントとコンテナーを一度だけ作ります。

    Terminal window
    az group create --name indx-everything-prod-tfstate --location japaneast
    az storage account create --name indxeverythingtfstate \
    --resource-group indx-everything-prod-tfstate --location japaneast \
    --sku Standard_LRS --encryption-services blob
    az storage container create --name tfstate \
    --account-name indxeverythingtfstate --auth-mode login
  • ゾーンの DNS への書き込み権限。ダッシュボードで使うものです。資格情報は 1 つも読みません。 ホスト名が必要とする 2 つのレコードは手で作りますが、その値はアプリができて初めて存在するので、 最初のデプロイはそれらを印字して止まります。

  • 空いているレジストリ名。ACR の名前は azurecr.io の DNS ラベルなので、レジストリは project_name から区切り文字を取り除いたもの(既定では indxeverythingprod)であり、その名前は Azure 全体で一意です。別のテナントが持っていれば apply は名前の可用性エラーで失敗します。 project_name を変えてください。

Azure OpenAI のアカウント自体はここでは作りません。それはクォータの申請、リージョンの選択、 データの所在の決定を伴い、テンプレートではなくサブスクリプションの所有者のものです(ADR-0053)。

API は公開されており、Azure OpenAI を背後に持つレーンへの呼び出しはすべてサブスクリプションに 課金されるので、それらのレーンは enable_azure_openaitrue でない限り無効です。無効とは、 コンテナがモデル変数を持たないことです。llmner-llmenrich-llm は宣伝されず、それを 名指しするリクエストは 422 で拒否され、generic-vlmhosted-text は自分を利用不可と 報告します。スイッチが無効のままデプロイメントを指定すると、無視されるのではなく terraform plan が失敗します。エンドポイントと鍵なし、あるいは通知先のアドレスなしでスイッチを 有効にした場合も同じです。

有効にすると、これらの変数がコンテナの次の行になります。

変数 なるもの
azure_openai_endpoint AZURE_API_BASE
azure_openai_api_version AZURE_API_VERSION、既定では 2024-10-21
azure_openai_api_key AZURE_API_KEY。環境値ではなく Container App のシークレットとして持つので、リビジョンのテンプレートからは読めません
azure_openai_llm_deployment INDX_CLASSIFIER_LLM_MODELINDX_NER_LLM_MODELINDX_ENRICH_LLM_MODELINDX_VLM_MODEL。いずれも azure/<deployment>
azure_openai_embed_deployment INDX_EMBED_MODELazure/<deployment>
azure_openai_embed_dimension INDX_EMBED_DIMENSION

次元は発見されるのではなく宣言されます。次元のないホスト型の埋め込み空間はそもそも宣伝されず、 それも黙って行われるので、次元のない埋め込みデプロイメントはプランが拒否します。

スイッチはリソースグループに対する月次の予算(azure_openai_monthly_budget_usd、既定では 50)も 作り、予測 80 パーセントと実績 100 パーセントで alert_emails に通知します。それはブレーキでは なく警笛です。Azure の予算は AWS Budgets のようにロールを外すことができないので (ADR-0040 は AWS だけのものです)、支出を止めるのは運用者がスイッチを無効にすることであり、超過は警告から その apply までに課金された分です。予算のスコープは Azure OpenAI のアカウントではなくこの リソースグループです。このグループの中に作られたアカウントは対象になり、ほかの場所に作られた ものは対象になりません。その場合、この数字が区切るのはコンピュートだけです。budget_start_date は月の初日でなければなりません。Azure はそれ以外を受け付けず、予算ができた後は動かせないからです。

ワークフローは 2 つの Entra のアプリケーション登録として認証します。deploy 用と plan 用で、 それぞれ 1 つの Environment のためだけに GitHub の OIDC の発行者を信頼します。一度だけ、手で 作ります。

Terminal window
subscription=<subscription id>
deploy=$(az ad app create --display-name indx-everything-prod-azure-deploy --query appId -o tsv)
plan=$(az ad app create --display-name indx-everything-prod-azure-plan --query appId -o tsv)
az ad sp create --id "$deploy"
az ad sp create --id "$plan"
az ad app federated-credential create --id "$deploy" --parameters '{
"name": "github-production-azure",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:INDXDev@209891251/indx-everything@1301159823:environment:production-azure",
"audiences": ["api://AzureADTokenExchange"]
}'
az ad app federated-credential create --id "$plan" --parameters '{
"name": "github-production-azure-plan",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:INDXDev@209891251/indx-everything@1301159823:environment:production-azure-plan",
"audiences": ["api://AzureADTokenExchange"]
}'

サブジェクトはリポジトリとその所有者を INDXDev/indx-everything ではなく不変の ID で名指しするので、 それらの Environment の外のワークフローはこれらのアイデンティティのトークンを交換できず、 リポジトリ名を変更しても信頼は移りません。オーディエンス api://AzureADTokenExchange は、この交換で Entra が受け付ける唯一のものです。

続いてロールです。

Terminal window
state="/subscriptions/$subscription/resourceGroups/indx-everything-prod-tfstate/providers/Microsoft.Storage/storageAccounts/indxeverythingtfstate"
az role assignment create --assignee "$deploy" --role Contributor --scope "/subscriptions/$subscription"
az role assignment create --assignee "$deploy" --role "Role Based Access Control Administrator" --scope "/subscriptions/$subscription"
az role assignment create --assignee "$plan" --role Reader --scope "/subscriptions/$subscription"
for app in "$deploy" "$plan"; do
az role assignment create --assignee "$app" --role "Storage Blob Data Contributor" --scope "$state"
done

そして最初の deploy がリソースグループを作ったあとに、プラン側のアプリケーションにもう 1 つ。

Terminal window
az role assignment create --assignee "$plan" --role "Container Apps Operator" \
--scope "/subscriptions/$subscription/resourceGroups/indx-everything-prod"

スコープがサブスクリプションなのは、スタック自身がリソースグループを作るからで、それができるまで もっと狭いものを与えようがありません。デプロイ側のアプリケーションに 2 つ目のロールがあるのは、 スタックがアプリに自分のイメージを pull させる AcrPull の割り当ても書くからで、ロールの割り当てを 書くことは Contributor にできることではありません。Reader はプランがサブスクリプションに求める すべてです。ただし Container App ができるまでの話です。それをリフレッシュすると listSecrets を 呼び、それは read ではなくアクションなので、Reader だけでは 2 回目のプランが 403 で失敗します。 Container Apps Operator は、アプリのアクションを書き込みなしで持つ組み込みのロールで、その アイデンティティにアプリの再起動も許します。リソースグループに絞って、最初のデプロイのあとに 与えるのは、それより前にはグループがないからです。どちらのアプリケーションもステートのアカウントに書きます。バックエンドはプランのため だけでもステートの blob にリースを取るからです。スタックはこれらのロールについて何も仮定せず、 このリソースグループとこのスタックが名指しするアドレスに絞ったより狭いポリシーは、運用者が 書くものです。

続いて Environment を 2 つ。production-azure-plan はレビュアーなしでデプロイブランチのルールは main のみ。production-azure は必須レビュアーつきで同じルール。そのレビュアーがすべての変更操作の 承認です。

続いてリポジトリの変数とシークレットを、ワークフローが読むとおりの名前で設定します。

名前 種類
AZURE_PROD_DOMAIN_NAME 変数、任意 公開ホスト名。indx.jp 配下の任意の名前。既定は azure.indx.jp
AZURE_PROD_SUBSCRIPTION_ID 変数 すべてのリソースが作られるサブスクリプション。既定値なし
AZURE_PROD_TENANT_ID 変数 2 つのアプリケーションがいるテナント。既定値なし
AZURE_PROD_CLIENT_ID 変数 デプロイ側のアプリケーションの ID。既定値なし
AZURE_PROD_PLAN_CLIENT_ID 変数 プラン側のアプリケーションの ID。既定値なし
AZURE_PROD_PROJECT_NAME 変数、任意 既定では indx-everything-prod
AZURE_PROD_LOCATION 変数、任意 既定では japaneast
AZURE_PROD_TF_BACKEND_RESOURCE_GROUP 変数、任意 既定では indx-everything-prod-tfstate
AZURE_PROD_TF_BACKEND_STORAGE_ACCOUNT 変数、任意 既定では indxeverythingtfstate
AZURE_PROD_TF_BACKEND_CONTAINER 変数、任意 既定では tfstate
AZURE_PROD_TF_BACKEND_KEY 変数、任意 既定では indx-everything/azure/terraform.tfstate
AZURE_PROD_MIN_INSTANCES 変数、任意 既定では 0。0 か 1
AZURE_PROD_OPENAI_ENABLED 変数、任意 true で Azure OpenAI のレーンを有効化。既定では false
AZURE_PROD_OPENAI_ENDPOINT 変数、スイッチ有効時は必須 https://<account>.openai.azure.com。既定値なし
AZURE_PROD_OPENAI_API_VERSION 変数、任意 既定では 2024-10-21
AZURE_PROD_OPENAI_LLM_DEPLOYMENT 変数、任意 分類、NER、エンリッチメント、VLM の各レーンの背後のデプロイメント。例 gpt-4o。既定値なし
AZURE_PROD_OPENAI_EMBED_DEPLOYMENT 変数、任意 ホスト型の埋め込み空間の背後のデプロイメント。例 text-embedding-3-large。既定値なし
AZURE_PROD_OPENAI_EMBED_DIMENSION 変数、埋め込みデプロイメント指定時は必須 モデルのベクトル次元数。例 3072。既定では 0
AZURE_PROD_OPENAI_MONTHLY_BUDGET_USD 変数、任意 既定では 50
AZURE_PROD_ALERT_EMAILS 変数、スイッチ有効時は必須 JSON の配列。例 ["ops@example.com"]。既定では []
AZURE_PROD_BUDGET_START_DATE 変数、任意 月の初日。既定では 2026-10-01T00:00:00Z
AZURE_PROD_OPENAI_API_KEY シークレット、スイッチ有効時は必須 アカウントの API キー。既定値なし

Cloudflare のシークレットはありません。このワークフローは DNS レコードを書きません。

AZURE_PROD_DOMAIN_NAMEindx.jp 配下の任意の名前で、ホスト名がどのクラウドで動いているかを 語る必要はありません。3 つのスタックは 1 つのゾーンにレコードを書くので、どのジョブも何よりも先に、 ほかの 2 つのクラウドのホスト名である PROD_DOMAIN_NAMEGCP_PROD_DOMAIN_NAME と等しい名前を 拒否します。2 つのワークフローが 1 つのレコードを名指さないのは、その 1 つの検査のおかげです。 deploy は運用者が confirm にその名前を打ち込んだときにだけ走ります。

すべての操作は main 上の Deploy to Azure (production) を手動で起動します。push はデプロイしません。 どの実行も、本番の資格情報を持たない静的検証のジョブから始まり、続いて action が名指しする 1 つの ジョブが走ります。

action Environment 起きること
plan production-azure-plan スタックの読み取り専用プラン。その要約が、デプロイが名指しすべきアーティファクトです。
deploy production-azure レジストリだけを適用し、イメージをビルドして push し、再プランして承認済みの実行と差があれば拒否し、適用し、リビジョンを待ち、マネージド証明書を束ね、公開スモーク、@deployed のシナリオ、Web アプリの仕様を走らせます。

main で起動します。レビュアーはいないので承認はこのプランと変更操作の間に残り、アイデンティティは 読み取り専用のものです。ジョブは 4 つの AZURE_PROD_TF_BACKEND_ の変数からバックエンドを初期化し、 イメージ参照 <registry>.azurecr.io/indx-everything:sha-<commit> でプランし、要約を azure-prod-plan-summary-<run id> としてアップロードします。その要約はリソースのアドレスとアクション、 それにプランしたコミット、ref、リポジトリであり、秘密は何も含まないので、レビュアーが読むのはそれです。

実行 ID を控えます。それがデプロイの名指しすべき plan_run_id で、そのコミットに対してだけ有効です。

confirm にホスト名、plan_run_id にそのプランの実行を渡して起動します。レビュアーが production-azure Environment を承認すると、次が順に走ります。

  1. ref、ホスト名、確認、実行 ID をガードし、その実行から承認済みの要約をダウンロードします。
  2. azurerm_container_registry.main だけを適用します。存在しないレジストリにはイメージを push できず、Container App はイメージなしでは起動できません。必要なリソースグループは -target が 引き連れてきます。
  3. ランナーのディスクを空け、重みのイメージを確保し、Dockerfilelinux/amd64 でビルドして sha-<commit> を push します。そのタグがすでにレジストリにあればスキップします。
  4. もう一度プランし、承認済みの要約と比べます。出どころはこのリポジトリ、main、このコミットで なければならず、変更の集合は上のステップが作ったレジストリとリソースグループを除いて同一で なければなりません。それ以外の差は、何も適用する前に実行を失敗させます。
  5. そのプランを適用します。
  6. 最新の有効なリビジョンが Provisioned かつ Healthy と報告するのを最大 900 秒待ちます。
  7. マネージド証明書を束ねます。まずホスト名と asuid の TXT を解決し、アプリの FQDN と検証 ID と 突き合わせます。違っていれば、作るべき 2 つのレコードをジョブの要約に書いて失敗します。そこが 最初の deploy の終わりです。一致していれば、az containerapp hostname add でホスト名を アプリに一度だけ加え(アプリが持たないホスト名のマネージド証明書は拒まれます)、 az containerapp hostname bind--validation-method CNAME で走らせ、証明書を見つけるか 発行し、それを束ねます。これがリビジョンの後なのは、DigiCert が動いているアプリに対して HTTP で その名前を検証するからで、一度束ねた後は何もしません。
  8. 公開スモーク scripts/infra/deployed-smoke.sh を走らせます。ホスト名を叩き、ホスト型の各レーンが そのモデルが設定されているときに限って存在すること、file: URI が拒否されること、イングレスの FQDN が直接のクライアントに 200 を返すことを検証します。それが公開のオリジンだからです。
  9. 公開ホストに対して just test::bdd::deployed を走らせます。
  10. 公開ホストに対して just frontend::e2e-deployed を走らせます。UI がデモを実行できないデプロイは、 緑ではなく赤いワークフローになります。

スモークの最初のリクエストは、レプリカが起きる間に失敗しえます。そのため最初の返事で失敗せず、 最大 15 分 /health を再試行します。それは下のゼロへのスケールの代償であって、不安定さでは ありません。

再デプロイとは、レジストリがすでに持つイメージの sha-<commit>image_tag に渡すことで、plandeploy の両方に渡します。それがロールバックです。ワークフローはレジストリが持たない image_tag を拒否するので、名指しされたタグは一度ビルドされたものの再デプロイにしかなりません。 cutover の操作はありません。レコードは運用者のものであり、動かされることがないからです。 rollback のジョブもありません。ロールバックはその同じ deploy だからです。

最初の deploy は 2 サイクルです。アプリの FQDN と検証 ID はアプリができて初めて存在するので、 1 回目の実行はスタックを適用し、作るべき CNAME と TXT を要約に印字して、束ねのところで失敗します。 それらを DNS のみで作り、もう一度 plandeploy を走らせてください。その 2 回目の実行が証明書を 束ね、3 つのスイートを走らせます。

これはワークフローが適用するのと同じスタックで、上の GitHub のセットアップを持たない運用者のための ものです。

サインインします。

Terminal window
az login
az account set --subscription <subscription id>

2 つの例からバックエンドと変数を書き、初期化します。

Terminal window
cd infra/azure/terraform
cp backend.hcl.example backend.hcl
cp terraform.tfvars.example terraform.tfvars
terraform init -backend-config=backend.hcl

イメージを push する前にレジストリが存在していなければならず、Container App はイメージなしでは 起動できないので、レジストリだけを先に適用します。必要なリソースグループは -target が 引き連れてきます。

Terminal window
terraform apply -target=azurerm_container_registry.main

イメージをビルドし、terraform.tfvars が名指しするタグで push します。

Terminal window
just infra::image::build
tag="sha-$(git rev-parse HEAD)"
docker tag indx-everything:local "indxeverythingprod.azurecr.io/indx-everything:$tag"
az acr login --name indxeverythingprod
docker push "indxeverythingprod.azurecr.io/indx-everything:$tag"

残りをスイッチ無効のまま適用してモデルには何も課金されないようにし、レコードが必要とする 2 つの 値を読みます。

Terminal window
terraform apply
terraform output -raw app_fqdn
terraform output -raw custom_domain_verification_id

2 つのレコードをゾーンに DNS のみで作ります。ホスト名に置く CNAME でその内容が FQDN であるものと、 asuid.<hostname> に置く TXT でその内容が検証 ID であるものです。そのうえで、リビジョンが healthy に なり、レコードが解決するようになったら、マネージド証明書を束ねます。

Terminal window
az containerapp hostname add \
--name "$(terraform output -raw app_name)" \
--resource-group "$(terraform output -raw resource_group)" \
--hostname <hostname>
az containerapp hostname bind \
--name "$(terraform output -raw app_name)" \
--resource-group "$(terraform output -raw resource_group)" \
--environment "$(terraform output -raw environment_name)" \
--hostname <hostname> --validation-method CNAME

add は一度きりです。アプリがすでに持つホスト名は拒みます。Azure に証明書を発行させるのは束ねで あり、それは発行を待ちます。そのうえで、この順にホストを確かめます。

Terminal window
curl -sf https://<hostname>/health | jq -e '.status == "ok"'
curl -sf https://<hostname>/ | grep -q '<html'
curl -sf https://<hostname>/en/deploy/ | grep -q '<html'
curl -s -o /dev/null -w '%{http_code}\n' "https://$(terraform output -raw app_fqdn)/health"

最後のものは 200 です。イングレスの FQDN がオリジンであり、それは公開だからです。続いてコンテナを 一度温め、実ホストを叩く 2 つのスイートを走らせます。

Terminal window
curl -sf https://<hostname>/health >/dev/null
just test::bdd::deployed https://<hostname>
just frontend::e2e-deployed https://<hostname>

この温めは儀式ではありません。下の節を見てください。最後にスイッチを有効にしてもう一度適用します。 鍵は環境で渡し、リポジトリのファイルからは決して渡しません。

Terminal window
TF_VAR_azure_openai_api_key=<key> terraform apply

terraform.tfvars には enable_azure_openai = true、エンドポイント、デプロイメント、次元、 alert_emails を設定します。スイッチをどちらの向きに切り替えても、コンテナの環境が変わるので 新しいリビジョンになります。

min_instances は既定で 0、検証で 0 か 1 に限られます。max_replicas はどちらの場合も 1 です。 0 はアイドルの間に何も費やしません。1 はレプリカを 1 つ温めたままにし、24 時間課金されます。

0 に代償がないわけではありません。コールドスタートはイメージの pull とエンジンの import であり、 起動プローブはそれに 300 秒を与えますが、Container Apps のイングレスはリクエストを 240 秒で 打ち切ります。したがってアイドルの後の最初のリクエストはイングレスで失敗することが想定され、 1 分後のリクエストは、それが起こしたレプリカに対して成功します。スクリプトから呼ぶクライアントは その最初の失敗を許容しなければならず、許容できないなら min_instances = 1 がその設定です。

コールドスタートは実際のデプロイではまだ計測していません。最初のデプロイの後にここへ数字を 書き入れてください。

  • レコードは DNS のみのままでなければならない。 Azure がマネージド証明書を発行し更新するのは、 アプリの FQDN を直接解決する CNAME に対してだけで、しかも DigiCert がアプリに到達できる間 だけです。プロキシを有効にしたり、イングレスに許可リストを加えたりすると、次のリクエストでは なく次の更新が壊れるので、失敗は数か月後にやってきます。
  • 束ねのステップは DNS をランナーのリゾルバーで読む。 実行の直前に作ったレコードはそこには まだ無いことがあり、そのときステップは、レコードが存在しないかのようにそれらを印字して失敗します。 1 分待ってもう一度起動してください。編集するものはありません。
  • ホスト名を束ねるのは Terraform ではなく CLI。 スタックはカスタムドメインのリソースも DNS の レコードも持ちません。プロバイダーにマネージド証明書のリソースがなく、レコードは運用者のもの だからです。環境の既定の証明書で応えるホスト名は、束ねがまだ走っていないということです。
  • ロールの割り当ては apply より遅いことがある。 Container App は AcrPull の付与の後に 順序づけられていますが、Entra のロール割り当ての伝播には時間がかかるので、最初のリビジョンが それでも pull に失敗しうる。apply をやり直すか、リビジョンを再起動してください。
  • Consumption は CPU とメモリを固定の組で売り、上限は 2 つある。 ONNX のグラフ、fastembed の モデル、OCR のエンジンが常駐したときに一緒に必要とする下限である 8 GiB が、container_cpu を 4 に 押し上げます。Consumption だけの環境は 2 vCPU / 4 GiB の組で止まり、最初のデプロイはそれを 400 から学びました。Consumption のワークロードプロファイルは 4 / 8 まで行き、それでもゼロにスケール するので、環境はそのプロファイルを宣言します。プロファイルなしで作った環境は後から得ることが できず、作り直すしかありません。2 / 4 に落とすとは両方の数字を落とすことであり、実トラフィックでの リビジョンのワーキングセットを読んでからにしてください。
  • ゼロからのスケールアウトは毎回イメージ全体を pull する。 イメージは重みを運ぶので、 コールドスタートは Python の最初の 1 行が走る前に数ギガバイトを動かします。 min_instances = 1 が買い取るのはその費用です。
  • 予算が見ているのはリソースグループであってモデルではない。 数字を信じる前に上のスコープを 読んでください。Azure OpenAI のアカウントがこのリソースグループにいない限り、予算が測るのは Container App 自身の費用です。