0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Foundry + アラート + Logic Apps を月間 Token 超過で自動 Pause

0
Last updated at Posted at 2026-05-24

本記事記述および実装内容は、多くの部分をAIで処理しており、人間チェックは甘いです

結論

Azure AI Foundry / Azure AI Services の model deployment は、2026-01-15-preview の Pause API を Logic Apps Consumption から自動キックできました。

検知レイヤーには Cognitive Services の Platform Metric TokenTransaction を対象にした Metric Alert を使い、Logic App 側で Log Analytics の Query API を叩いて当月累計を再計算 してからしきい値を超えた deployment だけ Pause する 2 段階構成にしました。検証では burn 完了から Pause 完了まで end-to-end 約 6 分 18 秒 を実測しています。

TL;DR

  • 検知: Metric Alert (Microsoft.Insights/metricAlerts, TokenTransaction Sum > 0 over PT5M, evaluationFrequency PT1M, dimension ModelDeploymentName) で deployment 別に発火させる
  • 処理: Logic App Consumption が Action Group 経由で起動し、当月累計 token を Log Analytics Query API で再計算 → しきい値超過なら ARM の Pause API (2026-01-15-preview) を Managed Identity で叩く
  • Logic App は condition.allOf[0].dimensions[0].value から deploymentName を抽出(空文字 fallback あり、coalesce で 2 段)し、accountResourceId は Bicep parameter への static fallback を持たせる設計にした
  • 実測: burn 完了 → Alert Fired まで 約 6 分 15 秒、Logic App run 約 1.49 秒、Pause API 200 + deploymentState=Paused
  • Pause 後の推論は HTTP 423 PausedDeployment で拒否され、Resume API で再開できた
  • セキュリティ補足: Action Group → Logic App は SAS callback URL (Managed Identity 非対応) なので production では Custom Webhook (webhookReceivers + useAadAuth: true) への置換を推奨

はじめに / 背景

Azure AI Foundry や Azure OpenAI 系の model deployment は、検証中でも意図せず token を消費し続けることがあります。開発環境では「一定量を超えたら人間に通知する」だけでなく、「一旦止める」仕組みがあると安心です。

この記事では、月間 token 上限を超えたら Logic Apps から Foundry deployment を Pause する構成を検証したときの実装メモと、Logic App workflow の中身、実測で見えた Azure Monitor / preview API の挙動をまとめます。

対象読者は、Azure AI Foundry / Azure OpenAI のコストガードレールを IaC で作りたい Azure エンジニアです。

全体アーキテクチャ

image.png

構成要素は次の通りです。

レイヤー 役割
Foundry / AI Services gpt-5-mini などの deployment が推論 token を消費する
Diagnostic Settings AI Services の metrics / logs を Log Analytics Workspace へ送る(Logic App の月次再クエリ用)
Metric Alert Cognitive Services Platform metric TokenTransactionSum > 0 で監視(PT5M window, PT1M 評価、ModelDeploymentName で dimension splitting)
Action Group Common Alert Schema webhook で Logic App を起動し、通知メールも送る
Logic Apps Consumption 当月累計 token を再計算し、しきい値超過時だけ Pause API を呼ぶ
Managed Identity Logic App から Log Analytics Query API と ARM Pause API を呼ぶ

2 段階アラート設計の意図

Metric Alert と Logic App で 役割をきっぱり分離 しています。

段階 やること 担当
① 短窓検知 「最近 token が動いた」を 1 分粒度で検知 Metric Alert (TokenTransaction Sum > 0 over PT5M)
② 累計判定 当月累計が threshold を超えたら Pause Logic App から LAW Query API を呼んで再計算

Metric Alert 単独で「当月累計」を判定しないのは、Platform metric は 直近 N 分 の窓しか持てない(Metric Alert の windowSize は最大 PT1D、それを超える集計は不可)からです。逆に Logic App 単独で 1 分粒度ポーリングするとコスト・運用コストが見合いません。

そこで Metric Alert は "動きがあったら Logic App を叩け" という gateLogic App は "本当に超過しているか確かめてから Pause" という 2 役にしています。

実装ハイライト

Metric Alert(Bicep)

