練習 - 植入儲存體帳戶和資料庫

已完成

您已更新工作流程,以建置和部署網站的應用程式至 Bicep 檔案中定義的 Azure App Service 應用程式,但因資料庫尚未運行,而導致煙霧測試作業失敗。 在此單元中,您將部署新的 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 容器名稱:

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

    注意

    為了簡單起見,應用程式會使用管理員登入和密碼來存取資料庫。 不過,這不是用於實際執行解決方案的良好做法。 最好使用 App Service 受控識別來存取資料庫,並為受控識別授與應用程式所需的最低權限。 我們會在本課程模組的 [摘要] 頁面中連結至更多資訊。

  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. 針對資料庫連接字串,將其他應用程式設定新增至 App Service 應用程式:

    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 資料庫專案,以部署和設定您的網站資料庫資料表。 在這裡,您會更新名為 workflow 的工作流程「組建」,以將資料庫專案建置至 DACPAC 檔案,並將其上傳為工作流程成品。

  1. 開啟 .github/workflows 資料夾中的 build.yml 檔案。

  2. 若要建置 Visual Studio 資料庫專案,並將產生的 DACPAC 檔案上傳為工作流程成品,請新增 build-database 作業:

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

    build-database 作業會使用 Windows 執行器。 目前,Visual Studio 資料庫專案必須建置在 Windows 作業系統上。

  3. 儲存對檔案所做的變更。

定義祕密

您必須為每個環境安全地儲存 Azure SQL 邏輯伺服器的管理員密碼。 您決定使用 GitHub 祕密來保護資訊。

  1. 在瀏覽器中,移至 設定>秘密和變數>動作

    GitHub 的螢幕擷取畫面,其中顯示 [Settings] \(設定\) 類別下的 [Secrets] \(祕密\) 功能表項目。

  2. 選取 [新增存放庫祕密] 按鈕。

  3. 輸入 SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST 作為祕密名稱,並輸入 SecurePassword!111 作為值。

    顯示新祕密的 GitHub 螢幕擷取畫面。

  4. 選取 [新增祕密]。

  5. 重複此程序,新增以 SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION 作為祕密名稱的另一個祕密,並輸入 SecurePassword!999 作為值。 選取 [新增祕密]。

將祕密和輸入新增至工作流程

  1. 在 Visual Studio Code 中,開啟 .github/workflows 資料夾中的 deploy.yml 檔案。

  2. 在檔案頂端,定義名為 sqlServerAdministratorLogin 的新輸入,以及名為 sqlServerAdministratorLoginPassword 的新祕密:

    name: deploy
    
    on:
      workflow_call:
        inputs:
          environmentType:
            required: true
            type: string
          resourceGroupName:
            required: true
            type: string
          reviewApiUrl:
            required: true
            type: string
          sqlServerAdministratorLogin:
            required: true
            type: string
        secrets:
          AZURE_CLIENT_ID:
            required: true
          AZURE_TENANT_ID:
            required: true
          AZURE_SUBSCRIPTION_ID:
            required: true
          reviewApiKey:
            required: true
          sqlServerAdministratorLoginPassword:
            required: true
    
  3. 儲存對檔案所做的變更。

  4. 開啟 workflow.yml 檔案。

  5. deploy-test 定義中,定義 sqlServerAdministratorLogin 輸入的值,並傳播 sqlServerAdministratorLoginPassword 祕密的值:

    # Deploy to the test environment.
    deploy-test:
      uses: ./.github/workflows/deploy.yml
      needs: [build, lint]
      with:
        environmentType: Test
        resourceGroupName: ToyWebsiteTest
        reviewApiUrl: https://sandbox.contoso.com/reviews
        sqlServerAdministratorLogin: TestToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_TEST }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_TEST }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_TEST }}
    
  6. 使用實際執行環境的值,重複 deploy-production 定義中的程序:

    # Deploy to the production environment.
    deploy-production:
      uses: ./.github/workflows/deploy.yml
      needs:
      - lint
      - build
      - deploy-test
      with:
        environmentType: Production
        resourceGroupName: ToyWebsiteProduction
        reviewApiUrl: https://api.contoso.com/reviews
        sqlServerAdministratorLogin: ToyCompanyAdmin
      secrets:
        AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID_PRODUCTION }}
        AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
        AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
        reviewApiKey: ${{ secrets.REVIEW_API_KEY_PRODUCTION }}
        sqlServerAdministratorLoginPassword: ${{ secrets.SQL_SERVER_ADMINISTRATOR_LOGIN_PASSWORD_PRODUCTION }}
    
  7. 儲存對檔案所做的變更。

