Übung: Seeding eines Speicherkontos und einer Datenbank

Abgeschlossen

Sie haben Ihre Pipeline aktualisiert, um die Anwendung Ihrer Website zu erstellen und für die Azure App Service-App bereitzustellen, die in Ihrer Bicep-Datei definiert ist. Die Feuerprobephase ist jedoch fehlgeschlagen, da die Datenbank noch nicht funktioniert. In dieser Lerneinheit stellen Sie einen neuen logischen Azure SQL-Server und eine Datenbank zur Verfügung und konfigurieren Ihre Pipeline für das Erstellen und Bereitstellen des Datenbankschemas. Außerdem aktualisieren Sie Ihre Pipeline, um einige Beispielproduktdaten für Ihre Testumgebung hinzuzufügen, damit Ihr Team die Website ausprobieren kann.

Im Rahmen des Vorgangs führen Sie die folgenden Aufgaben aus:

  • Fügen Sie dem Azure-Speicherkonto einen Blobcontainer hinzu.
  • Fügen Sie einen logischen Azure SQL-Server und eine Datenbank hinzu.
  • Aktualisieren Sie die Buildphase, um das Datenbankprojekt in einer DACPAC-Datei zu erstellen.
  • Fügen Sie Ihrer Variablengruppe neue Variablen für den logischen Azure SQL-Server und die Datenbank hinzu.
  • Aktualisieren Sie Ihre Bereitstellungsphasen, um die neuen Variablen als Parameterwerte zu verwenden.
  • Fügen Sie neue Pipelineschritte hinzu, um Ihre DACPAC-Datei bereitzustellen.
  • Führen Sie die Pipeline aus, und zeigen Sie die Website an.

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 drei neue Anwendungseinstellungen für den Speicherkontonamen, den Blobendpunkt und den Blobcontainernamen hinzuzufügen:

    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 der Datei 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 in 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. Links zu weiteren Informationen finden Sie in der Zusammenfassung.

  4. Fügen Sie am Ende der Datei 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 Werte sku 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 die Buildphase Ihrer Pipeline, um das Datenbankprojekt in einer DACPAC-Datei zu erstellen und als Pipelineartefakt zu veröffentlichen.

  1. Öffnen Sie im Ordner deploy/pipeline-templates die Datei build.yml.

  2. Zum Erstellen des Visual Studio-Datenbankprojekts kopieren Sie die generierte DACPAC-Datei in einen Stagingordner und veröffentlichen sie als Pipelineartefakt; fügen Sie die folgenden Schritte hinzu:

    jobs:
    - job: Build
      displayName: Build application and database
      pool:
        vmImage: windows-latest
    
      steps:
    
      # Build, copy, and publish the website.
      - task: DotNetCoreCLI@2
        displayName: Build publishable website
        inputs:
          command: 'publish'
          publishWebProjects: true
    
      - task: CopyFiles@2
        displayName: Copy publishable website
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Website/bin'
          contents: '**/publish.zip'
          targetFolder: '$(Build.ArtifactStagingDirectory)/website'
          flattenFolders: true
    
      - task: PublishBuildArtifacts@1
        displayName: Publish website as pipeline artifact
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)/website'
          artifactName: 'website'
    
      # Build, copy, and publish the DACPAC file.
      - task: VSBuild@1
        displayName: Build Visual Studio solution
        inputs:
          solution: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Database/ToyCompany.Database.sqlproj'
    
      - task: CopyFiles@2
        displayName: Copy DACPAC
        inputs:
          sourceFolder: '$(Build.SourcesDirectory)/src/ToyCompany/ToyCompany.Database/bin'
          contents: '**/*.dacpac'
          targetFolder: '$(Build.ArtifactStagingDirectory)/database'
          flattenFolders: true
    
      - task: PublishBuildArtifacts@1
        displayName: Publish DACPAC as pipeline artifact
        inputs:
          pathToPublish: '$(Build.ArtifactStagingDirectory)/database'
          artifactName: 'database'
    
  3. Speichern Sie die geänderte Datei.

