Manuelle Bicep-Bereitstellungen skalieren nicht? Mit GitHub Actions bereitstellen
Wird jeder Bicep-Befehl manuell von einem Computer ausgeführt, hängt die Bereitstellung von den Werkzeugen, der Azure-Sitzung und dem Gedächtnis einer Person ab. Ein übersprungener Validierungsschritt oder eine nicht committete lokale Datei kann zu einem anderen Ergebnis führen.
Verschiebe die getesteten Befehle in GitHub Actions. Pull Requests validieren
den Code ohne Azure-Anmeldedaten. Nachdem genehmigter Code main erreicht hat,
authentifiziert sich ein separater Job mit OpenID Connect (OIDC), prüft die
Live-TEST-Umgebung, stellt sie bereit und verifiziert das Speicherkonto.
GitHub Actions ist die Automatisierungsplattform. Der YAML-Workflow ist die Pipeline und mit einer Azure-DevOps-YAML-Pipeline vergleichbar. Jobs enthalten die Schritte, die auf gehosteten Agents, den sogenannten Runnern, ausgeführt werden.
Workflow verstehen
| GitHub-Ereignis | Ergebnis |
|---|---|
| Pull Request | Beide Einstiegspunkte linten und den committeten TEST-Snapshot validieren |
Push nach main |
Validieren, bei Azure anmelden, Preflight und What-if ausführen, TEST bereitstellen und prüfen |
| Manueller Start | Dieselbe validierte TEST-Bereitstellung bei Bedarf ausführen |
Die Bereitstellung zielt auf die vorhandene Ressourcengruppe
rg-cloudtrips-bicep-test-weu. Sie benötigt keine Berechtigung auf
Abonnementebene, um eine Ressourcengruppe zu erstellen.
Azure-Identität vorbereiten
Erstelle eine eigene Microsoft-Entra-App-Registrierung für diesen Workflow. Dadurch bleiben die Bicep-Bereitstellungsidentität und ihre Berechtigungen auf Ressourcengruppenebene von allen anderen Automatisierungsidentitäten getrennt.
Öffne im Azure-Portal:
Microsoft Entra ID > App-Registrierungen > Neue Registrierung
Gib CloudTrips Bicep TEST GitHub als Namen ein, wähle Nur Konten in diesem
Organisationsverzeichnis, lasse den Umleitungs-URI leer und wähle
Registrieren.
Kopiere auf Übersicht die Anwendungs-ID (Client-ID) und die Verzeichnis-ID (Mandanten-ID). Öffne anschließend Zertifikate & Geheimnisse > Verbundanmeldeinformationen > Anmeldeinformation hinzufügen.
Konfiguriere eine GitHub-Actions-Umgebungsanmeldeinformation mit:
- Organisation: Besitzer deines GitHub-Repositorys
- Organisations-ID: die von GitHub zurückgegebene Repository-Besitzer-ID
- Repository:
cloudtrips - Repository-ID: die von GitHub zurückgegebene Repository-ID
- Entitätstyp: Umgebung
- Umgebung:
cloudtrips-bicep-test - Name der Anmeldeinformation:
github-cloudtrips-bicep-test
Entra bezeichnet das Feld für den Repository-Besitzer als Organisation,
auch wenn der Besitzer ein persönliches GitHub-Konto ist. Ersetze
YOUR_GITHUB_OWNER und führe nach gh auth login diese Befehle in einem
Terminal aus, um die IDs abzurufen:
gh api users/YOUR_GITHUB_OWNER \
--jq .id
gh api repos/YOUR_GITHUB_OWNER/cloudtrips \
--jq .id
Bei einem unveränderlichen GitHub-OIDC-Subject folgt der generierte Identifier diesem Format:
repo:YOUR_GITHUB_OWNER@YOUR_OWNER_ID/cloudtrips@YOUR_REPOSITORY_ID:environment:cloudtrips-bicep-test
Die Werte nach @ sind korrekt, weil dieses Repository das unveränderliche
OIDC-Subject-Format von GitHub verwendet. Prüfe die Einstellung mit:
gh api repos/YOUR_GITHUB_OWNER/cloudtrips/actions/oidc/customization/sub
Das Ergebnis sollte "use_immutable_subject": true enthalten. Übernimm das
generierte Subject unverändert. Der Aussteller muss
https://token.actions.githubusercontent.com und die Zielgruppe
api://AzureADTokenExchange sein.
Das vollständige Subject wird in Entra bei der Verbundanmeldeinformation unter
Subject-Identifier gespeichert. GitHub speichert keine zweite bearbeitbare
Kopie. Es erzeugt den sub-Claim des Tokens aus der Repository-Identität und
dieser Zeile im Bereitstellungsjob:
environment: cloudtrips-bicep-test
Öffne nach einem Workflow-Lauf Actions > den Workflow-Lauf > deploy > Sign in to Azure with OIDC. Das Protokoll zeigt Federated token details und den subject claim. Dieser Laufzeit-Claim muss Zeichen für Zeichen mit dem Subject-Identifier in Entra übereinstimmen. Füge das Subject nicht als GitHub-Secret hinzu.
Richtigen Entitätstyp auswählen
Der Entitätstyp bestimmt, welcher GitHub-Kontext in das Subject des OIDC-Tokens geschrieben wird:
| Entitätstyp | Subject wird eingeschränkt auf | Typischer Einsatz |
|---|---|---|
| Umgebung | environment:<name> |
Bereitstellungen mit Umgebungssecrets, Brancheinschränkungen oder Genehmigungen |
| Branch | ref:refs/heads/<branch> |
Branch-Job, der keine GitHub-Umgebung deklariert |
| Pull Request | pull_request |
Job, der bei der Prüfung eines Pull Requests Azure-Zugriff benötigt |
| Tag | ref:refs/tags/<tag> |
Bereitstellungen, die von einem Release-Tag gestartet werden |
Wähle hier Umgebung, weil der Bereitstellungsjob Folgendes enthält:
environment: cloudtrips-bicep-test
Deklariert ein Job eine Umgebung, schreibt GitHub die Umgebung anstelle des
auslösenden Branches in sub. Branch mit main würde deshalb nicht zu
diesem Workflow passen und die Azure-Anmeldung würde fehlschlagen. Der
Pull-Request-Job besitzt absichtlich weder eine Umgebung noch eine
Azure-Anmeldung.
Nur den erforderlichen Azure-Zugriff gewähren
Öffne rg-cloudtrips-bicep-test-weu und anschließend
Zugriffssteuerung (IAM) > Zugriff überprüfen. Suche nach der
neuen Unternehmensanwendung CloudTrips Bicep TEST GitHub, die Microsoft Entra
für die App-Registrierung erstellt hat.
Wenn sie noch keinen ausreichenden Zugriff erbt, füge die Rolle Mitwirkender auf dieser Ressourcengruppenebene hinzu. Wähle die Unternehmensanwendung anhand ihres Namens als Mitglied aus.
Die Rolle gehört zum Dienstprinzipal, der durch die Unternehmensanwendung dargestellt wird. GitHub meldet sich trotzdem mit der Anwendungs-ID (Client-ID) der App-Registrierung und nicht mit ihrer Objekt-ID an.
GitHub-Umgebung erstellen
Öffne in GitHub das Repository cloudtrips und wähle Settings >
Environments > New environment. Verwende den Namen:
cloudtrips-bicep-test
Ändere unter Deployment branches and tags die Auswahl No restriction in
Selected branches and tags. Wähle Add deployment branch or tag rule,
dann Branch, gib main ein und füge die Regel hinzu. Nur Workflow-Läufe,
deren GITHUB_REF zum Branch main passt, können diese Umgebung jetzt
verwenden.
Ein erforderlicher Prüfer ist für TEST optional; ist er konfiguriert, wartet jede Bereitstellung auf dessen Genehmigung.
Bei einem privaten Repository erfordern Bereitstellungsbranch-Einschränkungen GitHub Pro, Team oder Enterprise. Fehlt der Abschnitt, prüfe den Tarif des Repositorys.
Füge diese Umgebungssecrets hinzu:
| Secret | Wert |
|---|---|
AZURE_CLIENT_ID |
Anwendungs-ID (Client-ID) aus der App-Registrierung |
AZURE_TENANT_ID |
Verzeichnis-ID (Mandanten-ID) |
AZURE_SUBSCRIPTION_ID |
Abonnement-ID von CloudTrips TEST |
Ein Clientsecret ist nicht erforderlich. Umgebungsname des Workflows, Subject der Verbundanmeldeinformation und GitHub-Umgebung müssen exakt übereinstimmen.