新增參數值和輸出

Bicep 檔案現在有兩個新的強制參數:sqlServerAdministratorLoginsqlServerAdministratorLoginPassword。 在這裡,您會針對 validatedeploy 作業,從工作流程輸入和祕密傳播這些參數值。 您也會將 Bicep 部署的輸出傳播至作業的輸出。

  1. 開啟 deploy.yml 檔案。

  2. 更新 validate 作業的 [Run preflight validation] \(執行預檢驗證\) 步驟,以新增參數:

    jobs:
      validate:
         runs-on: ubuntu-latest
         steps:
         - uses: actions/checkout@v3
         - uses: azure/login@v1
           name: Sign in to Azure
           with:
             client-id: ${{ secrets.AZURE_CLIENT_ID }}
             tenant-id: ${{ secrets.AZURE_TENANT_ID }}
             subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
         - if: inputs.environmentType != 'Production'
           uses: azure/arm-deploy@v1
           name: Run preflight validation
           with:
             deploymentName: ${{ github.run_number }}
             resourceGroupName: ${{ inputs.resourceGroupName }}
             template: ./deploy/main.bicep
             parameters: >
               environmentType=${{ inputs.environmentType }}
               reviewApiUrl=${{ inputs.reviewApiUrl }}
               reviewApiKey=${{ secrets.reviewApiKey }}
               sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
               sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
             deploymentMode: Validate
    
  3. 更新執行假設步驟以新增參數:

    - if: inputs.environmentType == 'Production'
      uses: azure/arm-deploy@v1
      name: Run what-if
      with:
        failOnStdErr: false
        resourceGroupName: ${{ inputs.resourceGroupName }}
        template: ./deploy/main.bicep
        parameters: >
          environmentType=${{ inputs.environmentType }}
          reviewApiUrl=${{ inputs.reviewApiUrl }}
          reviewApiKey=${{ secrets.reviewApiKey }}
          sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
          sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
        additionalArguments: --what-if
    
  4. 更新部署作業的部署 Bicep 檔案步驟,以新增參數:

    deploy:
      needs: validate
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      outputs:
        appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
        appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
      steps:
      - uses: actions/checkout@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/arm-deploy@v1
        id: deploy
        name: Deploy Bicep file
        with:
          failOnStdErr: false
          deploymentName: ${{ github.run_number }}
          resourceGroupName: ${{ inputs.resourceGroupName }}
          template: ./deploy/main.bicep
          parameters: >
             environmentType=${{ inputs.environmentType }}
             reviewApiUrl=${{ inputs.reviewApiUrl }}
             reviewApiKey=${{ secrets.reviewApiKey }}
             sqlServerAdministratorLogin=${{ inputs.sqlServerAdministratorLogin }}
             sqlServerAdministratorLoginPassword=${{ secrets.sqlServerAdministratorLoginPassword }}
    
  5. 作業的定義中,為 Bicep 檔案的輸出新增輸出:

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

新增資料庫和資料植入作業

