练习 - 为存储帐户和数据库设定种子

已完成

对管道进行更新以生成网站的应用程序,并将其部署到 Bicep 文件中定义的 Azure 应用服务应用。 但由于数据库还未开始工作,版本验收测试阶段失败。 在本单元中,你将部署新的 Azure SQL 逻辑服务器和数据库,并配置管道以生成和部署数据库的架构。 此外,你将更新管道,为测试环境添加一些示例产品数据,以便团队可以试用该网站。

在此过程中,你将执行以下任务:

  • 将 Blob 容器添加到 Azure 存储帐户。
  • 添加 Azure SQL 逻辑服务器和数据库。
  • 更新生成阶段以将数据库项目生成到 DACPAC 文件中。
  • 将新变量添加到 Azure SQL 逻辑服务器和数据库的变量组。
  • 更新部署阶段,将新变量用作参数值。
  • 添加新的管道步骤以部署 DACPAC 文件。
  • 运行管道并查看网站。

添加存储容器

Bicep 文件已经定义了一个存储帐户,但没有定义 Blob 容器。 将在此处将 Blob 容器添加到 Bicep 文件。 还需使用应用程序的配置设置为应用程序提供存储帐户和 Blob 容器的名称。 这样,应用便知道要访问哪个存储帐户。

  1. 在 Visual Studio Code 中,打开 deploy 文件夹中的 main.bicep 文件。

  2. 在定义资源名称的变量下方(第 27 行附近),为 Blob 存储容器的名称添加新的变量定义:

    var storageAccountImagesBlobContainerName = 'toyimages'
    
  3. 更新 storageAccount 资源以定义 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. 更新应用的 appSettings 属性以添加三个新的应用程序设置,用于存储帐户名称、Blob 终结点和 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. 在文件的末尾,添加新的输出以公开存储帐户和 Blob 容器的名称:

    output storageAccountName string = storageAccount.name
    output storageAccountImagesBlobContainerName string = storageAccount::blobService::storageAccountImagesBlobContainer.name
    
  6. 保存对该文件所做的更改。

  7. 将更改提交到 Git 存储库,但不要对其进行推送。 在 Visual Studio Code 终端中,运行以下命令:

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

添加 Azure SQL 逻辑服务器和数据库

Bicep 文件当前未部署 Azure SQL 逻辑服务器或数据库。 在本部分中,你将这些资源添加到 Bicep 文件。

  1. 在 main.bicep 文件中,在 reviewApiKey 参数下添加两个新参数:

    @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. 在定义资源名称的变量下方,添加新变量以定义 Azure SQL 逻辑服务器和数据库的名称:

    var sqlServerName = 'toy-website-${resourceNameSuffix}'
    var sqlDatabaseName = 'Toys'
    
  3. 在刚添加的变量下面,定义一个新变量,该变量会创建应用程序用于访问数据库的连接字符串:

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

    注意

    为简单起见,应用程序将使用管理员登录名和密码来访问数据库。 但对于生产解决方案而言,这并非是很好的做法。 最好使用应用服务托管标识来访问数据库,并向托管标识授予应用程序所需的最小权限。 在总结页面中,我们提供了有关更多信息的链接。

  4. 在文件内容末尾附近的输出上方,添加 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. 更新 environmentConfigurationMap 变量,以定义要用于每个环境的数据库的 sku 值:

    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. 在应用服务应用中为数据库连接字符串添加额外的应用设置:

    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. 在文件底部,添加输出以公开 Azure SQL 逻辑服务器的主机名和数据库名称:

    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. 保存对该文件所做的更改。

为数据库项目添加新的生成步骤

网站开发人员已准备好 Visual Studio 数据库项目,该项目会部署并配置网站数据库表。 在这里,你将更新管道“生成”阶段,以将数据库项目生成到 DACPAC 文件中,并将其发布为管道工件。

  1. 打开 deploy/pipeline-templates 文件夹中的 build.yml 文件。

  2. 若要生成 Visual Studio 数据库项目,请将生成的 DACPAC 文件复制到暂存文件夹,并将其发布为管道工件,添加以下步骤:

    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. 保存对该文件所做的更改。