Workflow erstellen
Erstelle .github/workflows/deploy-cloudtrips-bicep.yml:
name: Validate and deploy CloudTrips Bicep
on:
pull_request:
paths:
- 'cloudtrips-bicep/**'
- '.github/workflows/deploy-cloudtrips-bicep.yml'
push:
branches:
- main
paths:
- 'cloudtrips-bicep/**'
- '.github/workflows/deploy-cloudtrips-bicep.yml'
workflow_dispatch:
concurrency:
group: cloudtrips-bicep-test
cancel-in-progress: false
jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install current Bicep CLI
shell: bash
run: |
set -euo pipefail
az bicep upgrade
"$HOME/.azure/bin/bicep" --version
- name: Lint Bicep entry points
shell: bash
run: |
set -euo pipefail
az bicep lint --file cloudtrips-bicep/main.bicep
az bicep lint --file cloudtrips-bicep/subscription.bicep
- name: Validate TEST snapshot
shell: bash
run: |
set -euo pipefail
"$HOME/.azure/bin/bicep" snapshot cloudtrips-bicep/environments/test.bicepparam \
--mode validate \
--subscription-id 00000000-0000-0000-0000-000000000000 \
--resource-group rg-cloudtrips-bicep-test-weu \
--location westeurope \
--deployment-name deploy-cloudtrips-storage-test
deploy:
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
needs: validate
runs-on: ubuntu-latest
environment: cloudtrips-bicep-test
permissions:
contents: read
id-token: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Sign in to Azure with OIDC
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run Azure preflight validation
shell: bash
run: |
set -euo pipefail
az deployment group validate \
--name validate-cloudtrips-storage-test \
--resource-group rg-cloudtrips-bicep-test-weu \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--validation-level Provider \
--query "properties.provisioningState" \
--output tsv
- name: Preview Azure changes
shell: bash
run: |
set -euo pipefail
az deployment group what-if \
--resource-group rg-cloudtrips-bicep-test-weu \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--validation-level Provider
- name: Deploy CloudTrips TEST
id: deploy
shell: bash
run: |
set -euo pipefail
deployment_name="deploy-cloudtrips-storage-test-${GITHUB_RUN_NUMBER}"
az deployment group create \
--name "$deployment_name" \
--resource-group rg-cloudtrips-bicep-test-weu \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--query "properties.{state:provisioningState,outputs:outputs}" \
--output yaml
echo "deployment-name=$deployment_name" >> "$GITHUB_OUTPUT"
- name: Verify the deployed storage account
shell: bash
run: |
set -euo pipefail
storage_account_name="$(az deployment group show \
--name "${{ steps.deploy.outputs.deployment-name }}" \
--resource-group rg-cloudtrips-bicep-test-weu \
--query "properties.outputs.storageAccountName.value" \
--output tsv)"
az storage account show \
--name "$storage_account_name" \
--resource-group rg-cloudtrips-bicep-test-weu \
--query "{name:name,state:provisioningState,sku:sku.name}" \
--output table
Nur der Job deploy erhält id-token: write und die Umgebungssecrets. Die
Pull-Request-Validierung kann sich daher nicht bei Azure anmelden.
concurrency verhindert, dass zwei TEST-Bereitstellungen gleichzeitig laufen.
Pull-Request-Pipeline testen
Committe Workflow, Bicep-Dateien, Parameterdateien und Snapshot in einen
Feature-Branch. Öffne einen Pull Request nach main.
Öffne im Pull Request den Bereich Checks. Der Job validate sollte die
Bicep-Einstiegspunkte linten und bestätigen, dass test.snapshot.json weiterhin
übereinstimmt. Der Job deploy wird übersprungen, weil Pull Requests TEST nicht
verändern dürfen.

