Zum Inhalt

Deployment und Umgebungen

Diese Seite erklärt, wie das Portal gebaut wird, wie eine Umgebung beschrieben wird und wie eine Änderung in Produktion gelangt. Sie beschreibt das Referenzdesign: pipelines, Repository-Aufbau und Namen können in deinem Deployment abweichen. Für das erste Deployment einer Umgebung folge Eine Kundenumgebung onboarden.

Überblick

flowchart LR
    pr[Pull request] --> lint[Lint and<br/>Terraform tests]
    lint --> build[Build image once<br/>no Azure credential]
    build --> r1[Ring 1<br/>plan, approve, apply]
    r1 --> r2[Ring 2]
    r2 --> r3[Ring 3]
Prinzip Details
Einmal bauen, mehrfach deployen Pro Lauf wird ein Container-Image gebaut und in die Registry jeder Umgebung gepusht. Der Build-Job hat keine Azure-Credential.
Ein Terraform root pro Umgebung Jeder Kunde und jede Umgebung hat ein eigenes main.tf, sodass eine Änderung für einen Kunden einen anderen nicht versehentlich verändern kann.
Der Plan wird unverändert angewendet Der Plan, der erzeugt und angezeigt wurde, ist der Plan, der angewendet wird. Er wird beim Apply nicht neu berechnet.
Konfiguration in einer Datei Die Fakten einer Umgebung sind Zeilen in einer JSON-Datei, keine pipeline-Variablen.

Terraform roots

infra/
  modules/            shared modules
  platform/ado        private connections from the provider network
  platform/pagerduty  shared escalation policy and schedule
  <customer>/<env>/   one root per environment

Die gemeinsamen modules:

Module Erstellt
backstage Web App mit staging slot, Identitäten, private endpoints, PostgreSQL, Rollenzuweisungen
postgresql PostgreSQL Flexible Server 16
key_vault Key Vault mit RBAC, Purge Protection, ohne öffentlichen Zugriff
container_registry Premium-Registry, ohne öffentlichen Zugriff
azure_monitor Application Insights und Log Analytics
tenant_configuration Secrets des Tenants im Key Vault
cost_insights Data Factory, Storage, Data Lake, Azure SQL (Cost-Insights-Setup)
foundry_platform Azure-AI-Foundry-Account, Projekt, Modell-Deployments (AI-Setup)
pagerduty Service, Abhängigkeit und Azure-Monitor-Action-Group
landing_zone_spoke_canvas Nur Daten: liest die bestehende spoke Resource Group, das VNet, die Subnets, das Key Vault und das Log Analytics

Ein typisches Umgebungs-root setzt zusammen: spoke canvas, naming, Resource Group, App Service Plan, Key Vault, ein Secret für das MCP-Token, Container Registry, Azure Monitor, Tenant-Konfiguration, backstage, foundry_platform und pagerduty. Das root übergibt die App-Einstellungen der Web App: APP_CONFIG_organization_name, Feature-Flags, den Modell-Endpoint, das MCP-Token sowie optionale Einstellungen für Integrationen und Module Registry. Siehe Konfiguration.

Quellen der gemeinsamen modules

Manche Umgebungs-roots verweisen auf gemeinsame modules aus einer Registry oder einem Repository außerhalb dieser Code-Basis. Die Quelle beschreibt diesen Mechanismus nicht, bestätige daher beim Anbieter, woher diese modules stammen.

Die Environments-Datei

Jede Umgebung ist eine Zeile in infra/customer-environments.json. Ein Schema validiert die Datei, wenn ein Pull Request geöffnet wird, sodass ein fehlendes Feld vor dem Merge fehlschlägt.

Feld Bedeutung
owner, environment Name von Kunde und Umgebung (acc, prod)
deploymentEnvironment Name des GitHub Environments, <owner>_<env>
ring Reihenfolge des Deployments
enabled Zeile überspringen, wenn false
terraform.directory Pfad des roots der Umgebung
terraform.backend Resource Group, Storage Account und Container (Standard tfstate) des State
terraform.backstageClientSecretName Name des Repository Secrets mit dem Terraform Client Secret
terraform.appSecretNames Namen optionaler App-Secrets (GitHub App)
azure.clientId, tenantId, subscriptionId Für Plan und Apply verwendete Identität und subscription
azure.clientSecretName Nur für den Fallback auf ein Client Secret
azure.useDeployEnvironmentForPlan Das GitHub Environment an den Plan-Job binden
backstage.containerRegistry Login-Server der Registry
backstage.appService.name, resourceGroup Die Web App

Beispielzeile (Platzhalter):

{
  "owner": "<customer>",
  "environment": "acc",
  "deploymentEnvironment": "<customer>_acc",
  "ring": 2,
  "terraform": {
    "directory": "infra/<customer>/acc",
    "backstageClientSecretName": "BACKSTAGE_CLIENT_SECRET_<CUSTOMER>_ACC",
    "backend": {
      "resourceGroup": "<backend-resource-group>",
      "storageAccount": "<backend-storage-account>"
    }
  },
  "azure": {
    "clientId": "<client-id>",
    "tenantId": "<tenant-id>",
    "subscriptionId": "<subscription-id>"
  },
  "backstage": {
    "containerRegistry": "<registry>.azurecr.io",
    "appService": { "name": "<web-app-name>", "resourceGroup": "<resource-group>" }
  }
}

