Übung: Seeding eines Speicherkontos und einer Datenbank

Abgeschlossen

Sie haben Ihren Workflow aktualisiert, um die Anwendung Ihrer Website in der Azure App Service-App zu erstellen und bereitzustellen, die in Ihrer Bicep-Datei definiert ist, aber der Feuerprobenauftrag ist fehlgeschlagen, da die Datenbank noch nicht funktioniert. In dieser Lerneinheit stellen Sie einen neuen logischen Azure SQL-Server und eine Datenbank bereit und konfigurieren Ihren Workflow für die Erstellung und Bereitstellung des Datenbankschemas. Außerdem aktualisieren Sie Ihren Workflow, um einige Beispielproduktdaten für Ihre Testumgebung hinzuzufügen, damit Ihr Team die Website ausprobieren kann.

Dabei gehen Sie wie folgt vor:

  • Fügen Sie dem Azure-Speicherkonto einen Blobcontainer hinzu.
  • Fügen Sie einen logischen Azure SQL-Server und eine Datenbank hinzu.
  • Aktualisieren des Buildauftrags, um das Datenbankprojekt in eine DACPAC-Datei zu kompilieren
  • Hinzufügen neuer Variablen und Geheimnisse für den logischen Azure SQL-Server und die Datenbank
  • Aktualisieren Ihres Workflows, um die neuen Variablen und Geheimnisse zu verwenden
  • Hinzufügen neuer Workflowschritte, um Ihre DACPAC-Datei bereitzustellen
  • Ausführen des Workflows und Anzeigen der Website

Erstellen eines Speichercontainers

Ihre Bicep-Datei definiert bereits ein Speicherkonto, aber keinen Blobcontainer. Hier fügen Sie Ihrer Bicep-Datei einen Blobcontainer hinzu. Sie geben auch den Namen des Speicherkontos und Blobcontainers mithilfe der Konfigurationseinstellungen für die Anwendung an. Auf diese Weise weiß die App, auf welches Speicherkonto sie zugreifen soll.

  1. Öffnen Sie in Visual Studio Code die Datei main.bicep im Ordner Bereitstellen.

  2. Fügen Sie unter den Variablen, die Ressourcennamen definieren (in der Nähe von Zeile 27), eine neue Variablendefinition für den Namen des Blobspeichercontainers hinzu:

    var storageAccountImagesBlobContainerName = 'toyimages'
    
  3. Aktualisieren Sie die storageAccount-Ressource, um den Blobcontainer zu definieren:

    resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
      name: storageAccountName
      location: location
      kind: 'StorageV2'
      sku: environmentConfigurationMap[environmentType].storageAccount.sku
    
      resource blobService 'blobServices' = {
        name: 'default'
    
        resource storageAccountImagesBlobContainer 'containers' = {
          name: storageAccountImagesBlobContainerName
    
          properties: {
            publicAccess: 'Blob'
          }
        }
      }
    }
    
  4. Aktualisieren Sie die appSettings-Eigenschaft der App, um zwei neue Anwendungseinstellungen hinzuzufügen: eine für den Speicherkontonamen und eine für den Blobcontainernamen:

    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
          ]
        }
      }
    }
    
  5. Fügen Sie am Ende des Dateiinhalts neue Ausgaben hinzu, um die Namen des Speicherkontos und des Blobcontainers verfügbar zu machen:

    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    
  6. Speichern Sie die geänderte Datei.

  7. Committen Sie Ihre Änderungen in Ihr Git-Repository, aber pushen Sie sie noch nicht. Führen Sie im Visual Studio Code-Terminal die folgenden Befehle aus:

    git add .
    git commit -m "Add storage container"
    

Hinzufügen eines logischen Azure SQL-Servers und einer Datenbank