modules/alert-rule.bicep 抜粋。scope を AI Services account に向け、ModelDeploymentName dimension で deployment 別に発火させます。

metricName: 'TokenTransaction'にしているが、これはOpenAI専用。TotalTokensにするとAnthoropicなどでも使えるので、そちらにすべき

modules/alert-rule.bicep
resource alert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
  name: alertName
  location: 'global'
  properties: {
    severity: 2
    enabled: true
    scopes: [accountResourceId]
    targetResourceType: 'Microsoft.CognitiveServices/accounts'
    targetResourceRegion: location
    evaluationFrequency: 'PT1M'
    windowSize: 'PT5M'
    criteria: {
      'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
      allOf: [
        {
          name: 'TokenBurnDetected'
          criterionType: 'StaticThresholdCriterion'
          metricName: 'TokenTransaction'
          metricNamespace: 'Microsoft.CognitiveServices/accounts'
          timeAggregation: 'Total'
          operator: 'GreaterThan'
          threshold: 0
          dimensions: [
            {
              name: 'ModelDeploymentName'
              operator: 'Include'
              values: [deploymentName]
            }
          ]
        }
      ]
    }
    actions: [{ actionGroupId: actionGroupId }]
  }
}

ポイントは次の 3 点です。

  • metricNamespace: 'Microsoft.CognitiveServices/accounts' を明示しないと、TokenTransaction の解決でハマることがあります
  • dimensions[].operator: 'Include' + values: [deploymentName] で 1 deployment に絞れますが、['*'] にすると全 deployment の splitting alert になります
  • windowSizePT5M 未満にすると、Platform metric の集計遅延に負けて false negative が増えます

Foundry - 警告

Foundry 画面。右の「ユーザーの応答」で完了などステータスを変更可能。
image.png

アラートルール

条件

image.png

アクション

image.png

詳細

image.png

アクショングループ

image.png

Logic App ワークフロー全体の流れ

Logic App (Consumption) は次の順で動きます。coalesce で payload schema の差分を吸収しつつ、最終的に accountResourceId は Bicep parameter にハードコードしたものへ fallback できる作りです。

image.png

各 action でやっていることを 1 つずつ説明します。

Trigger: HTTP Request

Action Group から Common Alert Schema を POST で受け取ります。schema は最小限 (schemaIddata のみ) を宣言しています。trigger 自体は callback URL (SAS) ベースなので、Action Group には Logic App の listCallbackURL() の結果を渡しています(この方式のセキュリティ上の注意は後述の「セキュリティ」セクション参照)。

Init_deploymentName

alert payload から deploymentName を取り出します。Metric Alert (Common Alert Schema) では condition.allOf[0].dimensions[0].value に dimension splitting 結果の deployment 名が必ず入る ので、そこを 1 段目に置き、payload 異常時に Logic App 全体を落とさないための空文字を 2 段目に置く、というシンプルな 2 段 coalesce にしています。

init-deployment-name.expression
coalesce(
  first(first(triggerBody()?.data?.alertContext?.condition?.allOf)?.dimensions)?.value,
  ''
)

空文字 fallback を入れているのは、後段 Check_threshold で長さチェックして安全に skip するためです(パース失敗で Pause API が malformed URL を叩くのを防ぐ目的)。

補足: 検証当初は alertContext.DimensionsalertContext.SearchResults.tables[0].rows[0][1] も coalesce 経路に含めた 3 段構成にしていましたが、Metric Alert の Common Alert Schema では実際にヒットするのは condition.allOf[0].dimensions だけだったため、最終版では 2 段に整理しています。他の alert schema(Log Alerts V2 など)と相互運用したい場合は、その候補を 1 段足してください。

Init_accountResourceId

Pause URL を組み立てるためには AI Services account の Resource ID が必要です。Log Alerts V2 の SearchResults 行があれば 1 列目から取れますが、Metric Alert payload には SearchResults がありません。そのため Bicep parameter として渡した accountResourceId (defaultValue でハードコード) に必ず fallback できる作りにしています。

init-account-resource-id.expression
coalesce(
  triggerBody()?.data?.alertContext?.SearchResults?.tables[0]?.rows[0][0],
  parameters('accountResourceId')
)

Query_monthly_tokens