在本節中,您會定義部署網站的資料庫元件所需的步驟。 首先,您會新增步驟來部署工作流程先前建置的 DACPAC 檔案。 然後,將範例資料新增至資料庫和儲存體帳戶,但僅適用非商業執行環境。

  1. deploy-website 作業下方,新增作業以部署 DACPAC 檔案:

    deploy-database:
      needs: deploy
      environment: ${{ inputs.environmentType }}
      runs-on: ubuntu-latest
      steps:
      - uses: actions/download-artifact@v3
      - uses: azure/login@v1
        name: Sign in to Azure
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - uses: azure/sql-action@v1.2
        name: Deploy DACPAC to database
        with:
          server-name: ${{ needs.deploy.outputs.sqlServerFullyQualifiedDomainName }}
          connection-string: ${{ format('Server={0};Initial Catalog={1};User Id={2};Password={3};', needs.deploy.outputs.sqlServerFullyQualifiedDomainName, needs.deploy.outputs.sqlDatabaseName, inputs.sqlServerAdministratorLogin, secrets.sqlServerAdministratorLoginPassword) }}
          dacpac-package: database/ToyCompany.Database.dacpac
    
  2. 在您剛剛新增的作業下方,以及 smoke-test 作業上方,定義新作業以使用範例資料植入資料庫。

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

    請注意,[Add test data to database] \(將測試資料新增至資料庫\) 步驟有套用的條件。 亦即,其只會針對非實際執行環境執行。 條件會套用至步驟,而不是整個作業,因此無論環境類型為何,後續作業都可以相依於此作業。

  3. 在您剛剛新增的作業下方,以及「煙霧測試」 作業上方,定義另一個作業,藉由使用 Azure CLI 將一些範例玩具影像上傳至 Blob 容器:

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

    請注意,此作業使用 Ubuntu 執行器,因為 azure/cli 動作需要透過 Linux 執行,但您先前定義的 build-database 作業使用 Windows 執行器來建置資料庫專案。 此工作流程是使用各種作業系統達成需求的良好範例。

更新煙霧測試作業的相依性

  1. 更新 smoke-test 作業的相依性,確定其在所有部署步驟完成之後執行:

    smoke-test:
      runs-on: ubuntu-latest
      needs:
      - deploy
      - deploy-website
      - deploy-database
      - seed-database
      - seed-storage-account
      steps:
      - uses: actions/checkout@v3
      - run: |
          $container = New-PesterContainer `
            -Path 'deploy/Website.Tests.ps1' `
            -Data @{ HostName = '${{needs.deploy.outputs.appServiceAppHostName}}' }
          Invoke-Pester `
            -Container $container `
            -CI
        name: Run smoke tests
        shell: pwsh
    
  2. 儲存對檔案所做的變更。

驗證檔案並認可您的變更

  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 檔案看起來像這樣:

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

    如果沒有,請更新以符合檔案內容。

  3. 儲存對檔案所做的變更。

  4. 認可並推送您的變更至 Git 存放庫。 在 Visual Studio Code 終端中,執行下列命令:

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

執行工作流程

  1. 在瀏覽器中,前往工作流程執行。

  2. 選取最近的執行。

    等候測試環境的所有作業都順利完成。 請注意,煙霧測試現在也會成功。

    GitHub Actions 的螢幕擷取畫面,其中顯示工作流程執行針對測試環境的煙霧測試作業。狀態顯示作業已成功。

  3. 等候工作流程順利完成,包括實際執行部署。

檢視網站

  1. 選取 deploy-test / deploy-website 作業以開啟工作流程記錄。

  2. 選取 [Deploy website] \(部署網站\) 步驟。

    按住 Ctrl 鍵 (macOS 上為 ),然後選取 App Service 應用程式的 URL,以在新瀏覽器索引標籤中將其開啟。

    GitHub Actions 的螢幕擷取畫面,其中顯示測試環境 deploy-website 作業的工作流程記錄。已醒目提示 App Service 應用程式 URL。

  3. 選取 [玩具]

    玩具公司網站首頁的螢幕擷取畫面,其中已醒目提示 [玩具] 連結。

    請注意,範例資料即會在測試環境中顯示。

    測試網站玩具頁面的螢幕擷取畫面,其中顯示範例玩具。

  4. 針對 deploy-production / deploy-website 作業的應用程式,重複上述程序。

    請注意,實際執行環境中不會顯示任何範例資料。

    實際執行網站上 [玩具] 頁面的螢幕擷取畫面,其中沒有顯示任何玩具。

清除資源

您已完成練習,現在建議您移除 Azure 資源,以免產生費用。

在 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