ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

Azure Pipelines で Azure Storage の Static website のパイプラインを構築する

先日の記事で、Azure DevOps の Multi-Stage Pipelines を使って Azure Web Apps と Functions にデプロイする内容を記載しました。
gooner.hateblo.jp
gooner.hateblo.jp
今回は、Azure Storage の Static website 向けのパイプラインを構築します。Static website の場合、カスタムドメインで HTTPS を構成するために、Azure CDN や Front Door を配置する必要があります。今回は、Azure CDN を配置する構成を選択しました。アプリケーションは、Vue.js をデプロイします。

パイプラインの全体設計

先日の記事と同様に、パイプラインは、自動デプロイされる開発環境向け(Commit Stage)と承認デプロイされる本番環境向け(Production Stage)を作ります。YAML を Build と Release を分けて、可変部をパラメータで切り替えできるテンプレートを作ることで再利用性を向上させます。

開発環境向けパイプライン

まずは、パイプライン起動用の azure-pipelines.yml です。

trigger:
- master

variables:
- group: variable-group-commit
- name: imageName
  value: 'ubuntu-latest'
- name: nodeVersion
  value: '10.x'
- name: azureSubscription
  value: 'AzureSponsorships'
- name: environment
  value: 'Commit-Stage'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      nodeVersion: $(nodeVersion)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      storageAccountName: $(storageAccountName)
      environment: $(environment)
      cdnResourseGroupName: $(cdnResourseGroupName)
      cdnEndpointName: $(cdnEndpointName)
      cdnProfileName: $(cdnProfileName)

Azure Web Apps との違いは、Azure Storage と CDN の名前を渡している部分ぐらいで、全体の構成は変わりません。

次に、ビルド用の build-pipelines.yml です。

parameters:
  imageName: ''
  nodeVersion: ''

jobs:
- job: Build
  pool:
    vmImage: ${{parameters.imageName}}
  steps:
  - task: NodeTool@0
    inputs:
      versionSpec: ${{parameters.nodeVersion}}
    displayName: 'Install Node.js'
  
  - script: |
      npm install
      npm run build
    displayName: 'npm install and build'
  
  - task: ArchiveFiles@2
    inputs:
      rootFolderOrFile: '$(Build.SourcesDirectory)/dist'
      includeRootFolder: false
      archiveType: 'zip'
      archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildNumber).zip'
      replaceExistingArchive: true
    displayName: 'Archive artifact'

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'webapp'
      publishLocation: 'Container'
    displayName: 'Publish artifact'

Node.js Tool Installer task で npm コマンドでインストールとビルドを行い、Archive Files task で dist ディレクトリに出力されたファイル群を zip にまとめています。
最後に、Publish Build Artifacts task で、リリースパイプラインに渡すための zip をアーティファクトへ発行しています。

最後に、リリース用の release-pipelines.yml です。

parameters:
  imageName: ''
  azureSubscription: ''
  storageAccountName: ''
  environment: ''
  cdnResourseGroupName: ''
  cdnEndpointName: ''
  cdnProfileName: ''

jobs:
- deployment: Deploy_Static_Website_with_Azure_Storage
  displayName: 'Release'
  pool:
    vmImage: ${{parameters.imageName}}
  environment: ${{parameters.environment}}
  strategy:
    runOnce:
      deploy:
        steps:
        - script: |
            wget https://aka.ms/downloadazcopy-v10-linux
            tar -xvf downloadazcopy-v10-linux
            cp ./azcopy_linux_amd64_*/azcopy ./
          displayName: 'Install AzCopy v10'

        - task: ExtractFiles@1
          inputs:
            archiveFilePatterns: '$(Pipeline.Workspace)/**/*.zip'
            destinationFolder: '$(Pipeline.Workspace)/deploy'
            cleanDestinationFolder: true
          displayName: 'Extracting site content'
        
        - task: AzureCLI@1
          inputs:
            azureSubscription: ${{parameters.azureSubscription}}
            scriptLocation: 'inlineScript'
            inlineScript: |
              end=`date -d "5 minutes" '+%Y-%m-%dT%H:%M:%SZ'`
              sas=`az storage container generate-sas -n '$web' --account-name $1 --https-only --permissions dlrw --expiry $end -otsv`
              ./azcopy sync "$(Pipeline.Workspace)/deploy/" "https://$1.blob.core.windows.net/\$web?$sas" --recursive --delete-destination=true
            arguments: ${{parameters.storageAccountName}}
          displayName: 'Deploy to Static Website with Azure Storage'

        - task: AzureCLI@1
          inputs:
            azureSubscription: ${{parameters.azureSubscription}}
            scriptLocation: 'inlineScript'
            inlineScript: |
              az cdn endpoint purge -g ${{parameters.cdnResourseGroupName}} -n ${{parameters.cdnEndpointName}} --profile-name ${{parameters.cdnProfileName}} --content-paths '/*'
          displayName: 'Purge content for a CDN endpoint'

デプロイする Vue.js アプリケーションのファイル群は、AzCopy v10 を使って Blob Storage にアップロードしたいので、まずはインストールです。
インストールが完了したら、Extract Files task でビルドパイプラインから渡されたアーティファクトの zip を解凍し、AzCopy でアップロードします。
AzCopy は copy コマンドではなく、sync コマンドを使うことで、ファイルの削除や移動を気にせずにデプロイできます。Blob Storage へのアクセスには、Azure CLI task で SAS を発行して、一時的な権限を発行しています。
デプロイ自体はこれで完了ですが、最後に Azure CDN のエンドポイントにあるコンテンツを Purge しています。これでパイプライン完了後、Vue.js アプリケーションの更新をすぐに反映できます。

本番環境向けパイプライン

パイプライン起動用の azure-pipelines-production.yml です。

trigger:
- production

variables:
- group: variable-group-production
- name: imageName
  value: 'ubuntu-latest'
- name: nodeVersion
  value: '10.x'
- name: azureSubscription
  value: 'AzureSponsorships'
- name: environment
  value: 'Production-Stage'

stages:
- stage: Build
  jobs:
  - template: pipelines/build-pipelines.yml
    parameters:
      imageName: $(imageName)
      nodeVersion: $(nodeVersion)
    
- stage: Release
  dependsOn:
  - Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/production'))
  jobs:
  - template: pipelines/release-pipelines.yml
    parameters:
      imageName: $(imageName)
      azureSubscription: $(azureSubscription)
      storageAccountName: $(storageAccountName)
      environment: $(environment)
      cdnResourseGroupName: $(cdnResourseGroupName)
      cdnEndpointName: $(cdnEndpointName)
      cdnProfileName: $(cdnProfileName)

トリガーとなるブランチが production となっている箇所が開発環境向けパイプラインとの違いです。
Azure Web Apps の Deployment Slot 機能のような機能がないので、そのままデプロイしています。承認デプロイは使いたいので、承認設定した Environments を Production-Stage で登録しておきます。

まとめ

Azure DevOps の Multi-Stage Pipelines を使って、Azure Storage の Static website 向けのパイプラインを構築してみました。
Vue.js のような静的な Web アプリケーションの場合、Azure Web Apps を使う選択肢も十分に考慮しておきたいところです。

  • Azure CDN や Front Door を必須にしたくない
  • Blue / Green デプロイを実現したい(Deployment Slot 機能)
  • Easy Auth 機能で認証したい
  • Application Insight のモニタリングを活用したい

このような要件がある場合には、リリースパイプラインを Web Apps 向けに差し替えて使ってください。
こちらから、パイプライン YAML の全体を確認できます。
github.com