Hinzufügen von Werten zu den Variablengruppen

  1. Wechseln Sie in Ihrem Browser zu Pipelines>Bibliothek.

  2. Wählen Sie die Variablengruppe ToyWebsiteProduction aus.

    Screenshot of Azure DevOps showing the list of variable groups, with the ToyWebsiteProduction variable group highlighted.

  3. Fügen Sie die folgenden Variablen zur Variablengruppe hinzu:

    Name Wert
    SqlServerAdministratorLogin ToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!111
  4. Wählen Sie das Vorhängeschlosssymbol neben der Variablen SqlServerAdministratorLoginPassword aus. Dieses Feature weist Azure Pipelines an, den Wert der Variablen sicher zu behandeln.

    Screenshot of the production variable group, with the secret variable button highlighted.

  5. Speichern Sie die Variablengruppe.

    Screenshot of the production variable group, with the Save button highlighted.

  6. Wiederholen Sie den Vorgang, um der Variablengruppe ToyWebsiteTest die folgenden Variablen hinzuzufügen:

    Name Wert
    SqlServerAdministratorLogin TestToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!999

    Denken Sie daran, das Vorhängeschlosssymbol neben der Variablen SqlServerAdministratorLoginPassword auszuwählen und die Variablengruppe zu speichern.

Hinzufügen von Parameterwerten zu den Phasen „Überprüfen“ und „Vorschau“

Die Bicep-Datei verfügt jetzt über zwei neue obligatorische Parameter: sqlServerAdministratorLogin und sqlServerAdministratorLoginPassword. Hier geben Sie diese Parameterwerte sowohl für die Überprüfungs- als auch für die Vorschauphase von Ihrer Variablengruppe weiter.

  1. Öffnen Sie in Visual Studio Code im Ordner deploy/pipeline-templates die Datei deploy.yml.

  2. Aktualisieren Sie den Schritt RunPreflightValidation der Überprüfungsphase, um die neuen Parameter hinzuzufügen.

    - task: AzureResourceManagerTemplateDeployment@3
      name: RunPreflightValidation
      displayName: Run preflight validation
      inputs:
        connectedServiceName: ToyWebsite${{parameters.environmentType}}
        location: ${{parameters.deploymentDefaultLocation}}
        deploymentMode: Validation
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
          -reviewApiUrl $(ReviewApiUrl)
          -reviewApiKey $(ReviewApiKey)
          -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
          -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
    
  3. Aktualisieren Sie den Schritt RunWhatIf der Vorschauphase, indem Sie die neuen Parameter hinzufügen.

    inlineScript: |
      az deployment group what-if \
        --resource-group $(ResourceGroupName) \
        --template-file deploy/main.bicep \
        --parameters environmentType=$(EnvironmentType) \
                     reviewApiUrl=$(ReviewApiUrl) \
                     reviewApiKey=$(ReviewApiKey) \
                     sqlServerAdministratorLogin=$(SqlServerAdministratorLogin) \
                     sqlServerAdministratorLoginPassword=$(SqlServerAdministratorLoginPassword)
    

    Wichtig

    Achten Sie darauf, den umgekehrten Schrägstrich (\) am Ende der Zeile, die den reviewApiKey-Parameterwert festlegt, und in der nachfolgenden Zeile hinzuzufügen. Das Zeichen \ gibt an, dass es weitere Zeilen gibt, die Teil desselben Azure CLI-Befehls sind.