向变量组添加值

  1. 在浏览器中,转到“管道”>“库”。

  2. 选择 ToyWebsiteProduction 变量组。

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

  3. 将以下变量添加到变量组:

    名称
    SqlServerAdministratorLogin ToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!111
  4. 选择 SqlServerAdministratorLoginPassword 变量旁边的挂锁图标。 此功能指示 Azure Pipelines 安全地处理变量的值。

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

  5. 保存变量组。

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

  6. 重复此过程,将以下变量添加到 ToyWebsiteTest 变量组:

    名称
    SqlServerAdministratorLogin TestToyCompanyAdmin
    SqlServerAdministratorLoginPassword SecurePassword!999

    请记住选择 SqlServerAdministratorLoginPassword 变量旁边的挂锁图标并保存变量组。

将参数值添加到“验证”和“预览”阶段

Bicep 文件现在具有两个新的必需参数:sqlServerAdministratorLoginsqlServerAdministratorLoginPassword。 在这里,你将从变量组中传播这些参数值,以用于“验证”和“预览”阶段。

  1. 在 Visual Studio Code 中,打开 deploy/pipeline-templates 文件夹中的 deploy.yml 文件。

  2. 通过添加新参数,更新“验证”阶段的 RunPreflightValidation 步骤

    - 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. 通过添加新参数,更新“预览”阶段的 RunWhatIf 步骤

    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)
    

    重要

    请务必在设置 reviewApiKey 参数值的行的末尾以及后续行中添加反斜杠字符 (\)。 \ 字符指示同一 Azure CLI 命令中还有其他行。

添加参数值到“部署”阶段

  1. 通过添加新参数,更新“部署”阶段的 DeployBicepFile 步骤

    - 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. 创建管道变量,其中包含最近为存储帐户和 Azure SQL 资源添加的 Bicep 输出值:

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

添加数据库部署步骤

在本部分中,将定义部署网站的数据库组件所需的步骤。 首先,添加一个步骤来部署管道先前生成的 DACPAC 文件。 然后,将示例数据添加到数据库和存储帐户,但仅适用于非生产性环境。

  1. 在“部署”阶段的 DeployWebsiteApp 步骤下,添加新的步骤来部署 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. 在刚添加的步骤下,定义一个步骤,以使用示例数据为数据库设定种子。

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

    请注意,此步骤应用了一个条件,以便它仅在非生产性环境中运行。

  3. 在刚添加并仍处于条件范围内的步骤下,添加一个步骤,使用 Azure CLI 将一些示例玩具图像上传到 Blob 容器:

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

验证文件并提交更改

  1. 验证 main.bicep 文件是否如下所示:

    @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
    

    如果不是,则对其进行更新以匹配文件内容。

  2. 验证 deploy.yml 文件是否如下所示:

    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'
    

    如果不是,则对其进行更新以匹配文件内容。

  3. 保存对该文件所做的更改。

  4. 提交所做的更改并将其推送到 Git 存储库。 在 Visual Studio Code 终端中,运行以下命令:

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

运行管道

  1. 在浏览器中,转到“管道”。

  2. 选择管道的最新运行。

    等待测试环境的所有阶段都成功完成。 请注意,版本验收测试现在也成功了。

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

  3. 在“预览(生产环境)”阶段之前等待管道再次暂停,因为这次它需要其他变量组的权限。

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

  4. 选择“查看”,然后选择“允许”>“允许”。

    “预览(生产环境)”阶段成功完成。

  5. 在管道完成最后阶段时对其进行监视。

    “部署(生产环境)”阶段成功完成,“版本验收测试(生产环境)”阶段也成功完成。

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

查看网站

  1. 选择“部署(测试环境)”阶段,以打开管道日志。

  2. 选择“部署网站”步骤。

    按住 Ctrl 键(在 macOS 上为 ),并选择应用服务应用的 URL 以在新的浏览器标签页中将其打开。

    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. 选择“玩具”。

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

    请注意,示例数据显示在测试环境中。

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

  4. 对“部署(生产环境)”阶段的应用重复上述过程。

    请注意,生产环境中未显示任何示例数据。

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

清理资源

完成练习后,应移除相应资源,以免因其而产生费用。

在 Visual Studio Code 终端中,运行以下命令:

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

资源组将在后台删除。

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