Aus main bereitstellen
Merge den genehmigten Pull Request. Öffne Actions > Validate and deploy
CloudTrips Bicep und anschließend den durch den Push nach main gestarteten
Lauf.
Der Workflow sollte:
validateabschließen- über OIDC ein kurzlebiges Azure-Token beziehen
Succeededvon Preflight erhalten- das What-if-Ergebnis anzeigen
- mit einem Namen bereitstellen, der auf der GitHub-Laufnummer endet
- Name, Status und SKU des Speicherkontos anzeigen
Hat die GitHub-Umgebung einen erforderlichen Prüfer, wähle Review
deployments, markiere cloudtrips-bicep-test und genehmige die
Bereitstellung.

Was ist das Azure-DevOps-Äquivalent?
Azure DevOps kann dasselbe CI/CD-Design ausführen. Es ist eine alternative Pipeline-Engine und keine zusätzliche, von Bicep benötigte Schicht.
| GitHub Actions | Azure-DevOps-Äquivalent |
|---|---|
Workflow unter .github/workflows |
YAML-Pipeline |
runs-on: ubuntu-latest |
Von Microsoft gehosteter Agent unter pool |
| GitHub-Umgebung | Azure-DevOps-Umgebung |
Umgebungssecrets und azure/login |
Azure-Resource-Manager-Serviceverbindung und AzureCLI@2 |
| Deployment-Branch-Regel der Umgebung | Branch-Control-Prüfung |
GITHUB_RUN_NUMBER |
Build.BuildId |
Die Bicep-Dateien und Azure-CLI-Befehle ändern sich nicht. Authentifizierung und Pipeline-Syntax ändern sich.
Azure-DevOps-Verbindung erstellen
Öffne in einem Azure-DevOps-Projekt Project settings > Service connections > New service connection > Azure Resource Manager.
Wähle App registration (automatic) mit Workload identity federation und konfiguriere:
- Scope level: Subscription
- Subscription: CloudTrips TEST
- Resource group:
rg-cloudtrips-bicep-test-weu - Service connection name:
sc-cloudtrips-bicep-test
Lasse Grant access permission to all pipelines deaktiviert. Dadurch wird
die Verbindung auf ausdrücklich autorisierte Pipelines begrenzt. Die Auswahl
der Ressourcengruppe begrenzt außerdem den Azure-Zugriff der erzeugten
Identität auf das Bereitstellungsziel. Es werden weder ein Clientsecret noch
die Pipelinevariablen AZURE_CLIENT_ID, AZURE_TENANT_ID oder
AZURE_SUBSCRIPTION_ID benötigt.
Die automatische Option benötigt ausreichende Berechtigungen zum Erstellen der App-Registrierung und Rollenzuweisung; Microsoft dokumentiert für diesen Ablauf die Rolle Owner im Zielabonnement. Ist diese Option nicht verfügbar, soll der Azure-Administrator die Serviceverbindung erstellen, anstatt auf ein langlebiges Clientsecret auszuweichen.