Rollout in rings

Umgebungen werden in nummerierten rings deployt. Ring N startet erst, wenn ring N-1 als Ganzes abgeschlossen ist: Schlägt eine Umgebung in einem ring fehl, startet der nächste ring nicht. Das ist ein ring-weites Sicherheits-Gate und keine Isolation pro Kunde. Der ring jeder Umgebung steht im Feld ring, prüfe also die Datei auf die aktuelle Zuordnung.

Für jede Umgebung in einem ring macht die pipeline Folgendes:

  1. Sie erzeugt einen Terraform-Plan und eine lesbare Zusammenfassung, standardmäßig ohne GitHub Environment.
  2. Sie wartet am Gate des GitHub Environments.
  3. Sie wendet den gespeicherten Plan an.
  4. Sie deployt das Portal, wenn der Lauf es einschließt (Slot Swap).

Jede Umgebung hält außerdem für das gesamte Deployment ihren eigenen Concurrency-Slot, sodass zwei Läufe nicht gleichzeitig denselben Terraform-State verändern können. Die pipeline vergleicht keine branches oder commits, ein Engineer kann also gezielt einen branch oder einen älteren commit deployen, zum Beispiel für einen Rollback.

Pipelines

Workflow Zweck
CI/CD (Push auf main) Lint, Terraform-Tests, Build, dann ring 1, 2 und 3 mit Infrastruktur und Portal
Dispatch Manueller Lauf. Eingaben: deploy_to (ring 1, 2 oder 3), run_lint, deploy_infra, build_and_deploy_backstage
Terraform plan Plant geänderte roots bei Pull Requests und jede Umgebung zu Beginn ihres rings
Deploy environment Wendet den gespeicherten Plan an und deployt optional das Portal
Platform private connections Plant bei Pull Requests, wendet beim Merge an
Platform alerting Wendet die gemeinsame Escalation Policy und den Zeitplan an

Der Lauf hat ein Timeout von 210 Minuten, weil das Erstellen eines Foundry-Accounts mehr als zwei Stunden dauern kann.

Environment-Gate

Das in deploymentEnvironment genannte GitHub Environment trägt die Genehmigung. Acceptance hat normalerweise keine Reviewer. Produktion erfordert die Genehmigung durch das Plattform-Team. Der Plan-Job verwendet das Environment nur, wenn azure.useDeployEnvironmentForPlan true ist, damit der lesbare Plan existiert, bevor jemand genehmigt.

Slot Swap

Das Portal wird über den staging slot der Web App deployt:

  1. Das Image in die Registry der Umgebung pushen.
  2. Den staging slot starten.
  3. Das Container-Image auf dem slot setzen.
  4. Preview Swap.
  5. Smoke Test: Der Readiness-Endpoint liefert 200 und ein Response-Header x-build-id entspricht der Run-ID. Timeout 600 Sekunden.
  6. Bei Erfolg den Swap ausführen. Bei Fehler den Swap zurücksetzen.
  7. Den staging slot stoppen.

Authentifizierung gegenüber Azure

Plan und Apply authentifizieren sich per OIDC über eine federated credential auf einer App-Registration im Ziel-Tenant. In GitHub wird kein Azure-Secret gespeichert.

Federated credential Subject
Pull Requests repo:<owner-id>/<repo-id>:pull_request
Main branch repo:<owner-id>/<repo-id>:ref:refs/heads/main
Jede Umgebung repo:<owner-id>/<repo-id>:environment:<owner>_<env>

Alle haben die Audience api://AzureADTokenExchange. Der Issuer ist der OIDC-Issuer des GitHub-Enterprise-Tenants, der das Repository hostet. Er unterscheidet sich vom öffentlichen GitHub-Issuer, kopiere ihn also nicht aus generischen Beispielen. Das Subject verwendet die unveränderlichen Owner- und Repository-IDs, sodass eine Umbenennung oder ein Transfer die credentials nicht bricht.

Secret Verwendung
BACKSTAGE_CLIENT_SECRET_<OWNER>_<ENV> Repository Secret mit dem Client Secret der App-Registration des Portals, an Terraform übergeben. Es muss ein Repository Secret sein, weil der Plan ohne GitHub Environment läuft.
GH_APP_* Optionale GitHub-App-Werte, in appSecretNames benannt, Terraform als var.app_secrets bereitgestellt und im Key Vault gespeichert, aus den App-Einstellungen referenziert
AZURE_CLIENT_SECRET_<OWNER>_<ENV> Nur wenn sich keine federated credential anlegen lässt. Ein letzter Ausweg: ein langlebiges Secret, das du von Hand rotierst.

Plan und Apply teilen sich eine Identität

Die federated credential pro Umgebung trennt die Plan-Phase noch nicht von der Apply-Phase, weil beide dieselbe Identität verwenden. Das Entscheidungsprotokoll führt das als offenes Risiko (Entscheidungen).

Das Portal aktualisieren

Eine Code-Änderung läuft über einen Pull Request und dann durch die rings. Eine Konfigurationsänderung für einen Kunden ist eine Änderung an seiner app-config.<customer>.yaml oder an den App-Einstellungen in seinem Terraform root. Siehe Konfiguration. Kundenspezifische Code-Änderungen werden nicht vorgenommen.