Ihre Bicep-Datei stellt derzeit keinen logischen Azure SQL-Server oder eine Datenbank bereit. In diesem Abschnitt fügen Sie diese Ressourcen Ihrer Bicep-Datei hinzu.

  1. Fügen Sie am Anfang der Datei main.bicep unter dem Parameter reviewApiKey zwei neue Parameter hinzu:

    @description('The administrator login username for the SQL server.')
    param sqlServerAdministratorLogin string
    
    @secure()
    @description('The administrator login password for the SQL server.')
    param sqlServerAdministratorLoginPassword string
    
  2. Fügen Sie unterhalb der Variablen, die Ressourcennamen definieren, neue Variablen hinzu, um die Namen Ihres logischen Azure SQL-Servers und der Datenbank zu definieren:

    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
  3. Definieren Sie unter den soeben hinzugefügten Variablen eine neue Variable, die eine Verbindungszeichenfolge für die Anwendung für den Zugriff auf die Datenbank erstellt:

    // Define the connection string to access Azure SQL.
    var sqlDatabaseConnectionString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabase.name};Persist Security Info=False;User ID=${sqlServerAdministratorLogin};Password=${sqlServerAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
    

    Hinweis

    Der Einfachheit halber verwendet die Anwendung die Administratoranmeldung und das Kennwort für den Zugriff auf die Datenbank. Dies ist jedoch keine bewährte Methode für eine Produktionslösung. Es ist besser, eine von App Service verwaltete Identität für den Zugriff auf die Datenbank zu verwenden und der verwalteten Identität die für die Anwendung erforderlichen Mindestberechtigungen zu gewähren. Weitere Informationen finden Sie auf der Zusammenfassungsseite dieses Moduls.

  4. Fügen Sie am Ende des Dateiinhalts oberhalb der Ausgaben den logischen Azure SQL-Server und die Datenbank als Ressourcen hinzu:

    resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
      name: sqlServerName
      location: location
      properties: {
        administratorLogin: sqlServerAdministratorLogin
        administratorLoginPassword: sqlServerAdministratorLoginPassword
      }
    }
    
    resource sqlServerFirewallRule 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = {
      parent: sqlServer
      name: 'AllowAllWindowsAzureIps'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
    }
    
    resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
      parent: sqlServer
      name: sqlDatabaseName
      location: location
      sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
    }
    
  5. Aktualisieren Sie die Variable environmentConfigurationMap, um die SKUs zu definieren, die für Ihre Datenbank für jede Umgebung verwendet werden:

    var environmentConfigurationMap = {
      Production: {
        appServicePlan: {
          sku: {
            name: 'S1'
            capacity: 1
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_LRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
      Test: {
        appServicePlan: {
          sku: {
            name: 'F1'
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_GRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
    }
    
  6. Fügen Sie Ihrer App Service-App eine weitere App-Einstellung für die Datenbankverbindungszeichenfolge hinzu:

    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
            {
              name: 'SqlDatabaseConnectionString'
              value: sqlDatabaseConnectionString
            }
          ]
        }
      }
    }
    
  7. Fügen Sie am Ende der Datei Ausgaben hinzu, um den Hostnamen des logischen Azure SQL-Servers und den Namen der Datenbank verfügbar zu machen:

    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
    output sqlDatabaseName string = sqlDatabase.name
    
  8. Speichern Sie die geänderte Datei.

Hinzufügen neuer Buildschritte für das Datenbankprojekt

Ihre Websiteentwickler haben ein Visual Studio-Datenbankprojekt vorbereitet, das Ihre Websitedatenbanktabelle bereitstellt und konfiguriert. Hier aktualisieren Sie Ihren Workflow build, um das Datenbankprojekt in eine DACPAC-Datei zu kompilieren und diese als Workflowartefakt hochzuladen.

  1. Öffnen Sie die Datei build.yml im Ordner .github/workflows.

  2. Um das Visual Studio-Datenbankprojekt zu kompilieren und die generierte DACPAC-Datei als Workflowartefakt hochzuladen, fügen Sie den Auftrag build-database hinzu:

    name: build-website
    
    on:
      workflow_call:
    
    jobs:
      build-application:
        name: Build application
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Install .NET Core
          uses: actions/setup-dotnet@v3
          with:
            dotnet-version: 3.1
    
        - name: Build publishable website
          run: |
            dotnet publish --configuration Release
          working-directory: ./src/ToyCompany/ToyCompany.Website
    
        - name: Zip publishable website
          run: |
            zip -r publish.zip .
          working-directory: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish
    
        - name: Upload website as workflow artifact
          uses: actions/upload-artifact@v3
          with:
            name: website
            path: ./src/ToyCompany/ToyCompany.Website/bin/Release/netcoreapp3.1/publish/publish.zip
    
      build-database:
        name: Build database
        runs-on: windows-latest
        steps:
        - uses: actions/checkout@v3
    
        - name: Prepare MSBuild
          uses: microsoft/setup-msbuild@v1.1
    
        - name: Build database project
          working-directory: ./src/ToyCompany/ToyCompany.Database
          run: MSBuild.exe ToyCompany.Database.sqlproj -property:Configuration=Release
    
        - name: Upload website as workflow artifact
          uses: actions/upload-artifact@v3
          with:
            name: database
            path: ./src/ToyCompany/ToyCompany.Database/bin/Release/ToyCompany.Database.dacpac
    

    Der Auftrag build-database verwendet einen Windows-Runner. Derzeit müssen Visual Studio-Datenbankprojekte auf dem Windows-Betriebssystem entwickelt werden.

  3. Speichern Sie die geänderte Datei.

Definieren der Geheimnisse

Sie müssen das Administratorkennwort Ihres logischen Azure SQL-Servers für jede Umgebung sicher speichern. Sie entscheiden sich für die Verwendung von GitHub-Geheimnissen, um die Informationen zu schützen.

  1. Wechseln Sie in Ihrem Browser zu Einstellungen>Geheimnisse und Variablen>Aktionen.

    Screenshot: GitHub-Benutzeroberfläche mit Anzeige des Menüelements für Geheimnisse in der Kategorie für Einstellungen

  2. Wählen Sie die Schaltfläche New repository secret (Neues Repositorygeheimnis) aus.

  3. Geben Sie SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST als den geheimen Namen und SecurePassword!111 als Wert für den Wert ein.

    Screenshot: GitHub mit Anzeige eines neuen Geheimnisses.

  4. Klicken Sie auf Add secret (Geheimnis hinzufügen).

  5. Wiederholen Sie den Vorgang, um ein weiteres Geheimnis namens SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION mit dem Wert SecurePassword!999 hinzuzufügen. Klicken Sie auf Add secret (Geheimnis hinzufügen).

Hinzufügen der Geheimnisse und Eingaben in Ihrem Workflow

  1. Öffnen Sie in Visual Studio Code die Datei deploy.yml im Ordner .github/workflows.

  2. Definieren Sie am Anfang der Datei eine neue Eingabe mit dem Namen sqlServerAdministratorLogin und ein neues Geheimnis mit dem Namen sqlServerAdministratorLoginPassword:

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
          sqlServerAdministratorLogin:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
          sqlServerAdministratorLoginPassword:
            required: true
    
  3. Speichern Sie die geänderte Datei.

  4. Öffnen Sie die Datei workflow.yml.

  5. Definieren Sie in der Definition von deploy-test einen Wert für die sqlServerAdministratorLogin Eingabe und geben Sie den Wert für das sqlServerAdministratorLoginPassword-Geheimnis weiter:

    # Deploy to the test environment.
    deploy-test:
      uses: ./.github/workflows/deploy.yml
      needs: [build, lint]
      with:
        environmentType: Test
        resourceGroupName: ToyWebsiteTest
        reviewApiUrl: https://sandbox.contoso.com/reviews
        sqlServerAdministratorLogin: TestToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_TEST }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_TEST }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST }}
    
  6. Wiederholen Sie Vorgang in der Definition deploy-production mit den Werten für die Produktionsumgebung:

    # Deploy to the production environment.
    deploy-production:
      uses: ./.github/workflows/deploy.yml
      needs:
      - lint
      - build
      - deploy-test
      with:
        environmentType: Production
        resourceGroupName: ToyWebsiteProduction
        reviewApiUrl: https://api.contoso.com/reviews
        sqlServerAdministratorLogin: ToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_PRODUCTION }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION }}
    
  7. Speichern Sie die geänderte Datei.

