Azure Pipelines と Compute Engine による CI / CD パイプラインの作成

このチュートリアルでは、Azure Pipelines(旧称 Visual Studio Team Services)と Compute Engine を使用して、継続的インテグレーション / 継続的デプロイ(CI / CD)パイプラインを作成する方法について説明します。このチュートリアルでは、サンプル アプリケーションとしてオープンソースのコンテンツ管理システムの Orchard CMS を使用します。Orchard CMS は ASP.NET MVC をベースとし、Windows Server 2016 上で動作します。

CI / CD パイプラインは、テスト用と本番環境用の 2 種類の環境を使用します。デベロッパーが Git リポジトリへ変更を commit すると、commit によってソースコードがビルドされ、Packer を使用して新しい Windows Server 2016 ベースの仮想マシン(VM)イメージが作成されます。新しいイメージは、ローリング アップデートを使用して開発環境に自動的にリリースされます。テストが終わると、リリース管理者はそのリリースを昇格させ、本番環境にデプロイできるようになります。

デベロッパーとエンドユーザーがどのようにアプリケーションを操作するかを示す CI / CD パイプラインの概念図

このチュートリアルでは、.NET Framework、Windows Server、Microsoft インターネット インフォメーション サービス(IIS)、Azure Pipelines、Compute Engine に関する基本的な知識があることを前提としています。また、Azure DevOps アカウントに対する管理者権限を持っていること、Visual Studio 2017 がインストール済みで Azure DevOps アカウントに接続されている必要もあります。

目標

  • Compute Engine でプライベート Azure Pipelines エージェントを実行し、Azure Pipelines にこのエージェントを接続します。
  • Computer Engine で Packer を使用して Windows イメージを作成します。
  • Compute Engine マネージド インスタンス グループを使用して、ローリング デプロイを実装します。
  • Azure Pipelines で CI / CD パイプラインを設定して、ビルド、作成、デプロイのプロセスをオーケストレートします。

費用

このチュートリアルでは、Google Cloud の課金対象となる以下のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを作成できます。Azure DevOps の使用によって生じる費用については、Azure DevOps の料金ページをご覧ください。

始める前に

Google Cloud にアプリケーションをデプロイするには、Google Cloud プロジェクトが必要です。Identity and Access Management(IAM)ロールと権限を個別に付与できるようにするため、通常は CI、開発環境、本番環境のワークロードに別々のプロジェクトを使用することをおすすめします。

ただし、このチュートリアルではわかりやすくするために CI、開発環境、本番環境で単一のプロジェクトを使用します。

  1. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  2. Cloud Console のプロジェクト セレクタページで、Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタのページに移動

  3. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。プロジェクトに対して課金が有効になっていることを確認する方法を学習する

  4. Azure DevOps アカウントと、そのアカウントに対する管理者権限があることを確認します。まだ Azure DevOps アカウントがない場合は、Azure DevOps ホームページで登録できます。
  5. Visual Studio 2017 がインストールされ、Azure DevOps アカウントに接続していることを確認します。

Azure DevOps プロジェクトの作成

Orchard CMS を例にとり、Azure Pipelines を使用してソースコードを管理し、ビルドとテストを実行して、Compute Engine へのデプロイをオーケストレートします。