Hinzufügen von Parameterwerten zur Bereitstellungsphase

  1. Aktualisieren Sie den Schritt DeployBicepFile der Bereitstellungsphase, indem Sie die neuen Parameter hinzufügen:

    - task: AzureResourceManagerTemplateDeployment@3
      name: DeployBicepFile
      displayName: Deploy Bicep file
      inputs:
        connectedServiceName: ToyWebsite${{parameters.environmentType}}
        deploymentName: $(Build.BuildNumber)
        location: ${{parameters.deploymentDefaultLocation}}
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
          -reviewApiUrl $(ReviewApiUrl)
          -reviewApiKey $(ReviewApiKey)
          -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
          -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
        deploymentOutputs: deploymentOutputs
    
  2. Erstellen Sie Pipelinevariablen, die die Werte der Bicep-Ausgaben enthalten, die Sie kürzlich für das Speicherkonto und die Azure SQL-Ressourcen hinzugefügt haben:

    - bash: |
        echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
        echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
        echo "##vso[task.setvariable variable=storageAccountName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountName.value')"
        echo "##vso[task.setvariable variable=storageAccountImagesBlobContainerName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountImagesBlobContainerName.value')"
        echo "##vso[task.setvariable variable=sqlServerFullyQualifiedDomainName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlServerFullyQualifiedDomainName.value')"
        echo "##vso[task.setvariable variable=sqlDatabaseName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlDatabaseName.value')"
      name: SaveDeploymentOutputs
      displayName: Save deployment outputs into variables
      env:
        DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    

Hinzufügen von Schritten für die Datenbankbereitstellung

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 die Pipeline 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 unterhalb des Schritts DeployWebsiteApp in der Bereitstellungsphase einen neuen Schritt zum Bereitstellen der DACPAC-Datei hinzu:

    - task: SqlAzureDacpacDeployment@1
      name: DeploySqlDatabaseDacpac
      displayName: Deploy DACPAC to database
      inputs:
        ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
        authenticationType: 'server'
        serverName: $(sqlServerFullyQualifiedDomainName)
        databaseName: $(sqlDatabaseName)
        sqlUsername: $(SqlServerAdministratorLogin)
        sqlPassword: $(SqlServerAdministratorLoginPassword)
        deployType: 'DacpacTask'
        deploymentAction: 'Publish'
        dacpacFile: '$(Pipeline.Workspace)/database/ToyCompany.Database.dacpac'
    
  2. Definieren Sie unterhalb des gerade hinzugefügten Schritts einen Schritt zum Seeding der Datenbank mit Beispieldaten.

    - ${{ if ne(parameters.environmentType, 'Production') }}:
      - task: SqlAzureDacpacDeployment@1
        name: AddTestDataToDatabase
        displayName: Add test data to database
        inputs:
          ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
          authenticationType: 'server'
          serverName: $(sqlServerFullyQualifiedDomainName)
          databaseName: $(sqlDatabaseName)
          sqlUsername: $(SqlServerAdministratorLogin)
          sqlPassword: $(SqlServerAdministratorLoginPassword)
          deployType: 'sqlTask'
          sqlFile: 'deploy/sample-data/Toys.sql'
    

    Beachten Sie, dass für diesen Schritt eine Bedingung angewendet wird, sodass er nur für Nicht-Produktionsumgebungen ausgeführt wird.

  3. Fügen Sie unterhalb des gerade hinzugefügten Schritts und im Bereich der Bedingung einen Schritt zum Hochladen einiger Spielzeug-Beispielbilder in den Blobcontainer mithilfe der Azure CLI hinzu:

    - task: AzureCLI@2
      name: UploadSampleImages
      displayName: Upload sample images
      inputs:
        azureSubscription: ToyWebsite${{parameters.environmentType}}
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          az storage blob upload-batch \
            --account-name $(storageAccountName) \
            --destination $(storageAccountImagesBlobContainerName) \
            --source 'deploy/sample-data/toyimages'
    