Hinzufügen von Parameterwerten und Ausgaben

Die Bicep-Datei verfügt jetzt über zwei neue obligatorische Parameter: sqlServerAdministratorLogin und sqlServerAdministratorLoginPassword. Hier geben Sie diese Parameterwerte aus Ihren Workfloweingaben und -geheimnissen für die Aufträge validate und deploy weiter. Sie geben außerdem die Ausgaben der Bicep-Bereitstellungen an die Ausgaben des Auftrags weiter.

  1. Öffnen Sie die Datei deploy.yml.

  2. Aktualisieren Sie den Schritt Run preflight validation (Preflightvalidierung ausführen) des Auftrags validate, um die neuen Parameter hinzuzufügen:

    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             deploymentMode: Validate
    
  3. Aktualisieren Sie den Schritt Run what-if (Was-wäre-wenn-Ausführung), um die neuen Parameter hinzuzufügen:

    - if: inputs.environmentType == 'Production'
      uses: azure/arm-deploy@v1
      name: Run what-if
      with:
        failOnStdErr: false
        resourceGroupName: ${{ inputs.resourceGroupName }}
        template: ./deploy/main.bicep
        parameters: >
          environmentType=${{ inputs.environmentType }}
          reviewApiUrl=${{ inputs.reviewApiUrl }}
          reviewApiKey=${{ secrets.reviewApiKey }}
          sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
          sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
        additionalArguments: --what-if
    
  4. Aktualisieren Sie den Schritt Deploy Bicep file (Bicep-Datei bereitstellen) des Auftrags deploy, um die neuen Parameter hinzuzufügen:

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/arm-deploy@v1
        id: deploy
        name: Deploy Bicep file
        with:
          failOnStdErr: false
          deploymentName: ${{ github.run_number }}
          resourceGroupName: ${{ inputs.resourceGroupName }}
          template: ./deploy/main.bicep
          parameters: >
             environmentType=${{ inputs.environmentType }}
             reviewApiUrl=${{ inputs.reviewApiUrl }}
             reviewApiKey=${{ secrets.reviewApiKey }}
             sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
             sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
    
  5. Fügen Sie der deploy-Auftragsdefinition neue Ausgaben für die Bicep-Datei hinzu:

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
        storageAccountName: ${{ steps.deploy.outputs.storageAccountName }}
        storageAccountImagesBlobContainerName: ${{ steps.deploy.outputs.storageAccountImagesBlobContainerName }}
        sqlServerFullyQualifiedDomainName: ${{ steps.deploy.outputs.sqlServerFullyQualifiedDomainName }}
        sqlDatabaseName: ${{ steps.deploy.outputs.sqlDatabaseName }}
    