はじめに、Azure DevOps アカウントで新しいプロジェクトを作成します。

  1. Azure DevOps ホームページ(https://dev.azure.com/[YOUR_AZURE_DEVOPS_ORGANIZATION])に移動します。
  2. [Create Project] をクリックします。
  3. Orchard などのプロジェクト名を入力します。
  4. [Visibility] を [Private] に設定してから、[Create] をクリックします。
  5. プロジェクトが作成されたら、左側のメニューで [Repos] をクリックします。
  6. [インポート] をクリックして、GitHub から Orchard CMS リポジトリを fork します。次のように値を設定します。

    • Source type: Git
    • Clone URL: https://github.com/OrchardCMS/Orchard.git
    • [Requires authorization] チェックボックスはオフのままにします。

    [Import a Git repository] ダイアログ ボックスのスクリーンショット

  7. [Import] をクリックします。インポート処理が完了すると、Orchard CMS のソースコードが表示されます。

継続的なビルド

この段階で Azure Pipelines を使用して継続的インテグレーションを設定できます。Git リポジトリへ push する各 commit について、Azure Pipelines によってコードがビルドされ、生成されたビルドのアーティファクトが内部の Azure Pipelines ストレージに公開されます。

testing ブランチの作成

このチュートリアルの手順を確実に進めるため、特定バージョンのソースコードに基づくブランチを作成します。この手順により、今後 GitHub 上でコードの変更が発生しても、このチュートリアルが中断されないようになります。

  1. Azure DevOps のメニューで、[Repos] > [Tags] を選択します。
  2. タグのリストで、1.10.2 の横にあるアイコンを右クリックします。
  3. [New Branch] を選択します。
  4. [Name] ボックスにブランチ名として「testing」と入力し、[Create branch] をクリックして確定します。

    Azure Pipelines の [Create a branch] ダイアログ ボックスのスクリーンショット

Azure Pipelines では、デフォルトでコードが master ブランチにあるものと想定しています。testing ブランチが使用されるようにするには、デフォルトのブランチを変更する必要があります。

  1. Azure DevOps のメニューで、[Project settings] を選択します。
  2. [Repos] > [Repositories] の順に選択します。
  3. リポジトリのリストで、先ほどインポートした Git リポジトリを選択します。Azure DevOps プロジェクトと同じ名前になっているはずです。
  4. [Branches] の横の矢印をクリックしてブランチのリストを展開します。
  5. testing ブランチを選択します。ブランチ名の右側に [...] ボタンが表示されます。
  6. [...] をクリックし、[Set as default branch] を選択します。

ビルド定義の作成

ブランチの作成が終わると、ビルドの自動化を開始できます。Orchard CMS は、Visual Studio で作成された ASP.NET アプリケーションのため、ビルドの定義は次の手順で行います。

  • NuGet パッケージの依存関係の復元。
  • ソリューション(src\Orchard.sln)の構築。
  • Orchard.Web プロジェクトのビルド アーティファクトの公開。

その後、Compute Engine にデプロイする手順を行います。アプリケーションには Windows ベースの環境が必要となるため、ビルドプロセス全体を Windows ベースのビルド エージェントで実行できるように設定します。

Visual Studio でのプロジェクトのチェックアウト

ビルド パイプラインを定義する YAML ファイルを作成するには、まずコードをチェックアウトする必要があります。

  1. Visual Studio でチーム エクスプローラーを開きます。
  2. メニューの [接続の管理] アイコンをクリックします。
  3. [接続の管理] > [プロジェクトに接続] をクリックします。

    Visual Studio の [チーム エクスプローラー] ペインにある [プロジェクトに接続] オプションのスクリーンショット

  4. 次のダイアログで、[Orchard] Git リポジトリを選択し、[複製] をクリックします。

    Visual Studio の [プロジェクトに接続] ダイアログで [Orchard] Git リポジトリが選択されているスクリーンショット

ビルド パイプラインを定義する YAML ファイルを作成する

コードをチェックアウトしたら、ビルド パイプラインを構成できます。

  1. Visual Studio でソリューション エクスプローラーを開きます。
  2. ソリューションのルートで、azure-pipelines.yml という名前でファイルを新しく作成します。
  3. 次のコードをコピーしてファイルに貼り付けます。

    resources:
        - repo: self
          fetchDepth: 1
        trigger:
        - testing
        variables:
          artifactName: 'Orchard.Web'
        jobs:
        - job: Build
          displayName: Build application
          condition: succeeded()
          pool:
            vmImage: vs2017-win2016
            demands:
            - msbuild
            - visualstudio
          variables:
            solution: 'src\Orchard.sln'
            buildPlatform: 'Any CPU'
            buildConfiguration: 'Release'
          steps:
          - task: NuGetToolInstaller@0
            displayName: 'Use NuGet 4.4.1'
            inputs:
              versionSpec: 4.4.1
          - task: NuGetCommand@2
            displayName: 'NuGet restore'
            inputs:
              restoreSolution: '$(solution)'
          - task: VSBuild@1
            displayName: 'Build solution'
            inputs:
              solution: '$(solution)'
              msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactstagingdirectory)\\"'
              platform: '$(buildPlatform)'
              configuration: '$(buildConfiguration)'
          - task: PublishSymbols@2
            displayName: 'Publish symbols path'
            continueOnError: true
            inputs:
              SearchPattern: '**\bin\**\*.pdb'
              PublishSymbols: false
          - task: PublishBuildArtifacts@1
            displayName: 'Publish Artifact'
            inputs:
              PathtoPublish: '$(build.artifactstagingdirectory)/Orchard.Web.zip'
              ArtifactName: '$(artifactName)'
        
  4. チーム エクスプローラーを開き、左上の [ホーム] アイコンをクリックして [ホーム] ビューに切り替えます。

  5. [変更] をクリックします。

  6. Add build definition」といった Commit メッセージを入力します。

  7. [すべてをコミットしてプッシュ] をクリックします。

  8. Azure DevOps のメニューで、[Pipelines] > [Builds] の順に選択します。

    Git リポジトリに commit した YAML ファイルに基づいてビルド定義が作成され、ビルドが自動的にトリガーされるのを確認します。

ビルドが完了するまでに最長 7 分かかります。ビルドが終了すると、内部の Azure Pipelines アーティファクト ストレージ領域でファイル Orchard.Web.zip を使用できます。このファイルにはこのウェブ アプリケーションのすべてのファイルが格納されています。

ソースコードのビルドが継続して行われる場合、次のステップでは VM イメージが自動作成されます。この自動化には、次の手順が含まれます。

  • Windows Server 2016 イメージを使用する新しい一時的な VM インスタンスの起動。
  • IIS のインストールと構成。
  • Orchard CMS のデプロイ。
  • VM の停止。
  • イメージの作成。
  • 一時 VM の削除。

このプロセスの自動化には、Packer を使用します。Packer は VM インスタンスへのアクセスを必要としますが、このインスタンスを公共のインターネットには公開したくありません。したがって、Google Cloud ネットワーク内のプライベート Azure Pipelines エージェントで Packer を実行します。

プライベート Azure Pipelines エージェントのデプロイ

Google Cloud のプライベート エージェント上で Packer を実行することによって、公共のインターネットへのリソースの公開、および安全性の低い環境の作成を回避できます。ただし、これによって管理オーバーヘッドも増加します。このオーバーヘッドを最小化するには、マネージド インスタンス グループにエージェントをデプロイします。この方法を使うことにより、次のメリットが得られます。

  • マネージド インスタンス グループを使用すると、VM の再作成、必要なすべてのコンポーネントのインストール、Azure Pipelines への登録を自動化できます。
  • VM を削除し、最新の更新プログラムが適用された Windows イメージをベースとして使用し、マネージド インスタンス グループで環境を再作成することで、Windows Updates を適用できます。

