Ćwiczenie — inicjowanie konta magazynu i bazy danych

Ukończone

Zaktualizowano potok w celu skompilowania i wdrożenia aplikacji witryny internetowej w aplikacji usługi aplikacja systemu Azure Service zdefiniowanej w pliku Bicep. Jednak etap testu weryfikacyjnego kompilacji kończy się niepowodzeniem, ponieważ baza danych nie działa jeszcze. W tej lekcji wdrożysz nowy serwer logiczny i bazę danych usługi Azure SQL oraz skonfigurujesz potok w celu skompilowania i wdrożenia schematu bazy danych. Zaktualizujesz również potok, aby dodać przykładowe dane produktów dla środowiska testowego, aby zespół mógł wypróbować witrynę internetową.

W tym procesie wykonujesz następujące zadania:

  • Dodaj kontener obiektów blob do konta usługi Azure Storage.
  • Dodaj serwer logiczny i bazę danych usługi Azure SQL.
  • Zaktualizuj etap kompilacji, aby skompilować projekt bazy danych w pliku DACPAC.
  • Dodaj nowe zmienne do grupy zmiennych dla serwera logicznego i bazy danych azure SQL.
  • Zaktualizuj etapy wdrażania, aby użyć nowych zmiennych jako wartości parametrów.
  • Dodaj nowe kroki potoku, aby wdrożyć plik DACPAC.
  • Uruchom potok i wyświetl witrynę internetową.

Dodawanie kontenera magazynu

Plik Bicep definiuje już konto magazynu, ale nie definiuje kontenera obiektów blob. W tym miejscu dodasz kontener obiektów blob do pliku Bicep. Należy również podać nazwę konta magazynu i kontenera obiektów blob do aplikacji przy użyciu jej ustawień konfiguracji. Dzięki temu aplikacja wie, do którego konta magazynu ma uzyskać dostęp.

  1. W programie Visual Studio Code otwórz plik main.bicep w folderze deploy .

  2. Poniżej zmiennych definiujących nazwy zasobów (w pobliżu wiersza 27) dodaj nową definicję zmiennej dla nazwy kontenera magazynu obiektów blob:

    var storageAccountImagesBlobContainerName = 'toyimages'
    
  3. Zaktualizuj zasób, storageAccount aby zdefiniować kontener obiektów blob:

    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. Zaktualizuj właściwość aplikacji appSettings , aby dodać trzy nowe ustawienia aplikacji dla nazwy konta magazynu, punktu końcowego obiektu blob i nazwy kontenera obiektów blob:

    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. Na końcu pliku dodaj nowe dane wyjściowe, aby uwidocznić nazwy konta magazynu i kontenera obiektów blob:

    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    
  6. Zapisz zmiany w pliku.

  7. Zatwierdź zmiany w repozytorium Git, ale nie wypchnij ich jeszcze. W terminalu programu Visual Studio Code uruchom następujące polecenia:

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

Dodawanie serwera logicznego i bazy danych azure SQL

Plik Bicep nie wdraża obecnie serwera logicznego ani bazy danych Azure SQL. W tej sekcji dodasz te zasoby do pliku Bicep.

  1. W pliku main.bicep dodaj dwa nowe parametry poniżej parametrureviewApiKey:

    @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. Poniżej zmiennych definiujących nazwy zasobów dodaj nowe zmienne, aby zdefiniować nazwy serwera logicznego i bazy danych Azure SQL:

    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
  3. Poniżej właśnie dodanych zmiennych zdefiniuj nową zmienną, która tworzy parametry połączenia dla aplikacji w celu uzyskania dostępu do bazy danych:

    // 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;'
    

    Uwaga

    Dla uproszczenia aplikacja używa identyfikatora logowania administratora i hasła w celu uzyskania dostępu do bazy danych. Nie jest to jednak dobre rozwiązanie dla rozwiązania produkcyjnego. Lepiej użyć tożsamości zarządzanej usługi App Service w celu uzyskania dostępu do bazy danych i przyznać tożsamości zarządzanej minimalne uprawnienia wymagane przez aplikację. Link do dodatkowych informacji na stronie Podsumowanie.

  4. Na końcu pliku powyżej danych wyjściowych dodaj zasoby serwera logicznego i bazy danych Azure SQL:

    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. Zaktualizuj zmienną environmentConfigurationMap , aby zdefiniować sku wartości używane dla bazy danych dla każdego środowiska:

    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. Dodaj kolejne ustawienie aplikacji do aplikacji usługi App Service dla bazy danych parametry połączenia:

    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. W dolnej części pliku dodaj dane wyjściowe, aby uwidocznić nazwę hosta serwera logicznego Azure SQL i nazwę bazy danych:

    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. Zapisz zmiany w pliku.