Hinzufügen von Aufträgen für Datenbank und Datenseeding

In diesem Abschnitt definieren Sie die Schritte, die zum Bereitstellen der Datenbankkomponenten Ihrer Website erforderlich sind. Zunächst fügen Sie einen Schritt zum Bereitstellen der DACPAC-Datei hinzu, die der Workflow zuvor erstellt hat. Anschließend fügen Sie der Datenbank und dem Speicherkonto Beispieldaten hinzu, jedoch nur für Nicht-Produktionsumgebungen.

  1. Fügen Sie unter dem Auftrag deploy-website einen neuen Auftrag zum Bereitstellen der DACPAC-Datei hinzu:

    deploy-database:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/download-artifact@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/sql-action@v1.2
        name: Deploy DACPAC to database
        with:
          server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
          dacpac-package: database/ToyCompany.Database.dacpac
    
  2. Definieren Sie unterhalb des soeben hinzugefügten Auftrags und oberhalb des Auftrags smoke-test einen neuen Auftrag für das Seeding der Datenbank mit Beispieldaten.

    seed-database:
      needs:
      - deploy
      - deploy-database
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - if: inputs.environmentType != 'Production'
        uses: azure/sql-action@v1.2
        name: Add test data to database
        with:
          server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
          sql-file: 'deploy/sample-data/Toys.sql'
    

    Beachten Sie, dass auf den Schritt Add test data to database (Testdaten zur Datenbank hinzufügen) eine Bedingung angewendet wurde. Sie wird nur für Nicht-Produktionsumgebungen ausgeführt. Die Bedingung gilt für den Schritt, nicht für den gesamten Auftrag, sodass spätere Aufträge unabhängig vom Umgebungstyp von diesem Auftrag abhängen können.

  3. Definieren Sie unterhalb des soeben hinzugefügten Auftrags und oberhalb des Auftrags smoke-test einen weiteren Auftrag, um mithilfe der Azure CLI einige Beispielbilder von Spielzeug in den Blobcontainer hochzuladen:

    seed-storage-account:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - if: inputs.environmentType != 'Production'
        uses: azure/CLI@v1
        name: Upload sample images
        with:
          inlineScript: |
              az storage blob upload-batch \
                --account-name ${{ needs.deploy.outputs.storageAccountName }} \
                --destination ${{ needs.deploy.outputs.storageAccountImagesBlobContainerName }} \
                --source 'deploy/sample-data/toyimages'
    

    Beachten Sie, dass dieser Auftrag einen Ubuntu-Läufer verwendet, da für die azure/cli Aktion Linux ausgeführt werden muss, aber der von Ihnen definierte build-database Auftrag verwendet einen Windows-Runner zum Erstellen des Datenbankprojekts. Dieser Workflow ist ein gutes Beispiel für das Verwenden verschiedener Betriebssysteme, um Ihre Anforderungen zu erfüllen.

