Zum Inhalt

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.CostManagement muss 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:

  1. Deploye diese Version mit demselben Präfix für Export-Namen, das die pipeline pro Umgebung verwendet, sodass beide auf denselben Export zielen.
  2. Prüfe die Backend-Logs auf den Task configure-azure-cost-exports und bestätige im Azure Portal, dass die Exports existieren.
  3. Deaktiviere und lösche die alte pipeline.

Deployen und verifizieren

  1. 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.
  2. Genehmige die private-endpoint-Verbindungen und aktualisiere DNS.
  3. Deploye die nächste Umgebung auf dieselbe Weise.
  4. Bestätige, dass das Portal die Exports konfiguriert hat und mindestens eine Datei im Container billingdata gelandet 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.