Ü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:

    parameters:
    - name: environmentType
      type: string
    - name: deploymentDefaultLocation
      type: string
      default: westus3
    
    stages:
    
    - ${{ if ne(parameters.environmentType, 'Production') }}:
      - stage: Validate_${{parameters.environmentType}}
        displayName: Validate (${{parameters.environmentType}} Environment)
        jobs:
        - job: ValidateBicepCode
          displayName: Validate Bicep code
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureResourceManagerTemplateDeployment@3
              name: RunPreflightValidation
              displayName: Run preflight validation
              inputs:
                connectedServiceName: ToyWebsite${{parameters.environmentType}}
                location: ${{parameters.deploymentDefaultLocation}}
                deploymentMode: Validation
                resourceGroupName: $(ResourceGroupName)
                csmFile: deploy/main.bicep
                overrideParameters: >
                  -environmentType $(EnvironmentType)
                  -reviewApiUrl $(ReviewApiUrl)
                  -reviewApiKey $(ReviewApiKey)
                  -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
                  -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
    
    - ${{ if eq(parameters.environmentType, 'Production') }}:
      - stage: Preview_${{parameters.environmentType}}
        displayName: Preview (${{parameters.environmentType}} Environment)
        jobs:
        - job: PreviewAzureChanges
          displayName: Preview Azure changes
          variables:
          - group: ToyWebsite${{parameters.environmentType}}
          steps:
            - task: AzureCLI@2
              name: RunWhatIf
              displayName: Run what-if
              inputs:
                azureSubscription: ToyWebsite${{parameters.environmentType}}
                scriptType: 'bash'
                scriptLocation: 'inlineScript'
                inlineScript: |
                  az deployment group what-if \
                    --resource-group $(ResourceGroupName) \
                    --template-file deploy/main.bicep \
                    --parameters environmentType=$(EnvironmentType) \
                                 reviewApiUrl=$(ReviewApiUrl) \
                                 reviewApiKey=$(ReviewApiKey) \
                                 sqlServerAdministratorLogin=$(SqlServerAdministratorLogin) \
                                 sqlServerAdministratorLoginPassword=$(SqlServerAdministratorLoginPassword)
    
    - stage: Deploy_${{parameters.environmentType}}
      displayName: Deploy (${{parameters.environmentType}} Environment)
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        pool:
          vmImage: windows-latest
        variables:
        - group: ToyWebsite${{parameters.environmentType}}
        environment: ${{parameters.environmentType}}
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
    
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: ToyWebsite${{parameters.environmentType}}
                    deploymentName: $(Build.BuildNumber)
                    location: ${{parameters.deploymentDefaultLocation}}
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                      -reviewApiUrl $(ReviewApiUrl)
                      -reviewApiKey $(ReviewApiKey)
                      -sqlServerAdministratorLogin $(SqlServerAdministratorLogin)
                      -sqlServerAdministratorLoginPassword $(SqlServerAdministratorLoginPassword)
                    deploymentOutputs: deploymentOutputs
    
                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppName.value')"
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                    echo "##vso[task.setvariable variable=storageAccountName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountName.value')"
                    echo "##vso[task.setvariable variable=storageAccountImagesBlobContainerName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.storageAccountImagesBlobContainerName.value')"
                    echo "##vso[task.setvariable variable=sqlServerFullyQualifiedDomainName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlServerFullyQualifiedDomainName.value')"
                    echo "##vso[task.setvariable variable=sqlDatabaseName]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.sqlDatabaseName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
                - task: AzureRmWebAppDeployment@4
                  name: DeployWebsiteApp
                  displayName: Deploy website
                  inputs:
                    appType: webApp
                    ConnectionType: AzureRM
                    azureSubscription: ToyWebsite${{parameters.environmentType}}
                    ResourceGroupName: $(ResourceGroupName)
                    WebAppName: $(appServiceAppName)
                    Package: '$(Pipeline.Workspace)/website/publish.zip'
    
                - task: SqlAzureDacpacDeployment@1
                  name: DeploySqlDatabaseDacpac
                  displayName: Deploy DACPAC to database
                  inputs:
                    ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
                    authenticationType: 'server'
                    serverName: $(sqlServerFullyQualifiedDomainName)
                    databaseName: $(sqlDatabaseName)
                    sqlUsername: $(SqlServerAdministratorLogin)
                    sqlPassword: $(SqlServerAdministratorLoginPassword)
                    deployType: 'DacpacTask'
                    deploymentAction: 'Publish'
                    dacpacFile: '$(Pipeline.Workspace)/database/ToyCompany.Database.dacpac'
    
                - ${{ if ne(parameters.environmentType, 'Production') }}:
                  - task: SqlAzureDacpacDeployment@1
                    name: AddTestDataToDatabase
                    displayName: Add test data to database
                    inputs:
                      ConnectedServiceNameARM: ToyWebsite${{parameters.environmentType}}
                      authenticationType: 'server'
                      serverName: $(sqlServerFullyQualifiedDomainName)
                      databaseName: $(sqlDatabaseName)
                      sqlUsername: $(SqlServerAdministratorLogin)
                      sqlPassword: $(SqlServerAdministratorLoginPassword)
                      deployType: 'sqlTask'
                      sqlFile: 'deploy/sample-data/Toys.sql'
    
                  - task: AzureCLI@2
                    name: UploadSampleImages
                    displayName: Upload sample images
                    inputs:
                      azureSubscription: ToyWebsite${{parameters.environmentType}}
                      scriptType: 'bash'
                      scriptLocation: 'inlineScript'
                      inlineScript: |
                        az storage blob upload-batch \
                          --account-name $(storageAccountName) \
                          --destination $(storageAccountImagesBlobContainerName) \
                          --source 'deploy/sample-data/toyimages'
    
    - stage: SmokeTest_${{parameters.environmentType}}
      displayName: Smoke Test (${{parameters.environmentType}} Environment)
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy_${{parameters.environmentType}}.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
        steps:
          - task: PowerShell@2
            name: RunSmokeTests
            displayName: Run smoke tests
            inputs:
              targetType: inline
              script: |
                $container = New-PesterContainer `
                  -Path 'deploy/Website.Tests.ps1' `
                  -Data @{ HostName = '$(appServiceAppHostName)' }
                Invoke-Pester `
                  -Container $container `
                  -CI
    
          - task: PublishTestResults@2
            name: PublishTestResults
            displayName: Publish test results
            condition: always()
            inputs:
              testResultsFormat: NUnit
              testResultsFiles: 'testResults.xml'
    

    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
    