Dodawanie nowych kroków kompilacji dla projektu bazy danych

Deweloperzy witryny internetowej przygotowali projekt bazy danych programu Visual Studio, który wdraża i konfiguruje tabelę bazy danych witryny internetowej. W tym miejscu zaktualizujesz etap kompilacji potoku, aby skompilować projekt bazy danych w pliku DACPAC i opublikować go jako artefakt potoku.

  1. Otwórz plik build.yml w folderze deploy/pipeline-templates.

  2. Aby skompilować projekt bazy danych programu Visual Studio, skopiuj wygenerowany plik DACPAC do folderu przejściowego i opublikuj go jako artefakt potoku, dodaj następujące kroki:

    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. Zapisz zmiany w pliku.

Dodawanie wartości do grup zmiennych

  1. W przeglądarce przejdź do pozycji Biblioteka potoków>.

  2. Wybierz grupę zmiennych ToyWebsiteProduction .

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

  3. Dodaj do grupy zmiennych następujące zmienne:

    Nazwa/nazwisko Wartość
    SqlServer Administracja istratorLogin ToyCompany Administracja
    SqlServer Administracja istratorLoginPassword SecurePassword!111
  4. Wybierz ikonę kłódki obok zmiennej SqlServer Administracja istratorLoginPassword. Ta funkcja informuje usługę Azure Pipelines o bezpiecznym traktowaniu wartości zmiennej.

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

  5. Zapisz grupę zmiennych.

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

  6. Powtórz proces, aby dodać następujące zmienne do grupy zmiennych ToyWebsiteTest :

    Nazwa/nazwisko Wartość
    SqlServer Administracja istratorLogin TestToyCompany Administracja
    SqlServer Administracja istratorLoginPassword SecurePassword!999

    Pamiętaj, aby wybrać ikonę kłódki obok zmiennej SqlServer Administracja istratorLoginPassword i zapisać grupę zmiennych.

Dodawanie wartości parametrów do etapów weryfikacji i podglądu

Plik Bicep ma teraz dwa nowe obowiązkowe parametry: sqlServerAdministratorLogin i sqlServerAdministratorLoginPassword. W tym miejscu propagujesz te wartości parametrów z grupy zmiennych zarówno dla etapów Weryfikacji, jak i Podgląd.

  1. W programie Visual Studio Code otwórz plik deploy.yml w folderze deploy/pipeline-templates .

  2. Zaktualizuj krok RunPreflightValidation etapu Weryfikacji, dodając nowe parametry.

    - 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. Zaktualizuj krok RunWhatIf etapu podglądu, dodając nowe parametry.

    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)
    

    Ważne

    Pamiętaj, aby dodać znak ukośnika odwrotnego (\) na końcu wiersza, który ustawia reviewApiKey wartość parametru i w kolejnym wierszu. Znak \ wskazuje, że istnieją kolejne wiersze, które są częścią tego samego polecenia interfejsu wiersza polecenia platformy Azure.

Dodawanie wartości parametrów do etapu Wdrażanie

  1. Zaktualizuj krok DeployBicepFile etapu wdrażania, dodając nowe parametry:

    - 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. Utwórz zmienne potoku zawierające wartości danych wyjściowych Bicep, które zostały ostatnio dodane dla konta magazynu i zasobów usługi Azure SQL:

    - 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)
    

