Neuer spoke (Workload / Application Landing Zone)¶
Standardänderung: Neue Workload-/spoke-/Application-Landing-Zone (spoke canvas)
Auswirkung: Eine bestehende Azure-subscription wird zu einer einsatzbereiten landing zone. Sie wird in die gewählte Management Group verschoben, mit Tags versehen und umbenannt, an den hub angebunden und erhält die unten beschriebene Netzwerk-, Identity- und RBAC-Baseline.
Die Runbooks für Reviewer und Betreiber sind Spoke-Erstellung, Review des spoke-pull-requests und Spoke-Deployment.
Anfragefelder¶
| Anfragefeld | Wo es im spoke-JSON landet | Hinweise |
|---|---|---|
| Name | workloadShortName, plus azureSubscriptionName und der Dateiname |
workloadShortName hat höchstens 6 Zeichen. Es wird in jedem Ressourcennamen und im Terraform-state-Key verwendet. Wähle es sorgfältig: Eine spätere Änderung erzeugt einen neuen spoke. |
| Begründung | pull request / Change-Record | |
| Environment | environment |
dev, tst/test, acc/acc<n>, prd/prod, shrd/shared. Die pipeline normalisiert den Wert nur, um spokes zu stages zu gruppieren (acc2 → acc, prod → prd). Namen und der state-Key verwenden den Rohwert, daher wären prod und prd zwei verschiedene spokes. Wähle eine Schreibweise und bleibe dabei. |
| Usage type (production data yes/no) | managementGroupId und der Tag pii, falls personenbezogene Daten im Spiel sind |
Das JSON hat kein Feld für Produktionsdaten. Drücke es über die Wahl einer Management Group mit den passenden Policies aus, zum Beispiel eine untergeordnete Gruppe …-prdlike statt …-nonprd, falls deine Struktur sie hat. |
| Exposure to public internet yes/no | managementGroupId (Baum landingzones_corp vs. landingzones_online) und InternetSecurityEnabled |
Eingehendes Publishing läuft über den hub Application Gateway (Runbook). Ausgehender Internetzugriff braucht Firewallregeln (Runbook). |
| IP range / IP size | resourceconfiguration.network[].addressPrefix |
Vergib den Bereich aus deinem IP-Plan. Der Code prüft nicht auf Überschneidungen. Ein /24 ist ein üblicher Standard. Wenn du mehr brauchst, stimme das mit dem Eigentümer des IP-Plans ab. Trage den vergebenen Bereich im selben pull request im Plan ein. |
| Subnet name / size | network[].subnets[].name / .addressPrefix |
|
| Subnet delegations | network[].subnets[].delegations[] |
name muss einer der im Schema aufgelisteten Werte sein (zum Beispiel Microsoft.Web/serverFarms). |
| Subnet service endpoints | network[].subnets[].serviceEndpoints[] |
{ "service": "Microsoft.Storage" } |
| Which team needs access | adInfo.ProductTeamGroupNames, adInfo.PIMGroupConfig |
Anzeigenamen bestehender Entra-ID-Gruppen. |
| Kommentare | – |
Die Anfrage prüfen¶
Wenn eine Anfrage eingeht, prüfe zuerst, ob die Angaben vollständig und korrekt sind. Schicke unklare Anfragen zurück, bis sie klar sind.
| Punkt | Prüfung |
|---|---|
| Workload-Kurzname | Eindeutig über alle spokes. Höchstens 6 Zeichen. Nur Buchstaben und Ziffern: keine Leerzeichen oder Sonderzeichen. |
| Environment | Eine der unterstützten Lebenszyklusphasen (Entwicklung, Test, Abnahme, Produktion). Verwende die Kürzel dev, tst, acc, prd einheitlich. |
| Tags | Die erforderlichen Governance-Tags sind vorhanden: owner, department, costcentre, pii (ein Textwert, siehe die Referenz), dazu workload und environment. Die spoke-canvas-Anleitung verlangt Kleinbuchstaben; prüfe, was deine Tag-Policy verlangt. Füge nicht den reservierten Tag hidden-XebiaSpokeMetadata hinzu: Das Deployment fügt ihn hinzu. |
| Team-Gruppe | Der Name der Product-Team-Gruppe, genau so wie in Entra ID. |
| Zielregionen | Regionen, die es in deiner Plattform gibt. Verwende die Kurznamen deiner regioninfo (Gemeinsame Spoke-Einstellungen). Beispiele für Kurznamen: weu (West Europe), neu (North Europe). |
| Adressraum | Ein /24 (256 Adressen) ist das übliche Minimum. Alles, was größer als ein /23 ist, braucht ein Gespräch und eine Begründung. Siehe auch IP-Bereich und Subnets planen. |
| Subnets | Name, Größe, Delegierungen und Service Endpoints, gemäß Spoke-JSON-Referenz. |
| Management group | Aus den Gruppen deiner Struktur gewählt, siehe unten. |
Die management group wählen¶
Der spoke kommt in eine landing-zone-management-group. Zwei Entscheidungen bestimmen, in welche: ob der Workload aus dem Internet erreichbar ist (Corp oder Online) und ob er produktionsnahe Daten verarbeitet (PrdLike oder NonPrd). Mit einer canary- und einer Produktions-Plattformumgebung hat jede ihren eigenen Baum, also gibt es insgesamt acht Kombinationen:
| Plattformumgebung | Corp oder Online | NonPrd oder PrdLike | Management group (Beispielbenennung) |
|---|---|---|---|
| Canary | Corp | NonPrd | <canary>CorpNonPrd |
| Canary | Corp | PrdLike | <canary>CorpPrdLike |
| Canary | Online | NonPrd | <canary>OnlineNonPrd |
| Canary | Online | PrdLike | <canary>OnlinePrdLike |
| Produktion | Corp | NonPrd | <platform>CorpNonPrd |
| Produktion | Corp | PrdLike | <platform>CorpPrdLike |
| Produktion | Online | NonPrd | <platform>OnlineNonPrd |
| Produktion | Online | PrdLike | <platform>OnlinePrdLike |
Die Namen sind ein Beispiel einer Implementierung. Verwende die IDs, die in deiner management_group_structure existieren
(Hub-Config).
Bevor du loslegst¶
Zum ersten spoke einer neuen Plattform siehe Den spoke canvas deployen.
- Die subscription muss bereits existieren. Der subscription-Layer kann eine subscription erstellen, aber nur, wenn
azureSubscriptionIdleer ist und nur mit einem CSP-Billing-Account (xmscsp_billing_account_name). Die pipeline übergibt die CSP-Werte als leere Strings (Invoke-XebiaMcpSpokePlan.ps1), also musst du über die pipelines eine bestehende subscription-ID angeben. Erstelle die subscription zuerst über deinen EA/MCA/CSP-Prozess. - Die spoke deployment identity (
Azure.TerraformState.Spoke.ServicePrincipalIdinmcp.json, verwendet von der spoke service connection) muss alles können, was unter Was erstellt wird aufgeführt ist: - die subscription zwischen Management Groups verschieben;
- Rollenzuweisungen auf der subscription erstellen;
- eine virtual hub connection in der connectivity-subscription erstellen;
- Entra-ID-Gruppen in der Administrative Unit
SpokeCommon.tenantinfo.AdministrativeUnitIderstellen; - service principals erstellen;
- PIM für Gruppen konfigurieren.
- Die Ziel-
managementGroupIdmuss alsidin<env-folder>/<targetRegions.primary of the spoke>/foundation.auto.tfvarsvorkommen, sonst lehnt der Config-Test die Datei ab. Der Test akzeptiert jedeidinmanagement_group_structureund in den corp/online-Child-Maps. Wähle selbst eine landing-zone-Gruppe; der Test hindert dich nicht daran, zum Beispiel die Sandbox-Gruppe zu wählen. - Die Gruppen in
ProductTeamGroupNamesmüssen in Entra ID existieren. - Die Genehmigungen, die deine Organisation verlangt, liegen vor, zum Beispiel vom Product Owner sowie von Architektur und Security für einen neuen Workload, und eine Registrierung des Workloads in deinem Asset Register (CMDB), falls du eines führst.
- Der Adressbereich ist im IP-Plan reserviert (siehe Tabelle oben), und der Subnet-Plan lässt Platz für neue Subnets. Wenn du stattdessen einen IPAM-Pool nutzt, ist der Pool in den gemeinsamen Einstellungen aufgeführt (unten).
- Wenn der spoke ein Azure-DevOps-Repository oder ein GitHub-Repository erstellt, ist die Vorbereitung dafür erledigt (Spoke-Integrationen vorbereiten).
Schritte¶
1. Die spoke-Datei erstellen¶
Erstelle zuerst einen Branch. Ein einheitlicher Branch-Name macht Branches leicht auffindbar, zum Beispiel
feature/SpokeNew-<request id>-<workload short name>-<environment>. Prüfe deine eigenen Konventionen.
Erstelle dann <env-folder>/spokes/<team-or-workload>/<file>.json. Am einfachsten startest du mit einer Kopie der Template-spoke-Datei,
die mit dem Konfigurations-Repository oder der gepinnten Code-Version kommt. Template-Dateien für häufige Muster können im
selben Repository liegen, aber gib ihnen eine andere Endung als .json oder lege sie dorthin, wo der glob nicht greift, damit
die pipeline sie nicht deployt.
- Verwende einen Unterordner. Der Standard-glob
**/*.jsonbraucht ein/, daher werden Dateien direkt inspokes/nicht erfasst. - Verwende einen eindeutigen Dateinamen über alle Unterordner hinweg. Der Dateiname ohne
.jsonwird zum Namen des spokes in pipeline-Jobs und Artefakten (spoke_<name>,subscription_output_<name>). Zwei Dateien mit demselben Namen in verschiedenen Ordnern kollidieren in einem Lauf. - Ein einheitliches Muster wie
canvas_<workload>_<environment>.jsonmachtspokeGlobeinfach nutzbar. - Die separate spoke-canvas-pipeline erwartet mehr: Der Name beginnt mit
canvas_, endet auf.jsonund enthält_prod_oder_nonprod_(zum Beispielcanvas_nonprod_<workload>-<environment>.json). Halte dich an dieses Muster, wenn du diese pipeline betreibst.
Minimales Beispiel (gültig gegen src/Xebia.Mcp/Files/json/spokeschema.json):
{
"workloadShortName": "app1",
"environment": "dev",
"azureSubscriptionId": "00000000-0000-0000-0000-000000000000",
"azureSubscriptionName": "sub-app1-dev",
"managementGroupId": "cloudplatform-landingzones-corp",
"tags": {
"owner": "Jane Doe",
"department": "Finance IT",
"workload": "Invoice Portal",
"pii": "no",
"costcentre": "CC-1234",
"environment": "dev"
},
"adInfo": {
"ProductTeamGroupNames": ["Team Invoice Portal"],
"PIMGroupConfig": {
"Readers": "nopim",
"Contributors": "self-approve",
"Owners": "approval"
}
},
"targetRegions": {
"primary": "weu",
"additional": []
},
"resourceconfiguration": {
"network": [
{
"name": "vnet-##wlshortname##-##environment##-##region##-001",
"region": "weu",
"addressPrefix": ["10.20.4.0/24"],
"peerHub": true,
"dnsServerOverwrite": "",
"subnets": [
{
"name": "snet-##wlshortname##-pe-##environment##-##region##-001",
"isprivatelinksubnet": true,
"addressPrefix": "10.20.4.0/27",
"privateEndpointNetworkPoliciesEnabled": false,
"serviceEndpoints": [],
"delegations": [],
"linkedroutetablename": null,
"linkednetworksecuritygroupname": "nsg-##wlshortname##-##environment##-##region##-001"
},
{
"name": "snet-##wlshortname##-app-##environment##-##region##-001",
"isprivatelinksubnet": false,
"addressPrefix": "10.20.4.64/26",
"privateEndpointNetworkPoliciesEnabled": false,
"serviceEndpoints": [{ "service": "Microsoft.Storage" }],
"delegations": [
{
"name": "Microsoft.Web/serverFarms",
"actions": ["Microsoft.Network/virtualNetworks/subnets/action"]
}
],
"linkedroutetablename": null,
"linkednetworksecuritygroupname": "nsg-##wlshortname##-##environment##-##region##-001"
}
],
"networksecuritygroups": [
{ "name": "nsg-##wlshortname##-##environment##-##region##-001", "rules": [] }
],
"routetables": []
}
]
}
}
Die Platzhalter ##wlshortname##, ##environment## und ##region## werden in den Namen von Netzwerk, Subnets, NSGs und Route Tables
ersetzt.
Zwei strukturelle Regeln, die das Schema nicht erzwingt, auf die sich Terraform aber verlässt:
- Jeder
network[]-Eintrag braucht mindestens ein Subnet mit"isprivatelinksubnet": true(modules/virtual_network/locals.tfnimmt das erste); - wenn der Key Vault oder das storage account deployt wird (Standard), braucht jede Region in
targetRegionsein VNet mit einem solchen Subnet. Dort werden die private endpoints platziert. Alle Felder, einschließlich der optionalen Blöckekeyvault,storageaccount,loganalytics,azdoundextensions, sind in der spoke-JSON-Referenz beschrieben.
Adressraum aus einem IPAM-Pool¶
Wenn die Plattform IPAM-Pools hat (Gemeinsame Spoke-Einstellungen), kannst du
addressPrefix weglassen und stattdessen den Pool-Namen und die Größen angeben. Der Pool wählt die Adressen, und das Überschneidungsrisiko
handgeschriebener Bereiche entfällt. Die Felder und Regeln stehen in der
Spoke-JSON-Referenz. Trage die zugewiesenen Bereiche danach aus dem
Deployment-Output in deinen IP-Plan ein.
2. Über die optionalen Ressourcen entscheiden¶
Wenn du die Blöcke storageaccount, keyvault und loganalytics weglässt, werden trotzdem ein storage account, ein Key Vault und ein Log
Analytics workspace mit Modul-Defaults erstellt (deploy ist standardmäßig true in
spoke-canvas/deploy/spoke/locals.tf und main.tf). Ein storage account erzwingt immer einen Key Vault, der seinen
customer-managed key enthält. Um sie zu überspringen, füge den Block mit "deploy": false hinzu und mit allen Schlüsseln, die das Schema für diesen
Block verlangt (siehe die Referenz).
Erstelle den private endpoint des Key Vaults oder des storage accounts (createprivateendpoint, CreatePrivateEndpoint) nur dann,
wenn der Workload ihn nutzen wird. Der Key Vault enthält das Secret des Deployment-Service-Principals, sofern der spoke keine
federated credentials verwendet. Bevorzuge federated credentials (azdo.useFederatedCredentials, wenn du die Azure-DevOps-Service-
Connection oder die GitHub-Extension erstellst), damit kein Secret gespeichert oder rotiert werden muss. Beide Integrationen sind in
Spoke-Integrationen beschrieben.
3. Validieren und einen pull request öffnen¶
Prüfe die Datei noch einmal, committe sie in den Branch und öffne einen pull request. Eine andere Person prüft ihn (Review des spoke-pull-requests). Folge Änderungen sicher durchführen, Schritte 4–5.
4. Die spoke-pipeline ausführen¶
Führe spoke aus, wobei spokeGlob nur auf die neue Datei zeigt, zum Beispiel team-a/canvas_app1_dev.json.
- Azure DevOps: Pipelines →
spoke→ Run pipeline →spokeGlob=team-a/canvas_app1_dev.json. - GitHub Actions: Actions →
spoke→ Run workflow →spokeGlob=team-a/canvas_app1_dev.json.
Führe nicht mit dem Standard
**/*.jsonaus, es sei denn, du willst jeden spoke planen. Jede vom glob erfasste spoke-Datei muss die Config-Tests bestehen, und ihr environment muss in derenvironments-Liste der pipeline stehen (Standard dev → tst → acc → prd). Einshared-spoke lässt den Lauf fehlschlagen, es sei denn, du fügstsharedzuenvironmentshinzu.Stelle sicher, dass die Entry-pipeline nicht
whatIf: truehat, wenn du den spoke anwenden willst (siehe Pipelines und Workflows).
Was erstellt wird¶
Aus src/Xebia.Mcp/Files/terraform/spoke-canvas/deploy/spoke/main.tf:
| Ressource | Name / Detail |
|---|---|
| Subscription-Tags | Die tags aus dem JSON plus spoke_canvas_version |
| Management-Group-Platzierung | Verschiebt die subscription nach managementGroupId, falls sie dort noch nicht ist |
| Subscription-Umbenennung | In azureSubscriptionName. Wenn leer, in SpokeCommon.tenantinfo.SubsciptionNamingTemplate |
| Resource group (pro Region) | rg-canvas-<wl>-<env>-<region>-001, es sei denn, resourceGroup.name ist gesetzt |
| Virtual network, Subnets, NSGs, Regeln, Route Tables | Aus network[]. Jedes Subnet erhält einen NSG: den aus linkednetworksecuritygroupname oder einen Default-NSG |
| DNS-Server am virtual network | dnsServerOverwrite oder der hub-DNS-Server der Region (virtual_network_dns_servers in den hub-connectivity-tfvars) |
Virtual hub connection (wenn peerHub, Standard true) |
VNet-Name mit vnet- → con-, in der connectivity-subscription, internet_security_enabled = InternetSecurityEnabled (Standard true) |
| Log Analytics workspace (standardmäßig an) | log-<wl>-<env>-<region>-001 |
| Key Vault (standardmäßig an) | kv<wl><env><region>001, RBAC-Modus, standardmäßig private endpoint |
| Storage account (standardmäßig an) | st<wl><env><region>001, customer-managed key, standardmäßig private endpoint |
| Entra-ID-Gruppen | <GroupnamePrefix><wl>_<env>_Owners, _Contributors, _Readers, _Support in der Administrative Unit |
| PIM | Jede ProductTeamGroupNames-Gruppe wird Mitglied jeder spoke-Gruppe: nopim = aktiv, self-approve = eligible ohne Genehmigung, approval = eligible mit Genehmigung |
| Service principal | <SPNNamePrefix><wl>-<env>. Sein Secret wird im spoke-Key-Vault gespeichert, es sei denn, es werden federated credentials verwendet |
| Rollenzuweisungen | Subscription: SPN und Owners-Gruppe → Owner, Contributors → Contributor, Readers → Reader. Dazu Data-Plane-Rollen auf Key Vault und storage account, vergeben über die role map auf subscription-Ebene (RBAC und PIM) |
| Azure-DevOps-Repo und service connection | Nur wenn der Block azdo vorhanden ist |
| GitHub-Repository und Federated Credentials | Nur wenn extensions.github aktiviert ist (Spoke-Integrationen) |
Nicht vom spoke canvas erstellt: Budgets und Firewallregeln für den Traffic des spokes.
Prüfen¶
- Der Apply-Schritt der pipeline für die spoke-Umgebung ist grün.
- Im Azure portal gilt für die subscription:
- sie liegt unter
managementGroupId; - sie hat die Tags;
- sie hat das virtual network mit dem erwarteten Adressraum und den erwarteten Subnets.
- Connectivity-subscription → Virtual WAN → Virtual network connections: Das spoke-VNet ist als verbunden aufgelistet.
- Entra ID: Die vier
SG_SPOKE_<wl>_<env>_*-Gruppen existieren, und die Team-Gruppe ist wie konfiguriert (eligible) Mitglied. - Bitte das Team, seine Rolle zu aktivieren (falls PIM gilt), und den Zugriff zu bestätigen.
Rollback¶
Entferne den spoke mit dem Runbook Delete spoke. Das Löschen der JSON-Datei allein bewirkt nichts: Keine pipeline zerstört spokes, deren Datei fehlt.
Wenn der spoke Managed VMs bekommt, füge seinen Ordner zur Liste der spokes der Modul-pipeline hinzu (siehe Managed-VM-Modul).
Häufige Probleme¶
| Symptom | Ursache |
|---|---|
Config-Test: environment … must be one of |
Das environment des spokes steht nicht in der environments-Liste der pipeline. |
Config-Test: managementGroupId … must exist |
Die Gruppe steht nicht in der foundation.auto.tfvars der primären Region. |
| Config-Test: Schemafehler | Fehlender Pflicht-Key. Jedes Subnet braucht isprivatelinksubnet, privateEndpointNetworkPoliciesEnabled, serviceEndpoints, delegations und linkedroutetablename. |
| Der Plan will für einen bestehenden spoke einen komplett neuen spoke erstellen | workloadShortName oder environment wurde geändert, was den state-Key ändert. |
Ein erneuter Lauf plant, azurerm_management_group_subscription_association zu zerstören |
Bekanntes Problem: Das count der Association hängt davon ab, ob die subscription bereits in der Gruppe ist. Siehe Bekannte Probleme. Wende dieses destroy nicht an, ohne es zu verstehen. |
| Der spoke erreicht das Internet oder andere Netzwerke nicht | Firewallregeln fehlen. Siehe Firewallregeln. |