Dynamik mit Schleifen und Bedingungen
Infrastructure as Code mit Azure Bicep, Teil 3
Sobald mehrere gleichartige Ressourcen entstehen sollen, etwa drei Storage-Accounts für unterschiedliche Teams oder eine Reihe von Subnetzen, wird das Kopieren einzelner Ressourcenblöcke schnell unübersichtlich. Bicep löst dieses Problem über die for-Expression, die in zwei Varianten auftritt: eine zählende Schleife über einen Zahlenbereich mit range() und eine for-each-Schleife über eine bestehende Auflistung wie ein Array.
Grundprinzip der for-Expression
Zunächst das for-each über eine Auflistung: Diese häufigere Variante iteriert direkt über die Elemente eines Arrays:
resource storageAccounts 'Microsoft.Storage/storageAccounts@2023-05-01' = [for name in storageAccountNames: {
name: '${name}${uniqueString(resourceGroup().id)}'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}]
Für jedes Element im Array storageAccountNames entsteht eine eigenständige Ressourceninstanz – anstelle mehrerer manuell geschriebener Ressourcenblöcke genügt eine einzige, klar lesbare Definition.
Die zweite Variante ist eine zählende Schleife über range(): Steht statt einer Werteliste nur eine Anzahl fest, etwa „drei gleichartige Instanzen“, eignet sich die zählende Variante mit range(start, count). Sie liefert ein Array fortlaufender Zahlen, über das wie gewohnt iteriert wird:
resource numberedStorageAccounts 'Microsoft.Storage/storageAccounts@2023-05-01' = [for i in range(0, storageAccountCount): {
name: take('stnum${i}${uniqueString(resourceGroup().id)}', 24)
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}]
Hier liefert range(0, storageAccountCount) fortlaufende Zahlen ab 0, die direkt in den Ressourcennamen einfließen – passend, wenn nur die Anzahl relevant ist, nicht aber die individuellen Namen pro Instanz.
Zugriff auf den Index
Neben dem reinen Wert steht in vielen Fällen auch der Index einer Schleife zur Verfügung. Das ist hilfreich, wenn Ausgaben pro Instanz referenziert werden müssen.
output deployedNames array = [for (name, i) in storageAccountNames: storageAccounts[i].name]
Die Syntax (name, i) liefert sowohl den aktuellen Wert als auch die laufende Position innerhalb des Arrays. Damit lassen sich Outputs, Referenzen oder abhängige Ressourcen präzise einer bestimmten Instanz zuordnen.
Steuerung der Parallelität mit batchSize
Standardmäßig versucht Azure Resource Manager, alle Instanzen einer Schleife parallel bereitzustellen. Bei Ressourcen mit Abhängigkeiten untereinander oder bei API-Limits ist das nicht immer gewünscht. Der Decorator @batchSize begrenzt die Anzahl gleichzeitig laufender Deployments.
@batchSize(1)
resource storageAccounts 'Microsoft.Storage/storageAccounts@2023-05-01' = [for name in storageAccountNames: {
...
}]
Mit @batchSize(1) werden die Instanzen nacheinander statt gleichzeitig bereitgestellt, was insbesondere für Testszenarien und Ressourcen mit strikten Ratenlimits sinnvoll ist.
Bedingungen: if und der Ternary-Operator
Neben Schleifen gibt es in Bicep ein zweites Werkzeug für dynamische Templates: Bedingungen. Eine Ressource oder ein Modul wird nur dann bereitgestellt, wenn ein vorangestelltes if zu true auswertet – häufig miteinander kombiniert.
resource backupStorage 'Microsoft.Storage/storageAccounts@2023-05-01' = if (deployBackupAccount) {
name: 'stbackup${uniqueString(resourceGroup().id)}'
location: location
sku: {
name: 'Standard_GRS'
}
kind: 'StorageV2'
}
Hat deployBackupAccount den Wert false, entsteht eine Ressourceninstanz, ohne dass der Ressourcenblock selbst entfernt oder auskommentiert werden muss.
Für einfache, bedingte Werte anstelle ganzer Ressourcen eignet sich der Ternary-Operator, bekannt aus vielen Programmiersprachen in der Form Bedingung ? WertWennWahr : WertWennFalsch:
var storageSku = environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
Terraform kennt kein eigenes if: Konditionalität läuft dort über dieselbe count-Eigenschaft wie Wiederholungen, etwa count = var.deploy_backup ? 1 : 0. Bicep trennt beide Konzepte bewusst – for für Wiederholung, if für Bedingung –, wodurch die Absicht im Code sofort erkennbar ist.
Vollständiges Beispiel
Das folgende Beispiel demonstriert, wie man mehrere Azure-Storage-Accounts effizient über ein Array erstellt und deren finale Namen als Ausgabe zurückgibt:
param location string = resourceGroup().location
param storageAccountNames array = [
'stloopdemoa'
'stloopdemob'
'stloopdemoc'
]
@batchSize(1)
resource storageAccounts 'Microsoft.Storage/storageAccounts@2023-05-01' = [for name in storageAccountNames: {
name: '${name}${uniqueString(resourceGroup().id)}'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}]
output deployedNames array = [for (name, i) in storageAccountNames: storageAccounts[i].name]Warum ist das wichtig?
Schleifen verhindern, dass strukturell identischer Code mehrfach im Template auftaucht, und reduzieren damit das Risiko, dass bei einer späteren Änderung eine Kopie vergessen wird. Anpassungen an der Ressourcenlogik erfolgen nur an einer Stelle und wirken sich automatisch auf alle Instanzen aus.
Best Practices
- Arrays für Schleifen erhalten aussagekräftige Namen, die den Zweck der Instanzen erkennen lassen.
- @batchSize wird bewusst gesetzt, wenn Abhängigkeiten oder Ratenlimits eine sequenzielle Bereitstellung erfordern.
- Der Index wird nur dann verwendet, wenn er tatsächlich benötigt wird, um die Lesbarkeit zu erhalten.
- Schleifen werden klein und fokussiert gehalten; verschachtelte Strukturen werden nur bei klarem Mehrwert eingesetzt.
Häufige Fehler
- Fehlende Eindeutigkeit im Namen führt bei global eindeutigen Ressourcen wie Storage-Accounts zu Konflikten.
- Sehr tief verschachtelte Schleifen erschweren die Nachvollziehbarkeit erheblich, ähnlich wie bei Modulen.
- Parallele Deployments ohne @batchSize können bei abhängigen Ressourcen zu Race Conditions führen.
- Arrays werden über das Azure CLI ohne korrekte Quotierung übergeben, was zu Parsing-Fehlern führt.
Fazit
Schleifen und Bedingungen sind ein zentrales Werkzeug, um wiederkehrende Ressourcenstrukturen kompakt und wartbar zu beschreiben. In Kombination mit Parametern und Variablen, die der vorangegangene zweite Teil dieser Serie zu Infrastructure as Code mit Azure Bicep vorgestellt hat, entsteht damit bereits ein sehr flexibles Grundgerüst.
Der nächste Teil der Serie widmet sich Funktionen, mit denen sich wiederkehrende Logik zusätzlich kapseln und testen lässt.