Aktualisieren der Abhängigkeiten für den Auftrag „smoke-test“

  1. Aktualisieren Sie die Abhängigkeiten des Auftrags smoke-test, um sicherzustellen, dass er nach Abschluss aller Bereitstellungsschritte ausgeführt wird:

    smoke-test:
      runs-on: ubuntu-latest
      needs:
      - deploy
      - deploy-website
      - deploy-database
      - seed-database
      - seed-storage-account
      steps:
      - uses: actions/checkout@v3
      - run: |
          $container = New-PesterContainer `
            -Path 'deploy/Website.Tests.ps1' `
            -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
          Invoke-Pester `
            -Container $container `
            -CI
        name: Run smoke tests
        shell: pwsh
    
  2. Speichern Sie die geänderte Datei.

Überprüfen von Dateien und Commit ihrer Änderungen

  1. Überprüfen Sie, ob die Datei main.bicep wie folgt aussieht:

    @description('The location into which your Azure resources should be deployed.')
    param location string = resourceGroup().location
    
    @description('Select the type of environment you want to provision. Allowed values are Production and Test.')
    @allowed([
      'Production'
      'Test'
    ])
    param environmentType string
    
    @description('A unique suffix to add to resource names that need to be globally unique.')
    @maxLength(13)
    param resourceNameSuffix string = uniqueString(resourceGroup().id)
    
    @description('The URL to the product review API.')
    param reviewApiUrl string
    
    @secure()
    @description('The API key to use when accessing the product review API.')
    param reviewApiKey string
    
    @description('The administrator login username for the SQL server.')
    param sqlServerAdministratorLogin string
    
    @secure()
    @description('The administrator login password for the SQL server.')
    param sqlServerAdministratorLoginPassword string
    
    // Define the names for resources.
    var appServiceAppName = 'toy-website-${resourceNameSuffix}'
    var appServicePlanName = 'toy-website'
    var logAnalyticsWorkspaceName = 'workspace-${resourceNameSuffix}'
    var applicationInsightsName = 'toywebsite'
    var storageAccountName = 'mystorage${resourceNameSuffix}'
    var storageAccountImagesBlobContainerName = 'toyimages'
    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
    // Define the connection string to access Azure SQL.
    var sqlDatabaseConnectionString = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabase.name};Persist Security Info=False;User ID=${sqlServerAdministratorLogin};Password=${sqlServerAdministratorLoginPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
    
    // Define the SKUs for each component based on the environment type.
    var environmentConfigurationMap = {
      Production: {
        appServicePlan: {
          sku: {
            name: 'S1'
            capacity: 1
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_LRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
      Test: {
        appServicePlan: {
          sku: {
            name: 'F1'
          }
        }
        storageAccount: {
          sku: {
            name: 'Standard_GRS'
          }
        }
        sqlDatabase: {
          sku: {
            name: 'Standard'
            tier: 'Standard'
          }
        }
      }
    }
    
    resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
      name: appServicePlanName
      location: location
      sku: environmentConfigurationMap[environmentType].appServicePlan.sku
    }
    
    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
            {
              name: 'ReviewApiUrl'
              value: reviewApiUrl
            }
            {
              name: 'ReviewApiKey'
              value: reviewApiKey
            }
            {
              name: 'StorageAccountName'
              value: storageAccount.name
            }
            {
              name: 'StorageAccountBlobEndpoint'
              value: storageAccount.properties.primaryEndpoints.blob
            }
            {
              name: 'StorageAccountImagesContainerName'
              value: storageAccount::blobService::storageAccountImagesBlobContainer.name
            }
            {
              name: 'SqlDatabaseConnectionString'
              value: sqlDatabaseConnectionString
            }
          ]
        }
      }
    }
    
    resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
      name: logAnalyticsWorkspaceName
      location: location
    }
    
    resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
      name: applicationInsightsName
      location: location
      kind: 'web'
      properties: {
        Application_Type: 'web'
        Request_Source: 'rest'
        Flow_Type: 'Bluefield'
        WorkspaceResourceId: logAnalyticsWorkspace.id
      }
    }
    
    resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
      name: storageAccountName
      location: location
      kind: 'StorageV2'
      sku: environmentConfigurationMap[environmentType].storageAccount.sku
    
      resource blobService 'blobServices' = {
        name: 'default'
    
        resource storageAccountImagesBlobContainer 'containers' = {
          name: storageAccountImagesBlobContainerName
    
          properties: {
            publicAccess: 'Blob'
          }
        }
      }
    }
    
    resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = {
      name: sqlServerName
      location: location
      properties: {
        administratorLogin: sqlServerAdministratorLogin
        administratorLoginPassword: sqlServerAdministratorLoginPassword
      }
    }
    
    resource sqlServerFirewallRule 'Microsoft.Sql/servers/firewallRules@2022-05-01-preview' = {
      parent: sqlServer
      name: 'AllowAllWindowsAzureIps'
      properties: {
        endIpAddress: '0.0.0.0'
        startIpAddress: '0.0.0.0'
      }
    }
    
    resource sqlDatabase 'Microsoft.Sql/servers/databases@2022-05-01-preview' = {
      parent: sqlServer
      name: sqlDatabaseName
      location: location
      sku: environmentConfigurationMap[environmentType].sqlDatabase.sku
    }
    
    output appServiceAppName string = appServiceApp.name
    output appServiceAppHostName string = appServiceApp.properties.defaultHostName
    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
    output sqlDatabaseName string = sqlDatabase.name
    

    Falls nicht, aktualisieren Sie sie so, dass sie mit dem Dateiinhalt übereinstimmt.

  2. Überprüfen Sie, ob die Datei deploy.yml wie folgt aussieht:

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
          sqlServerAdministratorLogin:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
          sqlServerAdministratorLoginPassword:
            required: true
    
    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             deploymentMode: Validate
         - if: inputs.environmentType == 'Production'
           uses: azure/arm-deploy@v1
           name: Run what-if
           with:
             failOnStdErr: false
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             additionalArguments: --what-if
    
      deploy:
        needs: validate
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        outputs:
          appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
          appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
          storageAccountName: ${{ steps.deploy.outputs.storageAccountName }}
          storageAccountImagesBlobContainerName: ${{ steps.deploy.outputs.storageAccountImagesBlobContainerName }}
          sqlServerFullyQualifiedDomainName: ${{ steps.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          sqlDatabaseName: ${{ steps.deploy.outputs.sqlDatabaseName }}
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/arm-deploy@v1
          id: deploy
          name: Deploy Bicep file
          with:
            failOnStdErr: false
            deploymentName: ${{ github.run_number }}
            resourceGroupName: ${{ inputs.resourceGroupName }}
            template: ./deploy/main.bicep
            parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
    
      deploy-website:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/download-artifact@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/webapps-deploy@v2
          name: Deploy website
          with:
            app-name: ${{ needs.deploy.outputs.appServiceAppName }}
            package: website/publish.zip
    
      deploy-database:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/download-artifact@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - uses: azure/sql-action@v1.2
          name: Deploy DACPAC to database
          with:
            server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
            connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
            dacpac-package: database/ToyCompany.Database.dacpac
    
      seed-database:
        needs:
        - deploy
        - deploy-database
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - if: inputs.environmentType != 'Production'
          uses: azure/sql-action@v1.2
          name: Add test data to database
          with:
            server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
            connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
            sql-file: 'deploy/sample-data/Toys.sql'
    
      seed-storage-account:
        needs: deploy
        environment: ${{ inputs.environmentType }}
        runs-on: ubuntu-latest
        steps:
        - uses: actions/checkout@v3
        - uses: azure/login@v1
          name: Sign in to Azure
          with:
            client-id: ${{ secrets.AZURE_CLIENT_ID }}
            tenant-id: ${{ secrets.AZURE_TENANT_ID }}
            subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        - if: inputs.environmentType != 'Production'
          uses: azure/CLI@v1
          name: Upload sample images
          with:
            inlineScript: |
                az storage blob upload-batch \
                  --account-name ${{ needs.deploy.outputs.storageAccountName }} \
                  --destination ${{ needs.deploy.outputs.storageAccountImagesBlobContainerName }} \
                  --source 'deploy/sample-data/toyimages'
    
      smoke-test:
        runs-on: ubuntu-latest
        needs:
        - deploy
        - deploy-website
        - deploy-database
        - seed-database
        - seed-storage-account
        steps:
        - uses: actions/checkout@v3
        - run: |
            $container = New-PesterContainer `
              -Path 'deploy/Website.Tests.ps1' `
              -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
            Invoke-Pester `
              -Container $container `
              -CI
          name: Run smoke tests
          shell: pwsh
    

    Falls nicht, aktualisieren Sie sie so, dass sie mit dem Dateiinhalt übereinstimmt.

  3. Speichern Sie die geänderte Datei.

  4. Committen und pushen Sie Ihre Änderungen in Ihr Git-Repository. Führen Sie im Visual Studio Code-Terminal die folgenden Befehle aus:

    git add .
    git commit -m "Add SQL database"
    git push
    

Ausführen des Workflows

  1. Navigieren Sie in Ihrem Browser zu Ihren Workflowausführungen.

  2. Wählen Sie die letzte Ausführung aus.

    Warten Sie, bis alle Aufträge für die Testumgebung erfolgreich abgeschlossen wurden. Beachten Sie, dass die Feuerprobe jetzt auch erfolgreich ist.

    Screenshot: GitHub Actions mit Anzeige des Auftrags „smoke-test“ der Workflowausführung für die Testumgebung. Der Status zeigt, dass der Auftrag erfolgreich ausgeführt wurde.

  3. Warten Sie, bis der Workflow einschließlich der Produktionsbereitstellung erfolgreich abgeschlossen wurde.

Anzeigen der Website

  1. Wählen Sie den Auftrag deploy-test / deploy-website aus, um das Workflowprotokoll zu öffnen.

  2. Wählen Sie den Schritt Website bereitstellen aus.

    Halten Sie die STRG-TASTE gedrückt ( unter macOS), und wählen Sie die URL der App Service-App aus, um sie auf einer neuen Browserregisterkarte zu öffnen.

    Screenshot von GitHub-Aktionen mit dem Workflowprotokoll für den Auftrag zur Bereitstellungswebsite der Testumgebung. Die App-URL des App-Diensts ist hervorgehoben.

  3. Wählen Sie Toys aus.

    Screenshot der Homepage der Website des Spielzeugunternehmens mit hervorgehobener Verknüpfung „Toys“

    Beachten Sie, dass Beispieldaten in der Testumgebung angezeigt werden.

    Screenshot der Spielzeugseite der Testwebsite mit angezeigten Beispielen für Spielzeuge

  4. Wiederholen Sie den vorangegangenen Vorgang für die App des Auftrags deploy-production / deploy-website.

    Beachten Sie, dass in der Produktionsumgebung keine Beispieldaten angezeigt werden.

    Screenshot der Spielzeugseite der Produktionswebsite ohne Spielzeug

Bereinigen von Ressourcen

Nachdem Sie die Übung abgeschlossen haben, sollten Sie die Azure-Ressourcen entfernen, damit Ihnen dafür keine Gebühren berechnet werden.

Führen Sie im Visual Studio Code-Terminal die folgenden Befehle aus:

az group delete --resource-group ToyWebsiteTest --yes --no-wait
az group delete --resource-group ToyWebsiteProduction --yes --no-wait

Die Ressourcengruppe wird im Hintergrund gelöscht.

Remove-AzResourceGroup -Name ToyWebsiteTest -Force
Remove-AzResourceGroup -Name ToyWebsiteProduction -Force