← All guides AI Release
RU Subscribe to @ai_release1

How to set up AI-powered cloud cost monitoring: AWS, Azure, and GCP

Updated: 22.10.2026 · AI Release · @ai_release1
AI Release

TL;DR

- AWS CLI v2, authenticated profile with billing access

- Azure CLI (az) logged in to the target subscription

- Google Cloud CLI (gcloud) authenticated

- jq 1.6+ for JSON parsing

- Admin rights on each billing account (AWS: budgets:* + ce:*; Azure: Cost Management Contributor; GCP: Billing Account Costs Manager)

- A notification address: email alias or Slack/Telegram webhook

Values in angle brackets (<ACCOUNT_ID>, <BUDGET_AMOUNT>) are placeholders. Anything not stated here — API flag behaviour, quota limits, currency rules — check with the provider documentation before rollout.

Requirements and setup

Illustration: Requirements and setup
Illustration: Requirements and setup

Example: check identity and permissions before writing any budget.

aws sts get-caller-identity
az account show --query "{sub:name, id:id}" -o table
gcloud config get-value project

Expected output: an AWS account number, a one-line Azure table with the subscription name, and a GCP project ID. If any command errors, stop and fix authentication first — a budget created under the wrong account is silently useless.

Why this works: every later command reads credentials from the CLI's own credential chain; verifying identity up front turns a confusing AccessDenied later into a five-second fix now.

Step 1. Define a shared tagging and budget schema

What to do: agree on one tag key, one currency, and one budget period across all three clouds before creating anything.

CloudWhere you set itExample valueGrouping key used later
AWSCost allocation tags → activate `team``ml-platform``user:team$ml-platform`
AzureResource tag `team``ml-platform`tag filter in Cost analysis
GCPResource label `team``ml-platform`label in BigQuery export

Rules to pin down in writing:

1. Tag key: lowercase team, no spaces.

2. Budget period: MONTHLY everywhere — mixing weekly and monthly budgets makes the digest incomparable.

3. Budget amount per account: <BUDGET_AMOUNT> in the account's billing currency.

4. Alert thresholds: 50%, 80%, 100% of the budget.

Why: cost reports only aggregate by tag *after* the tag is activated in billing. In AWS a tag on a resource is invisible to Cost Explorer until you activate it as a cost allocation tag in the Billing console (Billing and Cost Management → Cost allocation tags). Azure and GCP apply tags immediately, which makes AWS the usual source of "my report is empty" tickets.

How to check: confirm at least one tagged resource appears in the cost grouping.

aws ce get-cost-and-usage \
  --time-period Start=$(date -u -d '7 days ago' +%Y-%m-%d),End=$(date -u +%Y-%m-%d) \
  --granularity DAILY \
  --metrics UnblendedCost \
  --group-by Type=TAG,Key=team \
  --output json | jq -r '.ResultsByTime[].Groups[] | "\(.Keys[0])\t\(.Metrics.UnblendedCost.Amount)"'

Expected: lines like team$ml-platform 12.34. An empty Groups array means the tag is not activated yet, or no spend occurred in that window.

If your agents also call paid model APIs and you need hard caps per agent rather than per cloud account, the per-agent ceiling pattern is described in How to Set Spending Limits and Budgets for AI Agents — run both layers, cloud budgets and agent budgets, because cloud budgets see infrastructure while agent budgets see tokens.

Step 2. Deploy native budgets and anomaly detection in AWS, Azure and GCP

Illustration: Step 2. Deploy native budgets and anomaly detection in AWS, Azure and GCP
Illustration: Step 2. Deploy native budgets and anomaly detection in AWS, Azure and GCP

What to do: create one budget per cloud with percentage thresholds, plus an anomaly detector where the provider offers one. AWS and Azure support anomaly detection natively; GCP budgets are threshold-based only.

AWS: budget file plus Cost Anomaly Detection

Create budget.json:

{
  "BudgetName": "ai-monthly-budget",
  "BudgetLimit": { "Amount": "<BUDGET_AMOUNT>", "Unit": "USD" },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST",
  "CostFilters": { "TagKeyValue": ["user:team$ml-platform"] },
  "CostTypes": { "IncludeCredit": false, "IncludeRefund": false }
}

Create notifications.json:

[
  {
    "Notification": {
      "NotificationType": "ACTUAL",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 80,
      "ThresholdType": "PERCENTAGE"
    },
    "Subscribers": [
      { "SubscriptionType": "EMAIL", "Address": "finops@example.com" }
    ]
  }
]

Apply both:

aws budgets create-budget \
  --account-id <ACCOUNT_ID> \
  --budget file://budget.json \
  --notifications-with-subscribers file://notifications.json

Then add an anomaly monitor scoped to service level — this catches a single runaway service before the monthly budget threshold is hit:

aws ce create-anomaly-monitor --anomaly-monitor \
  '{"MonitorName":"ai-services","MonitorType":"DIMENSIONAL","MonitorDimension":"SERVICE"}'

aws ce create-anomaly-subscription --anomaly-subscription \
  '{"SubscriptionName":"ai-anomalies","MonitorArnList":["<MONITOR_ARN>"],"Subscribers":[{"Type":"EMAIL","Address":"finops@example.com"}],"Threshold":100,"Frequency":"DAILY"}'

The Threshold here is an absolute currency amount, not a percentage — the monitor fires when impact exceeds it.

Azure: subscription budget via CLI

az consumption budget create \
  --budget-name ai-monthly-budget \
  --amount <BUDGET_AMOUNT> \
  --category cost \
  --time-grain Monthly \
  --start-date <YYYY-MM-01> \
  --end-date <YYYY-MM-DD> \
  --notifications '[{"enabled":true,"operator":"GreaterThan","threshold":80,"contactEmails":["finops@example.com"]}]'

If your CLI build rejects --notifications, create the budget with the command minus that flag, then add the alert in Cost Management → Budgets → ai-monthly-budget → Alert conditions. Flag availability varies by CLI release — check with the documentation.

GCP: budget with a Pub/Sub topic for programmatic alerts

gcloud pubsub topics create budget-alerts

gcloud billing budgets create \
  --billing-account=<BILLING_ACCOUNT_ID> \
  --display-name="ai-monthly-budget" \
  --budget-amount=<BUDGET_AMOUNT> \
  --threshold-rule=percent=0.5 \
  --threshold-rule=percent=0.9 \
  --threshold-rule=percent=1.0 \
  --all-updates-rule-pubsub-topic=projects/<PROJECT_ID>/topics/budget-alerts

Why: all three providers evaluate budgets against *actual* spend for the current period and can also warn on *forecast* spend. Routing GCP alerts to Pub/Sub instead of email lets you forward them into the same channel as the AWS and Azure alerts, so a single on-call rotation sees all three clouds.

How to check: list what you created.

aws budgets describe-budgets --account-id <ACCOUNT_ID> --output table
az consumption budget list --query "[].{name:name, amount:amount}" -o table
gcloud billing budgets list --billing-account=<BILLING_ACCOUNT_ID>

Expected: ai-monthly-budget in each output with your amount. For AWS also run aws ce get-anomaly-monitors and confirm ai-services has "Status": "ACTIVE"; a monitor created in the last 24 hours shows limited history and will not alert until it has baseline data.

Step 3. Verify alerts and ship a daily cost digest

Illustration: Step 3. Verify alerts and ship a daily cost digest
Illustration: Step 3. Verify alerts and ship a daily cost digest

What to do: prove the alert path end-to-end, then automate a daily top-services report.

1. Confirm the email subscriber in AWS is confirmed — an unconfirmed SNS email subscription swallows every alert.

2. Publish a test message to the GCP topic and confirm it reaches your forwarder.

3. Install the digest script below as a cron job (0 7 * * *).

#!/usr/bin/env bash
set -euo pipefail
START=$(date -u -d 'yesterday' +%Y-%m-%d)
END=$(date -u +%Y-%m-%d)

aws ce get-cost-and-usage \
  --time-period Start="$START",End="$END" \
  --granularity DAILY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --output json \