手順は次のとおりです。

  1. Cloud Console で、新しく作成されたプロジェクトに切り替えます。
  2. Cloud Shell を開きます。

    Cloud Shell に移動

  3. 時間を節約するため、プロジェクト ID のデフォルト値と Compute Engine ゾーンのデフォルト値を設定します。

    gcloud config set project [PROJECT_ID]
        gcloud config set compute/zone [ZONE]

    [PROJECT_ID] を Google Cloud プロジェクトの ID に置き換え、[ZONE] をリソースの作成に使用するゾーンの名前に置き換えます。どのゾーンを選択するかわからない場合は、us-central1-a を使用します。

    例:

    gcloud config set project devops-test-project-12345
        gcloud config set compute/zone us-central1-a
  4. Compute Engine API を有効にします。

    gcloud services enable compute.googleapis.com
  5. Azure DevOps のメニューに切り替えて [Project settings] を選択し、続いて [Pipelines] > [Agent pools] の順に選択します。

  6. [New agent pool] をクリックします。

  7. プール名として「Google Cloud」と入力し、[OK] をクリックします。このプールは、Compute Engine で実行される限定公開ビルド エージェントのキューになります。

  8. 左ペインで、新しく作成した Google Cloud プールを選択し、[Download agent] をクリックします。

  9. [Download agent] で、[Copy] をクリックして、ダウンロード URL をコピーします。

  10. Cloud Shell で環境変数を初期化します。URL 値には、前の手順でコピーした URL を貼り付けます。

    export AZURE_DEVOPS_AGENT_URL=[PASTE URL FROM CLIPBOARD]
  11. Azure Pipelines に戻り、画面の右上隅にあるユーザーのアバターをクリックし、[Security] をクリックします。

  12. [Security] > [Personal access tokens] の順に進み、[New Token] をクリックします。

  13. 次のように構成します。

    • Name: Google Cloud Agent
    • Scopes: Full access
  14. [Create] をクリックします。

  15. トークンをクリップボードにコピーします。

  16. Cloud Shell で次のコマンドを実行して、もう 1 つの環境変数を初期化します。値には、前のステップでコピーしたトークンを貼り付けます。

    export AZURE_DEVOPS_TOKEN=[PASTE TOKEN FROM CLIPBOARD]
  17. Azure Pipelines URL を格納する別の環境変数を初期化します。Azure Pipelines のアドレスバーの表示に従って、[ACCOUNT] をアカウント名に置き換えます。

    export AZURE_DEVOPS_URL=https://[ACCOUNT].visualstudio.com
  18. 次のコマンドを実行して specialize スクリプトを作成します。このスクリプトを使用して Azure Pipelines エージェント パッケージ、Packer、Cloud SDK をダウンロードしてインストールし、Azure Pipelines エージェントを Azure Pipelines に登録します。

        cat | envsubst '$AZURE_DEVOPS_AGENT_URL $AZURE_DEVOPS_TOKEN $AZURE_DEVOPS_URL' > specialize.ps1 << 'EOF'
        # Create an installation directory for the Azure Pipelines agent
        New-Item -ItemType directory -Path $env:programfiles\vsts-agent
    
        # Create a work directory for the Azure Pipelines agent
        New-Item -ItemType directory -Path $env:programdata\vsts-agent
    
        # Download and install the Azure Pipelines agent package
        Invoke-WebRequest `
          -Uri "$AZURE_DEVOPS_AGENT_URL" `
          -OutFile $env:TEMP\vsts-agent.zip
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory( `
          "$env:TEMP\vsts-agent.zip", `
          "$env:programfiles\vsts-agent")
    
        # Download and install Packer
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        Invoke-WebRequest `
          -Uri "https://releases.hashicorp.com/packer/1.2.2/packer_1.2.2_windows_amd64.zip" `
          -OutFile $env:TEMP\packer.zip
        [System.IO.Compression.ZipFile]::ExtractToDirectory( `
          "$env:TEMP\packer.zip", `
          "$env:programfiles\packer")
    
        # Download and install the Cloud SDK
        Invoke-WebRequest `
          -Uri https://dl.google.com/dl/cloudsdk/channels/rapid/GoogleCloudSDKInstaller.exe `
          -OutFile $env:TEMP\cloudsdk.exe
        Start-Process -Wait $env:TEMP\cloudsdk.exe -arg "/S /noreporting /nostartmenu /nodesktop"
    
        # Add Packer and the Cloud SDK installation directory to global path
        [Environment]::SetEnvironmentVariable( `
          "Path", $env:Path + ";$env:programfiles\packer;${env:ProgramFiles(x86)}\Google\Cloud SDK\google-cloud-sdk\bin", `
          [System.EnvironmentVariableTarget]::Machine)
    
        # Install gcloud beta commands
        $env:CLOUDSDK_PYTHON=gcloud components copy-bundled-python
        Start-Process -Wait gcloud -arg "components install beta --quiet"
    
        # Configure the Azure Pipelines agent
        & $env:programfiles\vsts-agent\bin\Agent.Listener configure `
          --url $AZURE_DEVOPS_URL `
          --agent "GCE Agent" `
          --work $env:programdata\vsts-agent `
          --pool "Google Cloud" `
          --replace `
          --runAsService `
          --windowsLogonAccount "NT AUTHORITY\NETWORK SERVICE" `
          --auth PAT `
          --token $AZURE_DEVOPS_TOKEN
        EOF
        
  19. Azure Pipelines エージェントのインスタンス テンプレートを作成します。VM インスタンスが起動時に specialize.ps1 を specialize スクリプトとして実行するように、このインスタンス テンプレートを構成します。

        gcloud compute instance-templates create vsts-agent \
            --machine-type n1-standard-1 \
            --image-family windows-2016-core \
            --image-project windows-cloud \
            --metadata-from-file sysprep-specialize-script-ps1=specialize.ps1 \
            --scopes "https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/devstorage.read_write"
        
  20. このインスタンス テンプレートをベースとして、マネージド インスタンス グループを作成します。新しい VM インスタンスが開始され、Azure Pipelines エージェントが登録されるまで、約 3 分かかります。

        gcloud compute instance-groups managed create vsts-agent \
            --template=vsts-agent \
            --size=1
        
  21. Azure DevOps で、プロジェクトに戻ります。

  22. メニューで [Project settings] を選択し、続いて [Pipelines] > [Agent pools] の順に選択します。

  23. Google Cloud プールを選択し、エージェントが登録され、その状態が [Online] となっていることを確認します。

    Azure Pipelines の [Agent Queues] ダイアログ ボックスのスクリーンショット

トラブルシューティング

10 分経過してもエージェントが登録されない場合は、問題が発生している可能性があります。まず、次の手順を実行します。

  1. Cloud Console で [Compute Engine] > [VM インスタンス] に移動します。
  2. 名前が vsts-agent で始まる VM の詳細を開きます。
  3. [シリアルポート 1(コンソール)] をクリックします。

コンソールログには specialize スクリプトの出力が含まれています。この出力はトラブルシューティングに利用できる場合があります。Azure Pipelines にエージェントが表示されても、最初は [Offline] と表示されることがあります。その場合は、ステータスが [Online] に変わるまでさらに 2 分間待ちます。

セットアップのテスト

必要に応じて、次のようにこのセットアップの復元力をテストします。

  1. [Compute Engine] > [VM インスタンス] に移動します。
  2. 名前が vsts-agent で始まる VM を削除します。

マネージド インスタンス グループによって、すぐに新しいインスタンスが生成され、数分後に新しいエージェントが再び動作可能になります。

VM イメージの作成

VM イメージの作成プロセスを自動化するには、Packer テンプレートを作成する必要があります。このテンプレートは、プロジェクトのソースコードを保持する JSON ファイルです。

Packer テンプレートの作成

テンプレートの作成前にソースコードをチェックアウトする必要があります。

  1. Visual Studio でソリューション エクスプローラーを開きます。
  2. ソリューションのルートで、packer.json という名前でファイルを新しく作成します。
  3. 新しく作成したファイルに次のコードをコピーして、ファイルを保存します。

        {
          "variables": {
            "gcp_project": "",
            "gcp_zone": "",
            "windows_user": "packer_user",
            "windows_password": "Packer123",
            "image_name": "vsts",
            "image_family": "vsts",
            "app_package": ""
          },
          "builders": [
            {
              "type": "googlecompute",
              "project_id": "{{user `gcp_project`}}",
              "source_image_family": "windows-2016",
              "disk_size": "50",
              "instance_name": "{{user `image_name`}}",
              "image_name": "{{user `image_name`}}",
              "image_family": "{{user `image_family`}}",
              "machine_type": "n1-standard-2",
              "communicator": "winrm",
              "winrm_username": "{{user `windows_user`}}",
              "winrm_password": "{{user `windows_password`}}",
              "winrm_insecure": true,
              "winrm_use_ssl": true,
              "winrm_port": 5986,
              "metadata": {
                "windows-startup-script-cmd": "winrm quickconfig -quiet & net user /add {{user `windows_user`}} {{user `windows_password`}} & net localgroup administrators {{user `windows_user`}} /add & winrm set winrm/config/service/auth @{Basic=\"true\"}"
              },
              "zone": "{{user `gcp_zone`}}",
              "use_internal_ip": true,
              "state_timeout": "8m",
              "scopes": [ "https://www.googleapis.com/auth/devstorage.read_only" ]
            }
          ],
          "provisioners": [
            {
              "type": "powershell",
              "inline": [
                "$ErrorActionPreference = \"Stop\"",
    
                "# Download application package from Cloud Storage",
                "gsutil cp {{user `app_package`}} $env:TEMP\\app.zip",
    
                "# Install IIS",
                "Enable-WindowsOptionalFeature -Online -FeatureName NetFx4Extended-ASPNET45",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServer",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-CommonHttpFeatures",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpErrors",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpRedirect",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-ApplicationDevelopment",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HealthAndDiagnostics",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpLogging",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-LoggingLibraries",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-RequestMonitor",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpTracing",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-Security",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-RequestFiltering",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-Performance",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerManagementTools",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-IIS6ManagementCompatibility",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-Metabase",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-DefaultDocument",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-ApplicationInit",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-NetFxExtensibility45",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-ISAPIExtensions",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-ISAPIFilter",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-ASPNET45",
                "Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpCompressionStatic",
    "# Extract application package to wwwroot", "New-Item -ItemType directory -Path $env:TEMP\\app", "Add-Type -AssemblyName System.IO.Compression.FileSystem", "[System.IO.Compression.ZipFile]::ExtractToDirectory(\"$env:TEMP\\app.zip\", \"$env:TEMP\\app\")", "Remove-Item $env:TEMP\\app.zip", "Move-Item -Path $(dir -recurse $env:TEMP\\app\\**\\PackageTmp | % { $_.FullName }) -Destination c:\\inetpub\\wwwroot\\app -force", "# Configure IIS web application pool and application", "Import-Module WebAdministration", "New-WebAppPool orchard-net4", "Set-ItemProperty IIS:\\AppPools\\orchard-net4 managedRuntimeVersion v4.0", "New-WebApplication -Name Orchard -Site 'Default Web Site' -PhysicalPath c:\\inetpub\\wwwroot\\app -ApplicationPool orchard-net4", "# Grant read/execute access to the application pool user", "&icacls C:\\inetpub\\wwwroot\\app\\ /grant \"IIS AppPool\\orchard-net4:(OI)(CI)(RX)\"", "# Create data folder and grant write access to the application pool user", "New-Item -ItemType directory -Path C:\\inetpub\\wwwroot\\app\\App_Data\\", "&icacls C:\\inetpub\\wwwroot\\app\\App_Data\\ /grant \"IIS AppPool\\orchard-net4:(OI)(CI)M\"", "# Disable searching for Windows updates", "New-ItemProperty -Path HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU -Name NoAutoUpdate -Value 1 -PropertyType DWORD -Force", "# Disable provisioning user", "disable-localuser {{user `windows_user`}}", "# Generalize the image", "& \"$Env:Programfiles\\Google\\Compute Engine\\sysprep\\gcesysprep\"" ] } ] }

Packer テンプレートには次の 3 つのセクションがあります。

  • variables セクションは、テンプレートで使用される変数を定義します。これらの変数の値は、Packer にコマンドライン引数として渡されます。
  • builders セクションには、一時的な VM インスタンスの作成とこのインスタンスとの通信のための設定が格納されます。
  • provisioners セクションには、作成した VM を構成するためにその VM 上で実行される一連のコマンドが格納されます。

上記のテンプレートは、Packer の Google Compute Builder を使用して、Windows Server 2016 を実行する VM インスタンスを作成します。Packer で使用されるソースイメージを指定する際は、最新のセキュリティ更新プログラムがすでに適用されているイメージを使用することが重要です。Google が Windows Server 2016 イメージの新しいバージョンをリリースするたびに source_image 設定を変更せず済むように、この構成は windows-2016-core イメージ ファミリーから最新のイメージを取得します。

テンプレートでは、起動スクリプトを使用して Windows Remote Management(WinRM)を有効化し、一時的なユーザーを作成します。VM が起動して実行されると、Packer は WinRM とこのユーザーを使用して、provisioners セクションで指定されている PowerShell コマンドを実行します。use_internal_ip 設定を使用すると、WinRM の通信がインターネット経由ではなくローカル ネットワーク経由になります。これは、以前にプロビジョニングしたプライベート ビルド エージェントで、対応するビルド手順が実行されるためです。最後に、builders セクションでは、VM の IAM スコープも定義され、Cloud Storage へのアクセスが可能になります。

provisioners セクションの PowerShell コマンドによって、Orchard CMS を実行するように VM が構成されます。この構成には、次の内容が含まれます。

  • Cloud Storage からのアプリケーション パッケージのダウンロード。
  • IIS のインストール。
  • IIS アプリケーション プールと Orchard CMS 用のアプリケーションの作成。
  • IIS webroot フォルダへのアプリケーション パッケージの抽出、およびこのフォルダのアクセス制御リスト(ACL)の構成。
  • 定期的な CI / CD パイプラインの再実行によって更新プログラムを適用する場合に不要になる Windows Updates の検索の無効化。
  • プロビジョニング ユーザーの無効化。
  • GCESysprep の使用によりイメージを一般化し、イメージから作成された各 VM に一意のセキュリティ識別子が割り当てられるようにする。

ファイルを Git に commit するには、次のようにします。

  1. Visual Studio でチーム エクスプローラーを開きます。
  2. 左上の [ホーム] アイコンをクリックして、[ホーム] ビューに切り替えます。
  3. [変更] をクリックします。
  4. [変更] で packer.json を右クリックし、[ステージ] をクリックします。
  5. Add Packer template」といった Commit メッセージを入力します。
  6. [ステージング済みをコミット] をクリックします。

ビルド アーティファクト用 Cloud Storage バケットの作成

Packer の File Provisioner を使用すると、Packer を実行しているマシンから VM にファイルをコピーできます。WinRM を使用している場合は、このオペレーションが遅くなる可能性があります。このため、前のセクションで作成したテンプレートでは、Cloud Stoarage 上の Orchard CMS アプリケーション パッケージが検索され、パフォーマンスが大幅に向上します。

これを目的として Cloud Storage にバケットを作成するには、次のコマンドを実行します。

gsutil mb gs://$(gcloud config get-value core/project)-artifacts

ビルドのアーティファクトをすべて保持する必要がない場合は、オブジェクトのライフサイクル ルールを構成して特定の期間を経過したファイルは削除することを検討してください。

ビルド定義の拡張

これまでの手順で、Git にチェックインされた Packer テンプレートの確認と、Cloud Storage バケットの作成が済んでいます。次は、Packer を Azure Pipelines ビルドプロセスに統合します。

これまでに作成した Azure Pipelines ビルド定義は、単一のフェーズを使用しています。Azure Pipelines では、同じフェーズに含まれるすべてのタスクが同じビルド エージェントで実行されます。この例のエージェントは Hosted VS2017 です。Compute Engine でプロビジョニングされたプライベート ビルド エージェントの使用を開始するには、ビルド定義に第 2 フェーズを追加する必要があります。

単一フェーズでは、タスクは作業ディレクトリを共有し、以前のタスクからのアーティファクトにアクセスできます。複数のフェーズ間でこのようなアクセスはできません。前のステージのアーティファクトを使用するには、最初のフェーズでこれらのアーティファクトを Azure Pipelines に公開し、次のフェーズでそれらをダウンロードする必要があるためです。このため、ビルド定義を次のように拡張します。

  1. Visual Studio で、azure-pipelines.yml を開きます。
  2. このファイルに次のコードを付加してビルド定義を拡張します。[PROJECT_ID] は Google Cloud プロジェクトの名前に置き換え、[ZONE] はリソースの作成に使用するゾーンの名前に置き換えます。選択するゾーンが不明な場合は、us-central1-a を使用します。

    - job: CreateVM
          displayName: Create VM image
          dependsOn: Build
          condition: succeeded()
          pool:
            name: 'Google Cloud'
          variables:
            Packer.Project: '[PROJECT_ID]'
            Packer.Zone: '[ZONE]'
          steps:
          - task: DownloadBuildArtifacts@0
            displayName: 'Download Build Artifacts'
            inputs:
              artifactName: '$(artifactName)'
          - task: CmdLine@1
            displayName: 'Publish artifact to Cloud Storage'
            inputs:
              filename: gsutil
              arguments: 'cp $(System.ArtifactsDirectory)\$(artifactName)\Orchard.Web.zip gs://$(Packer.Project)-artifacts/Orchard.Web-$(Build.BuildId).zip'
          - task: CmdLine@1
            displayName: 'Create image'
            inputs:
              filename: packer
              arguments: 'build -var "gcp_project=$(Packer.Project)" -var "gcp_zone=$(Packer.Zone)" -var "image_family=orchard" -var "image_name=orchard-$(Build.BuildId)" -var "app_package=gs://$(Packer.Project)-artifacts/Orchard.Web-$(Build.BuildId).zip" $(Build.SourcesDirectory)/packer.json'
        

変更を commit して Azure DevOps に push する手順は次のとおりです。

  1. Visual Studio でチーム エクスプローラーを開きます。
  2. 左上の [ホーム] アイコンをクリックして、[ホーム] ビューに切り替えます。
  3. [変更] をクリックします。
  4. [変更] で azure-pipelines.yml を右クリックし、[ステージ] をクリックします。
  5. Extend build definition to create VM image」といった Commit メッセージを入力します。
  6. [ステージング済みをコミットしてプッシュ] をクリックします。
  7. Azure DevOps のメニューで、[Pipelines] > [Build] の順に選択します。新しいビルドが自動的に開始されます。ビルドが完了するまで 10~15 分かかります。エラー メッセージ「Could not find a pool with name Google Cloud」が表示されてビルドが失敗した場合は、パイプラインの再保存が必要になることがあります。
  8. Google Cloud で [Compute Engine] > [イメージ] に移動し、orchard-N という名前のイメージが作成されていることを確認します。ここで、N は Azure Pipelines ビルドの ID を表します。

継続的デプロイ

これまでの手順で、Azure Pipelines が自動的にコードをビルドし、commit するたびに新しい VM イメージを作成するようになりました。次は、デプロイ手順に進みます。

開発環境の構成

Orchard CMS では、SQL Server またはデータをローカルに保存する組み込みデータベースを使用できます。わかりやすくするため、組み込みデータベースを利用するデフォルトの構成を使います。ただし、次の 2 つの制限が存在します。

  • 一度に実行できる VM インスタンスは 1 つだけです。そうしないと、コンテンツを提供する VM インスタンスによって異なるデータが表示される場合があります。
  • データ ストレージに Filestore を使用するようにデプロイを変更しない限り、VM インスタンスが再起動されるたびにすべてのデータ変更が失われます(この状況については、このチュートリアルでは扱いません)。

Azure Pipelines でデプロイが自動化されるように手順を構成するには、あらかじめ開発環境を準備しておく必要があります。この準備には、ウェブサーバーの VM インスタンスを管理するマネージド インスタンス グループの作成が含まれます。また、HTTP ロードバランサの作成も含まれます。

  1. Cloud Shell で標準の Windows Server 2016 Core イメージ(カスタマイズされていないもの)を使用するインスタンス テンプレートを作成します。ビルドのたびに新しいテンプレートが生成されるため、このテンプレートを使用するのは初回のみです。

    gcloud compute instance-templates create orchard-initial \
            --machine-type n1-standard-2 \
            --image-family windows-2016-core \
            --image-project windows-cloud
  2. HTTP ヘルスチェックを作成します。Orchard CMS には専用のヘルスチェック エンドポイントがないため、パス / を照会できます。

    gcloud compute http-health-checks create orchard-dev-http \
            --check-interval=10s --unhealthy-threshold=10 \
            --request-path=/
  3. 初期インスタンス テンプレートをベースとするマネージド インスタンス グループを作成します。説明をわかりやすくするため、次のコマンドではゾーンを対象とするマネージド インスタンス グループが作成されます。ただし、同じ方法を使用して、リージョンを対象とするマネージド インスタンス グループを作成して複数のゾーンに VM インスタンスを配信することもできます。

    gcloud beta compute instance-groups managed create orchard-dev \
            --template=orchard-initial \
            --http-health-check=orchard-dev-http \
            --initial-delay=2m \
            --size=1 && \
        gcloud compute instance-groups set-named-ports orchard-dev --named-ports http:80
  4. HTTP ヘルスチェックと前の手順で作成したマネージド インスタンス グループを使用するロードバランサ バックエンド サービスを作成します。

    gcloud compute backend-services create orchard-dev-backend \
            --http-health-checks orchard-dev-http \
            --port-name http --protocol HTTP --global && \
        gcloud compute backend-services add-backend orchard-dev-backend \
            --instance-group orchard-dev --global \
            --instance-group-zone=$(gcloud config get-value compute/zone)
  5. 次のようにして、ロードバランサのフロントエンドを作成します。

    gcloud compute url-maps create orchard-dev --default-service orchard-dev-backend && \
        gcloud compute target-http-proxies create orchard-dev-proxy --url-map=orchard-dev && \
        gcloud compute forwarding-rules create orchard-dev-fw-rule --global --target-http-proxy orchard-dev-proxy --ports=80
  6. gclb-backend タグでアノテーションが設定されたインスタンスに HTTP リクエストを送信することを Google ロードバランサに許可するファイアウォール ルールを作成します。このタグは、後続の手順でウェブサービスの VM インスタンスに適用します。

    gcloud compute firewall-rules create gclb-backend --source-ranges=130.211.0.0/22,35.191.0.0/16 --target-tags=gclb-backend --allow tcp:80

本番環境の構成

本番環境を設定するには、開発環境を構成するときと同様の手順を実行する必要があります。

  1. Cloud Shell で、HTTP ヘルスチェックを作成します。Orchard CMS には専用のヘルスチェック エンドポイントがないため、パス / を照会できます。

    gcloud compute http-health-checks create orchard-prod-http \
            --check-interval=10s --unhealthy-threshold=10 \
            --request-path=/
  2. 前の手順で作成した初期インスタンス テンプレートをベースとするマネージド インスタンス グループをさらに作成します。

    gcloud beta compute instance-groups managed create orchard-prod \
            --template=orchard-initial \
            --http-health-check=orchard-prod-http \
            --initial-delay=2m \
            --size=1 && \
        gcloud compute instance-groups set-named-ports orchard-prod --named-ports http:80
  3. HTTP ヘルスチェックと前の手順で作成したマネージド インスタンス グループを使用するロードバランサ バックエンド サービスを作成します。

    gcloud compute backend-services create orchard-prod-backend --http-health-checks orchard-prod-http --port-name http --protocol HTTP --global && \
        gcloud compute backend-services add-backend orchard-prod-backend --instance-group orchard-prod --global --instance-group-zone=$(gcloud config get-value compute/zone)
  4. 次のようにして、ロードバランサのフロントエンドを作成します。

    gcloud compute url-maps create orchard-prod --default-service orchard-prod-backend && \
        gcloud compute target-http-proxies create orchard-prod-proxy --url-map=orchard-prod && \
        gcloud compute forwarding-rules create orchard-prod-fw-rule --global --target-http-proxy orchard-prod-proxy --ports=80

リリース パイプラインの構成

他の継続的インテグレーション システムとは異なり、Azure Pipelines ではビルドとデプロイが区別され、デプロイ関連のすべてのタスクに、リリース管理というラベルが付けられた専用のツールセットが提供されます。

Azure Pipelines リリース管理は、次のコンセプトに基づいて構築されています。

  • リリースは、通常はビルドプロセスの結果からなる、アプリケーションの特定のバージョンを形成するアーティファクト群です。
  • デプロイとは、リリースを取得して特定の環境にデプロイするプロセスを意味します。
  • デプロイによって一連のタスクが実行され、タスクはジョブにグループ分けされます。
  • ステージによってパイプラインを分割でき、ステージを使用することで、開発環境やテスト環境などの複数の環境へのデプロイをオーケストレートできます。

通常、Azure Pipelines リリースでは、ビルドの zip ファイルなどのアーティファクトが利用されます。このチュートリアルでは、ビルドによって GCP に VM イメージが生成されるため、消費されるようなアーティファクトはありません。ただし、環境変数としてリリースに渡されるビルド ID を使用すると、対応する VM イメージを見つけてデプロイに使用できます。

リリース定義の作成

最初のステップとして、新しいリリース定義を作成します。

  1. Azure DevOps のメニューで、[Pipelines] > [Releases] の順に選択します。
  2. [New pipeline] をクリックします。
  3. テンプレートのリストから [Empty job] を選択します。
  4. ステージの名前の入力を求められたら、「Dev」と入力します。
  5. 画面の上部で、リリースに Orchard-ComputeEngine という名前を付けます。
  6. パイプライン図で、[Artifacts] の横にある [Add] をクリックします。
  7. [Build] を選択して、次の設定を追加します。

    • Source: azure-pipelines.yml ファイルを含む Git リポジトリを選択します。
    • Default version: Latest
    • Source alias: Orchard
  8. [Add] をクリックします。

  9. [Artifact] ボックスで、稲妻のアイコンをクリックしてデプロイ トリガーを追加します。

  10. [Continuous deployment trigger] で、スイッチを [Enabled] に設定します。

  11. [Save] をクリックします。

  12. 必要に応じてコメントを入力し、[Save] をクリックして確定します。

パイプラインは次のようになります。

Azure Pipelines のパイプラインのスクリーンショット

開発環境のデプロイ

これまでの手順で、リリース定義を作成しました。次に、開発環境へのローリング デプロイを開始する手順を追加します。

  1. Azure Pipelines で [Tasks] タブに切り替えます。
  2. [Agent job] をクリックします。
  3. エージェント プールを [Private] > [Google Cloud] に変更します。
  4. [Agent job] の横の [+] アイコンをクリックして、ステップをフェーズに追加します。
  5. [Command Line] タスクを選択し、[Add] をクリックして、次のように構成します。

    • Version: 1.*
    • Display name: Create instance template
    • Tool: gcloud
    • Arguments: compute instance-templates create orchard-$(Build.BuildId)-$(Release.ReleaseId) --machine-type n1-standard-2 --image orchard-$(Build.BuildId) --image-project $(Packer.Project) --tags=gclb-backend

    このコマンドは、以前に Packer で構築した VM イメージを使用する新しいインスタンス テンプレートを作成します。このコマンドは gclb-backend タグを適用して、ロードバランサがこのテンプレートから作成されたインスタンスに到達できるようにします。

  6. さらに [Command Line] タスクを追加し、次のように構成します。

    • Version: 1.*
    • Display name: Associate instance template
    • Tool: gcloud
    • Arguments: beta compute instance-groups managed set-instance-template orchard-dev --template=orchard-$(Build.BuildId)-$(Release.ReleaseId) --zone $(Deployment.Dev.Zone)

    このコマンドは、新しいインスタンス テンプレートを使用するように、既存のインスタンス グループを更新します。このコマンドを実行しても、既存の VM はいずれも、置換または更新されないことに注意してください。既存の VM に作用するのではなく、今後このインスタンス グループで作成される VM が新しいテンプレートから作成されるようになります。

  7. さらに [Command Line] タスクを追加し、次のように構成します。

    • Version: 1.*
    • Display name: Start rolling update
    • Tool: gcloud
    • Arguments: beta compute instance-groups managed rolling-action start-update orchard-dev --version template=orchard-$(Build.BuildId)-$(Release.ReleaseId) --type proactive --min-ready 2m --max-unavailable 0 --zone $(Deployment.Dev.Zone)

    このコマンドは、既存のインスタンス グループで既存の VM がローリング方式で新しい VM に置き換えられるようにします。

  8. [Variables] タブをクリックし、次の変数を追加します。

    名前
    Packer.Project Google Cloud Platform プロジェクトの名前です。
    Deployment.Dev.Zone 前の手順で gcloud config set compute/zone を実行したときに指定したゾーン(例: us-central1-a
  9. [Save] をクリックします。

  10. 必要に応じてコメントを入力し、[OK] をクリックして確定します。

本番環境のデプロイ

最後に、本番環境へのデプロイを構成する必要があります。

  1. Azure Pipelines で [Pipeline] タブに切り替えます。
  2. [Stages] ボックスで、[Add] > [New stage] を選択します。
  3. テンプレートのリストから [Empty job] を選択します。
  4. ステージの名前の入力を求められたら、「Prod」と入力します。
  5. 新たに作成したステージの稲妻のアイコンをクリックします。
  6. 次のように構成します。

    • Select trigger: After stage
    • Stages: Dev
    • Pre-deployment approvals: (有効)
    • Approvers: 自分のユーザー名を選択します。
  7. [Tasks] タブにマウスを合わせ、[Tasks] > [Prod] の順に選択します。

  8. [Agent job] をクリックします。

  9. [Agent pool] の値を Private > Google Cloud に変更します。

  10. [Agent phase] の横の [+] アイコンをクリックして、ステップをフェーズに追加します。

  11. [Command Line] タスクを追加して、次のように構成します。

    • Version: 1.*
    • Display name: Associate instance template
    • Tool: gcloud
    • Arguments: beta compute instance-groups managed set-instance-template orchard-prod --template=orchard-$(Build.BuildId)-$(Release.ReleaseId) --zone $(Deployment.Prod.Zone)

    このコマンドは、開発環境へのデプロイ時に作成したインスタンス テンプレートを使用するように、既存のインスタンス グループを更新します。同じインスタンス テンプレートを再利用することで、まったく同じイメージをデプロイできます。

  12. さらに [Command Line] タスクを追加して、次のように構成します。

    • Version: 1.*
    • Display name: Start rolling update
    • Tool: gcloud
    • Arguments: beta compute instance-groups managed rolling-action start-update orchard-prod --version template=orchard-$(Build.BuildId)-$(Release.ReleaseId) --type proactive --min-ready 2m --max-unavailable 0 --zone $(Deployment.Prod.Zone)

    このコマンドは、既存のインスタンス グループで既存の VM がローリング方式で新しい VM に置き換えられるようにします。

  13. [Variables] タブに切り替えます。

  14. 変数を次のように追加します。

    • Name: Deployment.Prod.Zone
    • Value: 前の手順で gcloud config set compute/zone を実行したときに指定したゾーン(例: us-central1-a
  15. [Save] をクリックします。

  16. 必要に応じてコメントを入力し、[OK] をクリックして確定します。

パイプラインの実行

ここまでの手順でパイプライン全体が構成されました。次はそれをテストします。前の手順で作成した Packer テンプレートでは、ソースイメージ ファミリーとして windows-2016 を使用しました。ウェブサーバーを実行するときに Windows Server のディストリビューションをフル機能で実行すると、リソースの消費量が増えるだけでメリットはありません。フル機能ではなく、Windows Server 2016 Core を使用するようにテンプレートを変更して、この変更を使用して CI / CD パイプライン全体を機能させます。

  1. Visual Studio でファイル packer.json を開きます。
  2. 次のようにソースイメージ ファミリーを変更します。

    "source_image_family": "windows-2016-core",
  3. チーム エクスプローラーを開き、[ホーム] ビューに切り替えます。

  4. [変更] をクリックします。

  5. [変更] で packer.json を右クリックし、[ステージ] をクリックします。

  6. Use Windows Server Core」といった Commit メッセージを入力します。

  7. [すべてをコミットしてプッシュ] をクリックします。

  8. Azure Pipelines で、[Build and Release] > [Bulids] の順に選択し、ビルドが自動的に開始されたことを確認します。

    進行中のビルドのリスト(図では Music Store のビルドが進行中)を示すスクリーンショット

    ステータスが [Succeeded] に切り替わるまで 10~15 分かかります。

  9. ビルドが完了したら、[Build and Release] > [Releases] の順に選択し、リリース プロセスが開始されたことを確認します。

    リリース プロセスが開始されたことを示すスクリーンショット

  10. [Release-1] をクリックして詳細ページを開き、Dev ステージのステータスが [Succeeded] に切り替わるのを待ちます。場合によっては、メニューの [Refresh] ボタンをクリックするか、ブラウザページを再読み込みしてステータスを更新する必要があります。

  11. Cloud Shell で、次のコマンドを実行して、開発環境のロードバランサの IP アドレスを取得します。

    gcloud compute forwarding-rules list | grep orchard-dev | awk '{print $2}'
  12. ブラウザで、以前のステップで取得した URL を使用して Orchard のインストール先に移動します。

    http://[DEV_IP]/orchard/

    ロードバランサが利用可能になるまで数分かかるため、最初はエラーが表示される場合があります。準備が整ったら、Orchard CMS が正常にデプロイされていることを確認します。

    ブラウザページで実行中の Orchard CMS アプリを表示するスクリーンショット

  13. Azure Pipelines の Prod ステージで、[Approve] をクリックして本番環境へのデプロイを開始します。

    リリースページとメッセージ「A pre-deployment approval is pending ... Approve or Reject」を表示しているスクリーンショット

  14. Prod ステージのステータスが [Succeeded] に切り替わるのを待ちます。場合によっては、ブラウザのページを手動で更新する必要があります。

  15. Cloud Shell で次のコマンドを実行して、本番環境のロードバランサの IP アドレスを取得します。

    gcloud compute forwarding-rules list | grep orchard-prod | awk '{print $2}'
  16. ブラウザで、前のステップで取得した URL を使用して Orchard のインストール先に移動します。

    http://[PROD_IP]/orchard/

    ここでも、ロードバランサが利用可能になるまで数分かかるため、最初はエラーが表示されることがあります。準備ができると、再度 Orchard CMS ページが表示されます。今回は本番環境クラスタで稼働しています。

クリーンアップ

チュートリアル完了後は、作成したリソースをクリーンアップして、これ以後リソースの料金が発生しないようにします。

Azure Pipelines プロジェクトの削除

Azure DevOps でプロジェクトを削除します。Azure DevOps プロジェクトを削除すると、ソースコードの変更もすべて失われるので注意してください。

GCP プロジェクトの削除

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