Erstelle unter Pipelines > Environments die Umgebung
cloudtrips-bicep-test. Öffne Approvals and checks, füge Branch control
hinzu und erlaube refs/heads/main. Eine Genehmigungsprüfung ist für TEST
optional.
Entsprechende Pipeline hinzufügen
Erstelle azure-pipelines.yml im Repository. Sie enthält dasselbe zweistufige
Design:
Validateläuft bei Pull Requests und aufmain, lintet beide Einstiegspunkte und validiert den eingecheckten TEST-Snapshot.Deployläuft nur vonrefs/heads/main, verwendet die geschützte Umgebungcloudtrips-bicep-testund führt mitAzureCLI@2und der Serviceverbindung Preflight, What-if, Bereitstellung und Überprüfung aus.
Erstelle die vollständige Datei azure-pipelines.yml:
name: cloudtrips-bicep-$(Date:yyyyMMdd).$(Rev:r)
trigger:
batch: true
branches:
include:
- main
paths:
include:
- cloudtrips-bicep/**
- azure-pipelines.yml
pr:
autoCancel: true
branches:
include:
- main
paths:
include:
- cloudtrips-bicep/**
- azure-pipelines.yml
variables:
azureServiceConnection: sc-cloudtrips-bicep-test
resourceGroupName: rg-cloudtrips-bicep-test-weu
stages:
- stage: Validate
displayName: Validate Bicep
jobs:
- job: Validate
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
- bash: |
set -euo pipefail
az bicep upgrade
"$HOME/.azure/bin/bicep" --version
displayName: Install current Bicep CLI
- bash: |
set -euo pipefail
az bicep lint --file cloudtrips-bicep/main.bicep
az bicep lint --file cloudtrips-bicep/subscription.bicep
displayName: Lint Bicep entry points
- bash: |
set -euo pipefail
"$HOME/.azure/bin/bicep" snapshot cloudtrips-bicep/environments/test.bicepparam \
--mode validate \
--subscription-id 00000000-0000-0000-0000-000000000000 \
--resource-group "$(resourceGroupName)" \
--location westeurope \
--deployment-name deploy-cloudtrips-storage-test
displayName: Validate TEST snapshot
- stage: Deploy
displayName: Deploy CloudTrips TEST
dependsOn: Validate
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployTest
displayName: Preview, deploy, and verify
pool:
vmImage: ubuntu-latest
environment: cloudtrips-bicep-test
strategy:
runOnce:
deploy:
steps:
- checkout: self
- task: AzureCLI@2
displayName: Deploy with workload identity federation
inputs:
azureSubscription: $(azureServiceConnection)
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
set -euo pipefail
az bicep upgrade
az deployment group validate \
--name validate-cloudtrips-storage-test \
--resource-group "$(resourceGroupName)" \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--validation-level Provider \
--query "properties.provisioningState" \
--output tsv
az deployment group what-if \
--resource-group "$(resourceGroupName)" \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--validation-level Provider
deployment_name="deploy-cloudtrips-storage-test-$(Build.BuildId)"
az deployment group create \
--name "$deployment_name" \
--resource-group "$(resourceGroupName)" \
--parameters cloudtrips-bicep/environments/test.bicepparam \
--query "properties.{state:provisioningState,outputs:outputs}" \
--output yaml
storage_account_name="$(az deployment group show \
--name "$deployment_name" \
--resource-group "$(resourceGroupName)" \
--query "properties.outputs.storageAccountName.value" \
--output tsv)"
az storage account show \
--name "$storage_account_name" \
--resource-group "$(resourceGroupName)" \
--query "{name:name,state:provisioningState,sku:sku.name}" \
--output table
AzureCLI@2 meldet sich vor dem Skript über die
Workload-Identity-Serviceverbindung an. Das ist das Azure-DevOps-Äquivalent zu
azure/login; die Azure-CLI-Befehle im Skript bleiben gleich.
Wähle in Azure DevOps Pipelines > New pipeline > GitHub, dann das
Repository cloudtrips, anschließend Existing Azure Pipelines YAML file
und /azure-pipelines.yml.
Beim ersten Lauf kann Azure DevOps melden, dass die Pipeline die Berechtigung
zur Verwendung von sc-cloudtrips-bicep-test benötigt. Wähle View >
Permit, um nur diese Pipeline zu autorisieren.
Für dieses GitHub-Repository validiert der YAML-Trigger pr Pull Requests und
die Bereitstellungsstufe wird übersprungen. Sobald die Änderung main
erreicht, kann die Bereitstellungsstufe die geschützte Umgebung und die
Serviceverbindung verwenden. Wird der Code später nach Azure Repos
verschoben, konfiguriere Repos > Branches > main > Branch
policies > Build validation, da Azure Repos den YAML-Trigger pr nicht
verwendet.

CloudTrips TEST wird nun aus einem geprüften Repository-Zustand statt vom
Arbeitsplatz eines Entwicklers bereitgestellt. Pull Requests liefern Continuous
Integration, während die geschützte main-Bereitstellung Continuous Delivery
ermöglicht, ohne ein Azure-Kennwort in GitHub oder Azure DevOps zu speichern.