Dodawanie kroków wdrażania bazy danych

W tej sekcji zdefiniujesz kroki wymagane do wdrożenia składników bazy danych witryny internetowej. Najpierw należy dodać krok wdrażania pliku DACPAC utworzonego wcześniej potoku. Następnie dodasz przykładowe dane do bazy danych i konta magazynu, ale tylko dla środowisk nieprodukcyjnych.

  1. Poniżej kroku DeployWebsiteApp na etapie Wdrażania dodaj nowy krok w celu wdrożenia pliku DACPAC:

    - 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. Poniżej właśnie dodanego kroku zdefiniuj krok inicjowania bazy danych z przykładowymi danymi.

    - ${{ 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'
    

    Zwróć uwagę, że ten krok ma zastosowany warunek, aby był uruchamiany tylko dla środowisk nieprodukcyjnych.

  3. Poniżej właśnie dodanego kroku i nadal w zakresie warunku dodaj krok, aby przekazać przykładowe obrazy do kontenera obiektów blob przy użyciu interfejsu wiersza polecenia platformy Azure:

    - 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'
    

Weryfikowanie plików i zatwierdzanie zmian

  1. Sprawdź, czy plik main.bicep wygląda następująco:

    @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
    

    Jeśli tak nie jest, zaktualizuj ją tak, aby była zgodna z zawartością pliku.

  2. Sprawdź, czy plik deploy.yml wygląda następująco:

    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'
    

    Jeśli tak nie jest, zaktualizuj ją tak, aby była zgodna z zawartością pliku.

  3. Zapisz zmiany w pliku.

  4. Zatwierdź i wypchnij zmiany do repozytorium Git. W terminalu programu Visual Studio Code uruchom następujące polecenia:

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

Uruchamianie potoku

  1. W przeglądarce przejdź do pozycji Potoki.

  2. Wybierz najnowszy przebieg potoku.

    Poczekaj, aż wszystkie etapy środowiska testowego zakończą się pomyślnie. Zwróć uwagę, że test weryfikacyjny kompilacji również zakończy się pomyślnie.

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

  3. Poczekaj na ponowne wstrzymanie potoku przed etapem Wersja zapoznawcza (środowisko produkcyjne), ponieważ tym razem potrzebuje uprawnień do innej grupy zmiennych.

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

  4. Wybierz pozycję Widok, a następnie wybierz pozycję Zezwól na zezwolenie>.

    Etap Wersja zapoznawcza (środowisko produkcyjne) kończy się pomyślnie.

  5. Monitoruj potok w miarę kończe ostatnich etapów.

    Etap Deploy (Production Environment) zakończy się pomyślnie, a etap Testu weryfikacyjnego kompilacji (środowisko produkcyjne) również zakończy się pomyślnie.

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

Wyświetlanie witryny internetowej

  1. Wybierz etap Deploy (Test Environment), aby otworzyć dziennik potoku.

  2. Wybierz krok Wdróż witrynę internetową .

    Przytrzymaj wciśnięty klawisz Ctrl (} w systemie macOS) i wybierz adres URL aplikacji usługi App Service, otwierając go na nowej karcie przeglądarki.

    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. Wybierz pozycję Zabawki.

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

    Zwróć uwagę, że przykładowe dane są wyświetlane w środowisku testowym.

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

  4. Powtórz poprzedni proces dla aplikacji etapu Wdrażanie (środowisko produkcyjne).

    Zwróć uwagę, że w środowisku produkcyjnym nie są wyświetlane żadne przykładowe dane.

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

Czyszczenie zasobów

Po zakończeniu ćwiczenia należy usunąć zasoby, aby nie były naliczane opłaty.

W terminalu programu Visual Studio Code uruchom następujące polecenia:

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

Grupa zasobów jest usuwana w tle.

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