| jq -r '.ResultsByTime[].Groups[]
    | "\(.Keys[0])\t\(.Metrics.UnblendedCost.Amount)"' \
| sort -k2 -nr | head -5

Example output for a single day, sorted by spend:

Amazon SageMaker	41.87
Amazon Elastic Compute Cloud - Compute	22.10
Amazon Bedrock	9.42
Amazon Simple Storage Service	1.03
AWS Key Management Service	0.14

The Unit field is returned per group; check it once with --output json | jq '.ResultsByTime[0].Groups[0].Metrics' so you are not comparing USD against another currency.

Why: a monthly budget tells you that you overspent; a daily top-five list tells you *which service* started the overspend, usually a day earlier. Shipping that list to a chat channel closes the loop — the same pattern used for server monitoring in How to Set Up VPS Monitoring via a Telegram Bot with Mini App, where the bot just forwards a formatted daily summary and a small web UI for history.

How to check: run the script manually and compare its total against the console.

aws ce get-cost-and-usage \
  --time-period Start="$START",End="$END" \
  --granularity DAILY --metrics UnblendedCost \
  --output json | jq '[.ResultsByTime[].Total.UnblendedCost.Amount | tonumber] | add'

Expected: a single number matching the sum of your top-five lines plus everything below the cutoff. If the console shows a different figure, the usual cause is a different date range or a currency setting — both are visible in the Cost Explorer header.

Troubleshooting

Problem. AWS refuses to create the budget from the CLI.

aws iam attach-user-policy --user-name <USER> --policy-arn arn:aws:iam::aws:policy/AWSBudgetsActionsWithAWSResourceControlAccess

Verify with aws budgets describe-budgets --account-id <ACCOUNT_ID>.

Problem. Azure budget creation fails with an authorization error.

az role assignment create \
  --assignee <CLIENT_ID> \
  --role "Cost Management Contributor" \
  --scope /subscriptions/<SUBSCRIPTION_ID>

The role assignment can take a few minutes to propagate; az consumption budget list returning an empty list after a successful command usually means propagation is still in flight.

Problem. AWS budget alerts never arrive.

aws sns subscribe \
  --topic-arn <TOPIC_ARN> \
  --protocol email \
  --notification-endpoint finops@example.com

Re-check with aws sns list-subscriptions-by-topic --topic-arn <TOPIC_ARN>; the status must read SubscriptionArn with a full ARN, not PendingConfirmation.

Summary and next steps

You now have a monthly budget with percentage thresholds in AWS, Azure and GCP, an AWS anomaly monitor for per-service spikes, and a daily top-five cost digest you can route into chat. Next, activate the team tag as a cost allocation tag in every account and add a second budget per team so the digest can be split by owner.

FAQ

How do I monitor cloud costs per team instead of per account?

Attach the same tag key (team) to resources in all three clouds, activate it as a cost allocation tag in AWS Billing, then group by Type=TAG,Key=team in Cost Explorer, by tag in Azure Cost analysis, and by label in the GCP BigQuery billing export.

Why is my AWS budget always showing zero spend?

Usually the CostFilters tag key is not activated for cost allocation, or the tag value in budget.json does not match the resource exactly (user:team$ml-platform is case-sensitive). Verify with aws ce get-cost-and-usage --group-by Type=TAG,Key=team.

Do budget alerts cost anything?

AWS Budgets allows a limited number of budgets and alert evaluations per account at no charge, with additional ones billed — the current allowance and pricing check with the AWS documentation. Azure budgets and GCP budgets are configuration objects on the billing account; GCP charges for the Pub/Sub messages and BigQuery storage the alerts produce.

Can I get alerts in Slack or Telegram instead of email?

Yes for GCP: point --all-updates-rule-pubsub-topic at a topic with a push subscription to your handler. For AWS, add a Lambda subscriber to the budget's SNS topic. For Azure, forward action-group notifications to a webhook. The formatting and forwarding pattern is the same one used in the Telegram monitoring bot described above.

🤖 AI summary