Manuelle Bicep-Bereitstellungen skalieren nicht? Mit GitHub Actions bereitstellen

Veröffentlicht am:

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.

GitHub-Umgebung für CloudTrips Bicep TEST mit den drei konfigurierten Namen der Azure-OIDC-Secrets

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.

Erfolgreiche Pull-Request-Validierung mit übersprungenem Azure-Bereitstellungsjob

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:

  1. validate abschließen
  2. über OIDC ein kurzlebiges Azure-Token beziehen
  3. Succeeded von Preflight erhalten
  4. das What-if-Ergebnis anzeigen
  5. mit einem Namen bereitstellen, der auf der GitHub-Laufnummer endet
  6. 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.

Erfolgreiche CloudTrips-Bicep-Bereitstellung aus main mit Job zur Überprüfung des Speicherkontos

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.

Azure-DevOps-Serviceverbindung mit Workload Identity und Gültigkeitsbereich der CloudTrips-TEST-Ressourcengruppe

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:

  • Validate läuft bei Pull Requests und auf main, lintet beide Einstiegspunkte und validiert den eingecheckten TEST-Snapshot.
  • Deploy läuft nur von refs/heads/main, verwendet die geschützte Umgebung cloudtrips-bicep-test und führt mit AzureCLI@2 und 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.

Erfolgreicher Azure-Pipelines-Lauf mit Validierung und anschließender CloudTrips-TEST-Bereitstellungsstufe

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.