Skip to content

Azure Cost Monitoring im Admin-Portal

Das Admin-Portal zeigt auf /costs eine Uebersicht der aktuellen und prognostizierten Azure-Kosten. Datenquelle ist Azure Cost Management Query/Forecast API, abgefragt mit einem Service Principal (Rolle Cost Management Reader) aus dem Admin-Portal-Backend.

Datenfrische: Azure aggregiert Usage asynchron. Die Werte liegen typischerweise 8–24 h hinter der Realtime-Nutzung. Das Dashboard ist kein Live-Monitor, sondern „gestriger Stand + Forecast auf Monatsende".

Was das Dashboard anzeigt

  • Subscription Overview: Month-to-Date, Forecast Monatsende, Letzter Monat, Delta zum Vormonat — alles in der Sub-Default-Waehrung (EUR).
  • Pro Resource Group: MTD-Tabelle mit Vergleich zum Vormonat.
  • 30-Tage-Trend: Tagesgenauer Verlauf als SVG-Linienchart.
  • Service-Breakdown: MTD pro Azure-Service (App Service, Postgres, Storage, …), mit prozentualem Anteil.
  • CSV-Export: Download der letzten 30 Tage mit allen Spalten (Datum, RG, Service, Kosten, Waehrung).

Budget & Alerts

In der Subscription targetshot-core-0 ist seit 2026-04-23 ein Monatsbudget targetshot-monthly-200eur eingerichtet:

Threshold Art Empfaenger
50 % Actual [email protected]
75 % Actual [email protected]
90 % Actual [email protected]
100 % Forecasted [email protected]

Die Emails kommen direkt aus Azure — kein Umweg ueber Postmark, die Azure-eigenen Alerts waren ausreichend und sparen die zweite Integration. Aendern laesst sich das im Portal unter Cost Management + Billing → Budgets.

Setup (einmalig)

1. Service Principal anlegen (Portal)

az ad sp create-for-rbac braucht in Entra ID die Rolle Application Developer oder hoeher. Wenn dein CLI-Login nur Contributor auf der Subscription hat, geht der Weg ueber's Portal.

  1. Azure Portal → Microsoft Entra IDApp registrationsNew registration.
  2. Name: targetshot-cost-reader, Supported account types: Single tenant, Redirect URI leer. → Register.
  3. In der neuen App: Certificates & secretsNew client secret → 24 Monate Laufzeit → sofort den Value kopieren (wird nach Neuladen nie wieder angezeigt).
  4. Overview merken: Application (client) ID und Directory (tenant) ID.

2. Rolle zuweisen

Im Portal: Subscriptions → targetshot-core-0 → Access control (IAM) → Add role assignment: - Role: Cost Management Reader - Members: die gerade angelegte App targetshot-cost-reader

Damit darf der SP lesen, aber nichts schreiben.

3. Credentials als GitHub-Variablen/Secrets eintragen

Im Repo targetshot-app/admin-portal:

  • Variable INTERNAL_AZURE_COST_TENANT_ID = 053aaa7b-46f7-4348-b702-9b54ffcacc1f
  • Variable INTERNAL_AZURE_COST_CLIENT_ID = Application (client) ID aus Schritt 1
  • Secret INTERNAL_AZURE_COST_CLIENT_SECRET = der Value aus Schritt 1
  • Variable INTERNAL_AZURE_COST_SUBSCRIPTION_ID = e4d628f6-ede4-4af9-8310-b25724a71961

Der deploy-internal.yml-Workflow injiziert sie beim naechsten Deploy in die Runtime .env des Admin-Portal-Backends. Ohne diese Werte zeigt das Dashboard einfach „nicht konfiguriert" — der Rest des Admin-Portals laeuft unveraendert.

Backend-Architektur

  • backend/src/azureCosts.ts — HTTP-Client mit OAuth2 Client-Credentials-Flow gegen login.microsoftonline.com/{tenant}/oauth2/v2.0/token, Scope management.azure.com/.default. Token wird mit 60-Sekunden-Puffer gecached.
  • backend/src/routes/costs.ts — vier Routen hinter dem normalen Admin-Guard:
  • GET /api/admin/v1/costs/status{ configured: boolean }
  • GET /api/admin/v1/costs/summary
  • GET /api/admin/v1/costs/breakdown
  • GET /api/admin/v1/costs/timeseries?days=30
  • GET /api/admin/v1/costs/export.csv?days=30
  • In-Memory-Cache von 30 Minuten pro Endpunkt. Das matcht die Datenfrische der Azure-API.
  • Rollen-Capability costs.read — vergeben an platform_admin, ops_admin, billing_admin. support_admin sieht die Kosten bewusst nicht.

Frontend

  • frontend/src/features/costs/CostsPage.tsx — rendert alle vier Bloecke + eingebautes SVG-Lineplot (kein Chart-Lib-Dependency, damit die Bundlegroesse nicht wegen 30 Tagen Datenpunkten steigt).
  • Navigation-Eintrag Azure Kosten wird nur angezeigt, wenn die Session die Capability costs.read hat.

Kostenimpact der Integration

Die Cost-Management-Query- und Forecast-API sind in Azure kostenlos. Der Service Principal verursacht keine Azure-Kosten. Der Admin-Portal- Backend macht pro Cache-Window (30 Minuten) ca. 6 Requests — das ganze ist vernachlaessigbar, liegt weit unterhalb des Free-Quotas.

Bekannte Grenzen

  • Datenlag: 8–24 h. Wer gerade ein Mini-Experiment laufen laesst, sieht es nicht sofort im Dashboard.
  • Kostenhorizont: Die Forecast-API gibt den laufenden Monat vorher, kein Jahresforecast. Fuer Jahres- und Multi-Month-Analysen das Portal oder Power BI nehmen.
  • Waehrung: die Sub liefert die Kosten in der bei der Subscription hinterlegten Waehrung (EUR). Multi-Waehrung ist nicht implementiert.
  • Subscription-scope: Der SP und alle Queries laufen auf genau einer Subscription. Falls wir irgendwann mehrere bekommen, muss der subscriptionId-Parameter pro Request konfigurierbar werden.

Incident-Handling

Wenn das Dashboard "Azure hat die Kostenanfrage gerade nicht akzeptiert" zeigt:

  1. Admin-Portal-Backend-Logs checken: azure_costs_*_failed-Zeilen enthalten HTTP-Status und Fehlertext.
  2. Typische Ursachen:
  3. 401 / expired_token — Client-Secret ist abgelaufen. Im Portal neues Secret erzeugen, GH-Secret rotieren, Admin-Portal redeployen.
  4. 403 / Missing subscription registration for Microsoft.CostManagement — Resource-Provider registrieren: az provider register --namespace Microsoft.CostManagement.
  5. 429 — Azure Rate Limit. Der Cache fangt das ab, aber wenn du gerade das Cache-TTL herabgesetzt hast: einfach warten.