Log Analytics Query API を Managed Identity (audience: https://api.loganalytics.io)POST します。3 回 / 30 秒間隔の retry policy 付き。

query-monthly-tokens.json
{
  "method": "POST",
  "uri": "https://api.loganalytics.io/v1/workspaces/<WORKSPACE_CUSTOMER_ID>/query",
  "headers": { "Content-Type": "application/json" },
  "body": {
    "query": "AzureMetrics | where ResourceProvider =~ \"MICROSOFT.COGNITIVESERVICES\" | where MetricName == \"TokenTransaction\" | where TimeGenerated >= startofmonth(now()) | where _ResourceId =~ \"<ACCOUNT_RESOURCE_ID>\" | summarize MonthlyTokens = sum(Total)"
  },
  "authentication": { "type": "ManagedServiceIdentity", "audience": "https://api.loganalytics.io" },
  "retryPolicy": { "type": "fixed", "count": 3, "interval": "PT30S" }
}

実測では AzureMetricsV2 ではなく AzureMetrics 側に Cognitive Services の token 行が入りました(後述)。

Parse_monthly_tokens

LAW のレスポンスは tables[0].rows[0][0] に集計値 1 つだけが入ります。coalesce(..., 0) で空のときも 0 に倒します。

parse-monthly-tokens.expression
coalesce(body('Query_monthly_tokens')?.tables[0]?.rows[0][0], 0)

Check_threshold

3 条件の AND を取って、本当に Pause すべきか判定します。

条件 意味
int(outputs('Parse_monthly_tokens')) > parameters('monthlyTokenThreshold') 当月累計がしきい値を超えている
length(variables('deploymentName')) > 0 deployment 名が解決できている
length(variables('accountResourceId')) > 0 account の Resource ID が解決できている

deploymentNameaccountResourceId の長さチェックを入れているのは、payload 解析が失敗したまま誤って空文字で Pause URL を組み立てるのを防ぐためです。

Compose_pauseUri(true 分岐)

accountResourceId の末尾スラッシュを除去してから Pause URL を組み立てます。

compose-pause-uri.expression
concat(
  'https://management.azure.com',
  if(endsWith(variables('accountResourceId'), '/'),
     substring(variables('accountResourceId'), 0, sub(length(variables('accountResourceId')), 1)),
     variables('accountResourceId')),
  '/deployments/', encodeUriComponent(variables('deploymentName')),
  '/pause?api-version=', parameters('pauseApiVersion')
)

Pause_deployment(true 分岐)

組み立てた URL に Managed Identity (audience: https://management.azure.com) で空ボディ POST します。こちらも 3 回 / 30 秒 retry 付き。

pause-deployment.json
{
  "method": "POST",
  "uri": "<computed pause uri>",
  "headers": { "Content-Type": "application/json" },
  "body": {},
  "authentication": { "type": "ManagedServiceIdentity", "audience": "https://management.azure.com" },
  "retryPolicy": { "type": "fixed", "count": 3, "interval": "PT30S" }
}

Log_below_threshold(false 分岐)

しきい値を超えていない場合は Pause せず、Compose に診断ログだけ残します。

log-below-threshold.expression
concat('Below threshold; not pausing. MonthlyTokens=',
       string(outputs('Parse_monthly_tokens')),
       ' Deployment=', variables('deploymentName'))

run history の Compose を見ればなぜ Pause しなかったかが追えるので、検証フェーズで便利でした。

Response

最後に HTTP 200 を返します。body には monitoring 用に主要な変数を入れています。Action Group の webhook receiver は 200 を期待するので、Pause したかどうかに関わらず必ず Response を返します。

response.json
{
  "statusCode": 200,
  "body": {
    "monthlyTokens": "@outputs('Parse_monthly_tokens')",
    "deploymentName": "@variables('deploymentName')",
    "accountResourceId": "@variables('accountResourceId')",
    "threshold": "@parameters('monthlyTokenThreshold')"
  }
}

Logic App の Managed Identity に必要な RBAC

rbac.sh
# Log Analytics Reader on the workspace
az role assignment create \
  --role "Log Analytics Reader" \
  --assignee <logic-app-principal-id> \
  --scope <workspace-resource-id>

# Cognitive Services Contributor on the AI Services account (Pause/Resume を含む)
az role assignment create \
  --role "Cognitive Services Contributor" \
  --assignee <logic-app-principal-id> \
  --scope <ai-services-account-resource-id>

Pause/Resume は dataAction ではなく ARM control plane の action なので、Cognitive Services Contributor で動きました。Contributor の他、最小権限を狙うなら Microsoft.CognitiveServices/accounts/deployments/pause/action.../resume/action を含むカスタムロールでも代用可能なはずです。

実測してわかったこと

ここが今回いちばん重要な学びです。

1. AzureMetricsV2 ではなく AzureMetrics に出た

当初は AzureMetricsV2 を見に行きましたが、Cognitive Services の token 行は取得できませんでした。また、手元の workspace では AzureMetricsV2 schema に MetricNamespace カラムがありませんでした。

実際に token metrics が見えたのは AzureMetrics で、観測できた MetricName は次の 4 種です。

MetricName 意味合い 採用有無
TokenTransaction request ごとの token transaction ✅ Metric Alert / monthly recalc に採用
TotalTokens total tokens 相当 参考値
InputTokens prompt tokens 相当 参考値
OutputTokens completion tokens 相当 参考値

MetricNamespaceModelDeploymentName のような列が常にある前提で KQL を書くと、workspace / table 差分で semantic error になります。検証開始時に getschema と sample query を残すのがおすすめです。

2. Metric Alert payload の dimensions 位置

Common Alert Schema (Metric Alert) の場合、deployment 名は次の path に来ました。

metric-alert-payload-path.txt
data.alertContext.condition.allOf[0].dimensions[0].value

Logic App の Init_deploymentNamecoalesce2 番目の候補 を引いている path がこれです。

Metric Alert payload には SearchResults 概念が無いので、accountResourceId は payload からは取れません。Logic App 側で Bicep parameter accountResourceId (defaultValue でハードコード) に fallback できる作りにしておく必要があります。

payload の最小イメージ
metric-alert-payload-shape.json
{
  "schemaId": "AzureMonitorCommonAlertSchema",
  "data": {
    "alertContext": {
      "condition": {
        "allOf": [
          {
            "dimensions": [
              {
                "name": "ModelDeploymentName",
                "value": "gpt-5-mini"
              }
            ]
          }
        ]
      }
    }
  }
}

3. deploymentState は preview REST GET で確認する

stable な az cognitiveservices account deployment show では、deploymentStatenull になることがありました。

Pause / Resume の状態確認は、当面 2026-01-15-preview の REST GET を正とするのが安全です。今回も preview REST GET で Paused / Running を確認しました。

check-state.sh
az rest \
  --method get \
  --url "https://management.azure.com/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/rg-<env>-japaneast/providers/Microsoft.CognitiveServices/accounts/aiservices-<env>-<suffix>/deployments/gpt-5-mini?api-version=2026-01-15-preview" \
  --query "properties.deploymentState" \
  --output tsv

4. triggeredAlerts endpoint は対応外

triggeredAlerts REST endpoint は、今回構成した Metric Alert に対して期待した結果が返らなかったため、alert 発火の evidence は Logic App run history と action details を正としました。

検証結果サマリ

ID シナリオ 結果 確認内容
S1 Alert firing and Logic App run succeeded ✅ PASS token 消費後に Metric Alert が発火し、Logic App run が Succeeded
S2 Pause API 200 and deploymentState Paused ✅ PASS monthly tokens がしきい値を超え、Pause API が HTTP 200。preview GET で Paused
S3 Paused deployment rejects inference ✅ PASS Pause 後の推論が HTTP 423 PausedDeployment で拒否
S4 Resume API restores inference ✅ PASS Resume API が HTTP 200。preview GET で Running、推論も再開

実測値としては、Metric Alert 発火から Logic App 実行、Pause API 呼び出しまで一連の流れを確認できました。コストは dev-safe 範囲内 (詳細は省略) です。

検知 latency の内訳

burn 完了から Pause 完了まで 約 6 分 18 秒 でした。内訳は次のとおりです。

区間 実測値 備考
① Cognitive Services → Platform metric への ingestion 約 1〜3 分 Platform metric の標準遅延
② Metric Alert の次回評価サイクル待ち 最悪 +1 分 evaluationFrequency: 'PT1M'
③ Alert Fired → Action Group → Logic App webhook 約 1.16 秒 callback URL POST
④ Logic App run 全体 (LAW Query + Pause API) 約 1.49 秒 Logic App run 履歴
合計 (今回実測) 約 6 分 18 秒 burn 完了 → Pause 完了

evaluationFrequencyalert 課金に影響しません(Metric Alert は Alerts Metric Monitored の signal/month 単位で課金されるため、評価頻度を変えても料金は変わらない)。PT5M に緩めると検知 latency の上振れが ±4 分になります。dev 環境のコストガードレールとしては PT1M を推奨します。

Resume REST API は POST body が空でも Content-Length: 0 ヘッダが必須 です。curl-d "" を付けないと HTTP 411 Length Required を返します。az rest は自動で付けるので問題ありません。

セキュリティ: Action Group → Logic App は SAS callback URL で叩く

検証完了後に「Action Group が Logic App を起動するときの認証はどうなっているのか?」と気になって調べたところ、Managed Identity ではなく signed callback URL (SAS) を使う方式 だと分かりました。dev 環境用なので即修正はしませんでしたが、本番運用する場合に意識すべき点なので書き残します。

仕組み

Microsoft.Insights/actionGroupslogicAppReceivers は、Logic App workflow の trigger に対する sig= SAS token 付きの callback URL を ARM に保存し、alert 発火時にこの URL に HTTPS POST します。

modules/action-group.bicep
logicAppReceivers: [
  {
    name: 'pause-deployment'
    resourceId: logicAppResourceId
    callbackUrl: logicAppCallbackUrl
    useCommonAlertSchema: true
  }
]

callbackUrl は Bicep の listCallbackURL('${logicApp.id}/triggers/manual', '2019-05-01').value で取得します。Logic App receiver の schema には useAadAuth 相当のプロパティが存在しない ため、AAD / Managed Identity 認証はできません(webhook receiver にはあります)。

つまり「URL を知っている人 = Logic App を実行できる人」という状態になります。

懸念点

# 懸念 影響度
1 callback URL の漏洩 = 実行権限の漏洩 中〜高
2 URL の保存場所が多い (Action Group 定義、ARM deployment history、azd .env、Bicep output)
3 Bicep module の output で SAS を返却 (outputs-should-not-contain-secrets warning)
4 実行される処理が強力(ARM Pause API を叩く)。URL 漏洩 + 連打で deployment を強制 Pause させ続ける DoS 的攻撃が成立しうる
5 Logic App access key の rotation が運用上重く、放置になりやすい

Bicep linter が outputs-should-not-contain-secrets warning を出していたのを「output しないと action-group module に渡せないし…」と放置していました。後述の対策で、main.bicep 内で listCallbackURL() を直接呼ぶ ことで module output から secret を消せます。Linter warning を放置しない、というのが今回の教訓のひとつです。

緩和要素(現実的なリスクは低めの理由)

  • callback URL の hostname (prod-XX.<region>.logic.azure.com) は subscription/region を知らないと外部から推測困難 (security-through-obscurity)
  • リポジトリに .env は commit していない(.gitignore 済み)
  • URL を読める Azure principal は既に Logic App Contributor 相当の権限を持つことが多く、追加リスクが小さい

改善案(費用対効果順)

優先度 改善 実装難度
HIGH Bicep module の output から logicAppCallbackUrl を削除し、main.bicep 内で listCallbackURL() を直接呼んで action-group に渡す
HIGH Logic App 側で payload の alertTargetIDs / accountResourceId を validate し、想定外の deployment は no-op で 200 OK 返却
MED Logic App の accessControl.triggers.allowedCallerIpAddresses で外部 IP 制限
MED Logic App access key の定期 rotation を自動化
LOW logicAppReceivers を捨てて webhookReceivers + Function App (useAadAuth: true) に置き換える

Custom Webhook (webhookReceivers) との比較

観点 logicAppReceivers (今回採用) webhookReceivers (custom webhook)
認証方式 sig= SAS callback URL のみ AAD Bearer token (useAadAuth: true) または無認証
Managed Identity ❌ 不可 ✅ Action Group の System-assigned Identity で取得
呼び先 Logic App (Consumption) 限定 任意の HTTPS endpoint (Function App, App Service, APIM, SaaS など)
実装コスト 低 (Logic App workflow 1 個) 中 (Function App + audience 検証コード + Managed Identity)

production 相当の環境で再利用する場合は、Function App + webhook receiver + AAD 認証 への置き換えを推奨します。dev 環境のコストガードレールとしては、上記 HIGH の 2 件 (Bicep output 削除、payload validation) で十分実用に耐えると判断しました。

参考: Configure authentication for a secure webhook

コスト構成・変動要素・算出例

Azure Retail Prices API から japaneast の公開リテール価格を取得して整理しました
(USD、EA / CSP 割引未考慮、2026-05 時点)。

主要メーターと無料枠

リソース meter 無料枠 超過後
Metric Alert Alerts Metric Monitored 10 signals / 月 $0.10 / signal / 月
Action Group: Email Email Notification 1,000 通 / 月 $0.000002 / email
Action Group: Webhook (= Logic App) Webhook 100,000 / 月 $0.0000006 / call
Logic Apps Consumption — Built-in Built-in Actions 4,000 actions / 月 $0.000028 / action
Log Analytics (PerGB2018) Data Ingestion 5 GB / 日(≒ 150 GB / 月) $3.34 / GB
LAW Retention Retention 31 日 $0.15 / GB / 月
(参考)gpt-5-mini Global Standard Foundry Models n/a Input $0.25 / 1M、Output $2.00 / 1M

本検証構成(1 alert / 1 Logic App / Email + Webhook receiver / Metrics ingest のみ)は 全項目が無料枠に収まる ため、月額コストは実質 $0 です。Logic Apps を Consumption(Standard ではない)にしたことが効いています。

コスト変動要素

パラメータ 直接影響する課金項目 効果
alert rule の数(= deployment 数 N) Metric Alert signal 線形 N。先頭 10 個まで無料、11 個目から $0.10 / 月
alert 発火頻度 Logic Apps built-in / LAW Query run 数 × ~9 actions/run。月 4,000 actions(≒ 444 run)まで無料
evaluationFrequency (PT1M vs PT5M) (signal/month 単位なので)直接の影響は無し 発火タイミングの早さが変わるだけ
Diagnostic Settings の対象(AllMetrics / AllLogs / category 限定) LAW Ingestion metrics のみなら数 MB / 月。AllLogs を有効化すると GB 単位に増加
LAW retention 日数 LAW Retention 31 日超で線形
通知チャネル Action Group Email / Webhook はほぼ無料、SMS / Voice / Push は per-message 課金
Pause 完了までの latency (= gpt-5-mini の token 課金削減量) 真の節約効果はここ。Pause が早いほど無駄 token を抑えられる
算出例 3 パターン(dev 検証 / 小規模 prod / 中規模 prod)

ケース A: 本検証構成(1 deployment、月 10 alert 発火、軽量 ingest)

項目 計算 月額 USD
Metric Alert signal 1 → 無料枠 (≤ 10) $0.00
Action Group: Email 10 → 無料枠 $0.00
Action Group: Webhook 10 → 無料枠 $0.00
Logic Apps built-in 10 run × 9 = 90 → 無料枠 (≤ 4,000) $0.00
LAW Ingestion ~10 MB → 無料枠 $0.00
小計(アラート基盤) $0.00 / 月
(参考)gpt-5-mini 1,100 token 1,100 × $0.25 / 1M $0.0003

ケース B: 小規模 prod(5 deployment、月 1,000 alert、Metrics ingest のみ)

項目 計算 月額 USD
Metric Alert signal 5 → 無料枠 $0.00
Action Group: Email 1,000 → 無料枠 $0.00
Action Group: Webhook 1,000 → 無料枠 $0.00
Logic Apps built-in 9,000 → 5,000 課金 × $0.000028 $0.14
LAW Ingestion ~100 MB → 無料枠 $0.00
小計 ~$0.14 / 月

ケース C: 中規模 prod(20 deployment、月 10,000 alert、AllLogs 1 TB ingest、retention 90 日)

項目 計算 月額 USD
Metric Alert signal 10 課金 × $0.10 $1.00
Action Group: Email 9,000 課金 × $0.000002 $0.02
Action Group: Webhook 10,000 → 無料枠 $0.00
Logic Apps built-in 86,000 課金 × $0.000028 $2.41
LAW Ingestion (AllLogs) 850 GB × $3.34 $2,839
LAW Retention (90 日) ~1,967 GB·月 × $0.15 ~$295
小計 ~$3,138 / 月

ケース C では LAW Ingestion (AllLogs) が支配的で、アラート基盤そのもの (~$3.43) は誤差レベルです。コスト削減ポイントは Diagnostic Settings の category 絞り込み(AllMetrics のみ ON、AllLogs OFF)。

ボトムライン

  1. 本検証構成は実質 $0 / 月(無料枠に完全収納)
  2. deployment 数が 10 個を超えた瞬間に Metric Alert が課金開始 → 設計判断のしきい値
  3. コスト爆発の真の犯人は LAW の AllLogs 有効化SMS / Voice 通知 であり、アラート pipeline 自体は安い
  4. 真の節約効果は token 消費の抑止。アラート基盤のランニングコスト ($0〜$3) と引き換えに桁違いの token コスト爆発を防ぐ構造

複数 Deployment への拡張

検証は 1 deployment (gpt-5-mini) で完結しましたが、複数の deployment(例: gpt-5-mini + gpt-4o + text-embedding-3-large)を 1 つの AI Services account 上で運用する場合の構成判断を整理します。

2 つの構成オプション

観点 Option A: alert rule × N 本 Option B: 1 alert rule + dimension splitting
alert rule deployment 数 N 1 本
Action Group 1 本(共通) 1 本(共通)
Logic App 1 本(共通) 1 本(共通)
threshold rule ごとに個別設定可能 全 deployment で共通 1 値
Azure Monitor Alerts 画面 deployment ごとに Fired / Resolved run history を掘る必要がある
Metric Alert 課金 N signals(11 個目から $0.10 / 月) 1 signal(無料枠内)
IaC 行数 rule 定義が N 倍 一定

Logic App 側のループは不要

Metric Alert の dimension splitting を有効化すると、各 dimension instance(= 各 deployment)は 独立した alert として fire し、Action Group webhook も 1 fire = 1 call で Logic App に飛びます。よって:

  • 1 Logic App run = 1 deployment(並列に複数 run が走るだけ)
  • 本記事の 2 段 coalesce ロジックがそのまま使える
  • For ループは不要

For ループが必要になるのは「1 alert rule に複数の criteria を束ねた構成」「Log Search Alert で結果が複数行」「Azure Functions などで集約後に Logic App を呼ぶ構成」のいずれかに限られます。今回はどれにも該当しません。

Per-deployment 別 threshold が必要な場合

Metric Alert の thresholdrule 単位で 1 値しか持てません。splitting を有効化しても全 dimension で共通 threshold が適用されます。

  • 厳密に deployment ごとに別 threshold が要件 → Option A 推奨(rule 数 = N)
  • どうしても rule 数を抑えたい → Option B + Logic App 側で 再判定(Switch アクションで {deploymentName → threshold} の lookup map を持つ)。ただし alert 側 threshold は「最も緩い deployment の閾値」まで下げる必要があり、発火頻度が増える副作用があります

判断早見表

要件 推奨構成 Logic App の中身
全 deployment で同じ threshold で十分 Option B(1 alert + splitting / 1 AG / 1 LA) 現状の 2 段 coalesce のまま(for ループ無し)
deployment ごとに別 threshold(厳密値) Option A + 1 AG + 1 LA 現状ロジック(for ループ無し)
別 threshold + rule 数を抑制したい Option B + LA 内 lookup map Switch で分岐(for ループ不要)
1 alert rule に複数 criterion を相乗り Option B 拡張版 allOf[] を For each で走査

将来複数 deployment に拡張する際は Option A を第一候補 とすることを推奨します。Azure Monitor Alerts 画面で deployment ごとの fire / resolve 状態が可視化される運用メリットが、rule 数増加の認知負荷を上回るためです。

既知の課題 / 未確認事項

  • Pause / Resume API は 2026-01-15-preview のため、GA 時の API version、response schema、RBAC 要件は再確認が必要です。
  • AzureMetrics / AzureMetricsV2 の table 名、schema、dimension 出力は、リージョンやテナント、診断設定の状態で差分が出る可能性があります。
  • deployment 別の metrics dimension が出ない場合、複数 deployment を 1 account に載せる構成では別設計が必要です。
  • Logic App の payload schema 解析は Metric Alert に特化した 2 段 coalesce (condition.allOf[0].dimensions + 空文字 fallback) に整理してありますが、将来の alert schema 変更や他種 alert (Log Alerts V2 等) との相互運用が必要になったら候補を増やしてください。
  • alert 発火確認に triggeredAlerts endpoint を使えるとは限らないため、Logic App run history などの代替 evidence を用意する必要があります。

まとめ

Foundry deployment の token 使用量を監視し、月間上限を超えたら自動 Pause する構成は、Metric Alert + Logic Apps Consumption + Managed Identity で実装できました。

実装上の勘所は次の 3 つです。

  1. 検知は Metric Alert (TokenTransaction Sum > 0, dimension ModelDeploymentName)、累計判定は Logic App で LAW 再クエリ という 2 段階構成にする。Metric Alert 単独で月次累計はできない (windowSize 最大 PT1D) ため。
  2. Logic App は Metric Alert の condition.allOf[0].dimensions[0].value から deployment 名を取り出す 2 段 coalesce(空文字 fallback 付き)と、Bicep parameter accountResourceId への static fallback を必ず入れる。これで payload 異常時にも Pause API へ malformed URL を投げず安全に skip できる。
  3. Pause / Resume / deploymentState の確認は 2026-01-15-preview の REST API を正とする。stable CLI では deploymentState が null になることがある。

セキュリティ面では、Action Group → Logic App が SAS callback URL を使う方式で Managed Identity が使えない ことを検証後に認識しました。dev 環境としては許容しつつ、production 相当では Bicep output から SAS を消す + payload validation + 将来的に Custom Webhook (useAadAuth: true) への置き換え を推奨します(詳細は「セキュリティ」セクション)。

Preview API 依存ではありますが、開発環境のコスト暴走対策としては実用的なガードレールになりそうです。

補足: 当初は Scheduled Query Rule で実装していた

実は最初の実装では Scheduled Query Rule (SQR) で検知レイヤーを組んでいました(AzureMetrics | where MetricName == "TokenTransaction"PT5M 評価、PT1H window で監視)。検証中に「2 段階構成(短窓 trigger + Logic App での月次再計算)を採るのであれば、SQR の必然性はなく Metric Alert で十分」と気付き、本記事の Metric Alert 構成に差し替えました。

差し替えによる変化は次の通りです。

観点 v1 (Scheduled Query Rule) v2 (Metric Alert・本記事)
発火条件 KQL 静的 threshold + dimension splitting
dimension splitting KQL の extend DeploymentName で投影 metric dimension ModelDeploymentName で native splitting
Diagnostic Settings → LAW の AllMetrics 送信 必須 不要(Logic App 月次再クエリでは継続使用)
ingestion latency 約 3〜5 分 約 1〜3 分
end-to-end latency 実測 約 18.2 分 約 6 分 18 秒
Logic App workflow 定義 v1→v2 切替時は変更不要coalesce 第 2 候補 = Metric Alert payload の condition.allOf[0].dimensions[0].value と一致したため)。本記事の最終版では Metric Alert 専用に 2 段 coalesce に整理

差し替えた結果、end-to-end latency が 18.2 分 → 6 分 18 秒 (約 66% 短縮) されました。Logic App の workflow は v1→v2 切替時は無修正で動いたcoalesce 第 2 候補 = condition.allOf[0].dimensions[0].value が Metric Alert payload と完全一致したため)ので、当時は「coalesce で複数 schema 候補を並べる defensive parse の価値を再確認した経験」になりました。Metric Alert 専用に絞った最終版(本記事掲載分)では、その学びを踏まえて使われない候補は削除し 2 段 coalesce に整理しています。

「短窓 trigger + 長窓で再計算」を採るときは最初から Metric Alert を選ぶ、というのが今回の最大の学びです。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?