Führen Sie die Pipeline aus.

  1. Wechseln Sie in Ihrem Browser zu Pipelines.

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

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

    Screenshot of Azure DevOps showing the pipeline run's Smoke Test stage for the test environment. The status shows that the stage succeeded.

  3. Warten Sie, bis die Pipeline vor der Phase Vorschau (Produktionsumgebung) noch mal angehalten wird, da sie dieses Mal die Berechtigung für eine andere Variablengruppe benötigt.

    Screenshot of Azure DevOps showing the pipeline run paused at the Deploy stage. Permission is required to continue. The View button is highlighted.

  4. Wählen Sie Anzeigen und dann Zulassen>Zulassen aus.

    Die Phase Vorschau (Produktionsumgebung) wurde erfolgreich abgeschlossen.

  5. Überwachen Sie die Pipeline beim Abschließen der letzten Phasen.

    Die Phase Bereitstellen (Produktionsumgebung) wird erfolgreich abgeschlossen, und die Phase Feuerprobe (Produktionsumgebung) wird ebenfalls erfolgreich abgeschlossen.

    Screenshot of Azure DevOps showing the pipeline run with all stages showing success.

Anzeigen der Website

  1. Wählen Sie die Phase Bereitstellen (Testumgebung) aus, um das Pipelineprotokoll 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 of Azure DevOps showing the pipeline run log for the test environment's Deploy stage. The URL of the App Service app is highlighted.

  3. Wählen Sie Toys aus.

    Screenshot of the toy company website homepage, with the Toys link highlighted.

    Beachten Sie, dass Beispieldaten in der Testumgebung angezeigt werden.

    Screenshot of the test website's toy page, with the sample toys displayed.

  4. Wiederholen Sie den vorherigen Prozess für die App der Phase Bereitstellen (Produktionsumgebung).

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

    Screenshot of the production website's toy page, with no toys displayed.

Bereinigen von Ressourcen

Nachdem die Übung abgeschlossen ist, müssen Sie die 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