Cost-Insights-Setup¶
Diese Seite richtet sich an das Plattform-Team. Sie beschreibt, wie du die Kostenansichten (Insights) für eine Umgebung aktivierst. Sie ist ein Referenzdesign: Ressourcennamen, Scopes und Einstellungen können in deinem Deployment abweichen.
Datenfluss¶
flowchart LR
be[Portal backend<br/>scheduled tasks] -->|creates exports| cm[Cost Management]
cm -->|FOCUS Parquet, Snappy| st[(Storage account<br/>billingdata container)]
cm -->|FOCUS Parquet, Snappy| dl[(Data Lake Gen2 accounts<br/>optional)]
st --> adf[Data Factory<br/>daily pipeline, 04:00]
adf --> sql[(Azure SQL<br/>tables and views)]
sql --> be2[Portal backend<br/>queries at runtime]
dl -.-> pg[(PostgreSQL<br/>azure_storage extension)]
Das Portal erstellt und pflegt die Exports selbst. Eine Data-Factory-pipeline läuft täglich (Trigger um 04:00 Uhr W. Europe time), lädt die Parquet-Dateien in Azure SQL und aktualisiert die Views, die das Portal abfragt.
Schritte¶
| Schritt | Was |
|---|---|
| 1 | Füge das module cost_insights zum Umgebungs-root hinzu und verbinde seine Outputs mit dem module backstage |
| 2 | Füge private Verbindungen für den SQL server hinzu, wenn der Anbieter aus seinem eigenen Netzwerk deployt |
| 3 | Setze die Einstellungen für den Cost Export, damit das Portal die Exports konfiguriert |
| 4 | In zwei Durchläufen deployen und verifizieren |
1. Das module¶
Füge für jedes Umgebungs-root das module cost_insights hinzu. Die Eingaben stammen aus Outputs bestehender modules (spoke canvas, Key Vault, naming und das module backstage).
module "cost_insights" {
source = "../../modules/cost_insights"
location = module.canvas.location
environment = module.canvas.environment
workload_short_name = module.canvas.workload_short_name
subnets = module.canvas.subnets
log_analytics_workspace_id = module.canvas.log_analytics_workspace_id
key_vault_id = module.key_vault.id
data_factory_name = module.naming.data_factory.name
# One Data Lake Gen2 account per key. The key is part of the resource name,
# so keep it stable: changing it recreates the account.
datalake_storage_accounts = {
default = {}
}
backstage_user_assigned_identity = {
resource_name = module.backstage.user_assigned_identity.resource_name
client_id = module.backstage.user_assigned_identity.client_id
}
}
Das module erstellt eine eigene Resource Group mit:
| Ressource | Zweck |
|---|---|
| Azure Data Factory | Lädt die Export-Dateien in SQL. Hat einen Fehler-Alert. |
| Storage Account | Empfängt Exports im Container billingdata und speichert die Audit-Logs der Data Factory |
| Data-Lake-Gen2-Storage-Account | Einer pro Schlüssel in datalake_storage_accounts, mit eigenen Exports |
| Azure SQL server und Datenbank | Verarbeitete Abrechnungsdaten. Die Standard-SKU ist S0 und die maximale Größe 150 GB. |
| User-assigned Managed Identities | Zugriff mit minimalen Rechten zwischen Data Factory, Storage und SQL |
| Key-Vault-Schlüssel | Customer-managed Verschlüsselung für Storage und SQL |
Ein Eintrag in datalake_storage_accounts kann readers auflisten: eine Map, die über die Entra-Objekt-ID adressiert wird. Jeder Reader erhält Storage Blob Data Reader auf diesem Account, sodass ein anderes Team, etwa eine Data-Plattform, die exportierten Dateien lesen kann. Die Map ist standardmäßig leer, daher kann außerhalb des Portals niemand die Exports lesen, bis du einen Reader hinzufügst.
Das module verwendet den Terraform-Provider mssql, um Datenbankbenutzer zu verwalten, daher braucht das Umgebungs-root diesen Provider-Block. Der Runner, der Terraform anwendet, braucht deshalb einen Netzwerkpfad zum SQL server.
provider "mssql" {
azure_auth = {}
hostname = module.cost_insights.database_server
port = module.cost_insights.database_port
}
Übergib die Outputs an das module backstage: die Resource-IDs der Storage Accounts, den SQL server, Port und Datenbanknamen (als App-Einstellungen, die mit APP_CONFIG_costInsights_database_ beginnen) sowie das Präfix der Export-Namen.
module "backstage" {
# ... existing configuration ...
cost_exports_storage_account_resource_id = module.cost_insights.storage_account_resource_id
cost_exports_datalake_storage_accounts = {
default = {
resource_id = module.cost_insights.storage_account_datalake_resource_ids["default"]
role_assignment_enabled = true
}
}
additional_app_settings = {
APP_CONFIG_costInsights_database_server = module.cost_insights.database_server
APP_CONFIG_costInsights_database_port = module.cost_insights.database_port
APP_CONFIG_costInsights_database_database = module.cost_insights.database_name
APP_CONFIG_cost_exports_exportNamePrefix = "<prefix>_"
}
}
Verwende die module-Eingaben, nicht die rohen App-Einstellungen
Erst das Setzen der Storage-Eingaben des modules backstage erstellt die Rollenzuweisungen auf jedem Storage Account. Die entsprechenden App-Einstellungen allein tun das nicht.
Das Portal lädt das Cost-Backend nur, wenn costInsights.database.server gesetzt ist. Ohne diese Einstellung sind die Kostenseiten nicht verfügbar.
2. Private Verbindungen¶
Deployt der Anbieter aus seinem eigenen Netzwerk, füge im Plattform-root eine private Verbindung für den SQL server hinzu (Subresource sqlServer), genehmige sie dann auf Kundenseite und registriere den DNS-Eintrag. Siehe Eine Kundenumgebung onboarden.
Vom Portal konfigurierte Cost Exports¶
Mit den obigen Einstellungen führt das Cost-Backend geplante Tasks aus, die pro subscription im Scope einen Parquet-Export (Snappy) im FOCUS-Schema erstellen. Jeder Export deckt die Daten des laufenden Monats bis heute ab.
| Task | Ziel |
|---|---|
configure-azure-cost-exports |
cost.exports: das Storage Account, das die Data Factory liest |
configure-azure-cost-exports-datalake-<prefix> |
Ein Task pro Eintrag in cost.dataLakeExports: sein eigenes Data-Lake-Account |
| Verhalten | Details |
|---|---|
| Batch | Jeder Lauf bearbeitet einen kleinen Batch von subscriptions (Standard 25) |
| Zeitfenster | Läuft nur in einem Zeitfenster außerhalb der Geschäftszeiten und setzt sich über mehrere Läufe fort, bis alle subscriptions konfiguriert sind |
| Bestehende Exports | Werden erkannt und bleiben unverändert |
| Reprocess-Intervall | Standardmäßig ein Tag |
| Dry Run | Eine Einstellung dryRun protokolliert, was sich ändern würde, ohne Exports zu erstellen |
| Präfix | Das Präfix der Export-Namen ist standardmäßig der Name des Storage Accounts plus _ |
| State | Jedes Ziel verfolgt seinen eigenen State, sodass sich Tasks nicht stören |
Begrenze die subscriptions, wenn „jede subscription, die die Identität sehen kann“ zu weit gefasst ist:
cost:
exports:
includeManagementGroups: ['<management-group>']
dataLakeExports:
default:
includeManagementGroups: ['<management-group>']
Anforderungen auf der Azure-Seite:
- Die Managed Identity des Portals braucht Cost Management Contributor auf der management group im Scope und Lesezugriff auf beide Storage Accounts (Voraussetzungen).
Microsoft.CostManagementmuss auf jeder subscription im Scope registriert sein. Ist es das nicht, protokolliert das Portal eine Warnung und versucht es beim nächsten Lauf erneut.
Direkte Ingestion in PostgreSQL¶
Das Portal kann die Parquet-Dateien aus den Data-Lake-Accounts in seine eigene PostgreSQL-Datenbank importieren. Das module postgresql bereitet den Server vor: Er hat eine system-assigned Managed Identity, das module backstage gibt ihr Storage Blob Data Reader auf jedem Data-Lake-Account, und die Extension azure_storage ist erlaubt und vorgeladen. Wende die Änderung, die die Identität erstellt, vor der Änderung an, die die Rolle zuweist, weil Terraform keine Rollenzuweisung für einen unbekannten Principal planen kann. Eine Änderung an shared_preload_libraries startet den Server neu.
Die Quelle beschreibt diesen Weg als Vorbereitung für eine spätere Phase, in der das Portal Kostendaten selbst einliest und mit Katalogdaten kombiniert.
Von einer früheren Export-pipeline migrieren¶
Betreibt ein Kunde noch eine frühere, skriptbasierte Export-pipeline:
- Deploye diese Version mit demselben Präfix für Export-Namen, das die pipeline pro Umgebung verwendet, sodass beide auf denselben Export zielen.
- Prüfe die Backend-Logs auf den Task
configure-azure-cost-exportsund bestätige im Azure Portal, dass die Exports existieren. - Deaktiviere und lösche die alte pipeline.
Deployen und verifizieren¶
- Deploye die erste Umgebung. Der erste Lauf überspringt das Anlegen von Datenbanktabellen und Berechtigungen, weil die Resource Group der Data Factory noch nicht existiert, daher brauchst du ein zweites Deployment.
- Genehmige die private-endpoint-Verbindungen und aktualisiere DNS.
- Deploye die nächste Umgebung auf dieselbe Weise.
- Bestätige, dass das Portal die Exports konfiguriert hat und mindestens eine Datei im Container
billingdatagelandet ist. Exports laufen normalerweise innerhalb von 24 Stunden und die Ingestion über Nacht, daher können Daten ein bis zwei Tage brauchen, bis sie erscheinen.