Help us understand the problem. What is going on with this article?

Azure BatchのCI/CDを考えてみた

はじめに

本記事は、Azure DevOps Advent Calendar 2019 10日目の記事です。

最近、Azure BatchのCI/CDを構築する機会があったので、
本記事でAzureBatchの作成からAzureDevOpsでCI/CDの構築手順を紹介したいと思います。
また、他に良い方法があればご指摘をお願い致します。

Azure Batchとは

大規模な並列コンピューティングやハイパフォーマンス コンピューティング (HPC) のバッチ ジョブを実行が可能なサービス。

AzureBatchのワークフローとしては、コンピューティング ノード (仮想マシン) のプールを作成を行い、
実行するアプリケーションをAzureStorageにアップロードしてノードにインストールをして、
ノードで実行するジョブ定義してタスクを実行する流れになります。

Azure Batch とは

前準備

まずは、CI/CDパイプラインを構築する前にAzureBatchの環境と
5分毎に稼働しデプロイされている環境をログに出力するアプリを作成します。

アプリの構築手順

AzureBatchで稼働するアプリケーションの作成

コンソールアプリ(.NETFramework)でバージョン.NETFramework,Version=v4.7.2で作成を行います。

visualstudio.PNG

対象のファイルを以下のように変更してください。

Program.cs
using System;
using System.Configuration;

namespace AzureBatchDemoApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(ConfigurationManager.AppSettings["ApplicationName"]);
            Console.WriteLine(ConfigurationManager.AppSettings["ApplicationVersion"]);
            Console.WriteLine(ConfigurationManager.AppSettings["Environment"]);
        }
    }
}

App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <appSettings>
      <add key="ApplicationName" value="AzureBatchDemoApp" />
      <add key="ApplicationVersion" value="1.0" />
      <add key="Environment" value="LOCAL" />
    </appSettings>  
</configuration>

ローカルの実行結果がコンソールに以下のように表示されれば完了です。

2019-12-07_23h58_25.png

成果物の作成

ビルドを行いAzureBatchにアップロードする成果物を作成します。
以下のファイルを LOCALPRODUCTIONに書き換え、bin\Release 内の成果物をZip化します。

AzureBatchDemoApp.exe.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <appSettings>
      <add key="ApplicationName" value="AzureBatchDemoApp" />
      <add key="ApplicationVersion" value="1.0" />
      <add key="Environment" value="PRODUCTION" />
    </appSettings>  
</configuration>

AzureBatchの構築

Batchアカウント・ストレージアカウントの作成

Cloud Shellに下記コマンドを張り付けて、Batchアカウントとストレージアカウントを作成します。

AzureCLI
###################################################
# 変数
###################################################

# リソースグループ名
ResourceGroup="yantzn-azurebatch-je-demo"
# リージョン
Location="japaneast"
# 稼働させるアプリケーション格納用ストレージアカウント
StorageAccount="yantzndemostorage"
# Batchアカウント
BatchAccount="yantzndemobatchaccount"
# アプリケーションの名称
AppName="demoapp"

###################################################
# Batchアカウント・ストレージアカウントの作成
###################################################

# リソースグループの作成
az group create \
    --name $ResourceGroup \
    --location $Location

# リソースグループの作成を待つ
az group wait --created \
    --resource-group $ResourceGroup

# AzureBatch用のストレージアカウントを作成
az storage account create \
    --resource-group $ResourceGroup \
    --name $StorageAccount \
    --location $Location \
    --sku Standard_LRS

# Batchアカウントを作成
az batch account create \
    --name $BatchAccount \
    --storage-account $StorageAccount \
    --resource-group $ResourceGroup \
    --location $Location

# アプリケーションを作成
az batch application create \
   --application-name $AppName \
   --name $BatchAccount \
   --resource-group $ResourceGroup

アプリケーションのアップロード

下記手順に従い、AzureBatchで稼働させるアプリケーションをアップロードします。

batchaccunt.PNG

2019-12-08_23h00_05.png

2019-12-07_23h18_53.png

2019-12-07_20h07_34.png

プールの作成

Cloud Shellに下記コマンドを張り付けて、プールとスケジュールされたジョブを作成します。

AzureCLI

###################################################
# 変数
###################################################

# リソースグループ名
ResourceGroup="yantzn-azurebatch-je-demo"
# Batchアカウント
BatchAccount="yantzndemobatchaccount"
# アプリケーションの名称
AppName="demoapp"
# プールの名称
PoolName="demopool"

###################################################
# プールとスケジュールされたジョブの作成
###################################################

# プールとジョブを作成のためにログイン
az batch account login \
    --name $BatchAccount \
    --resource-group $ResourceGroup \
    --shared-key-auth

# プール作成
az batch pool create \
    --id $PoolName \
    --vm-size standard_a1 \
    --target-dedicated-nodes 1 \
    --image microsoftwindowsserver:windowsserver:2016-datacenter:latest \
    --node-agent-sku-id "batch.node.windows amd64" \
    --application-package-references $AppName


# スケジュールされたジョブの作成
az batch job-schedule create \
    --id demoschedule \
    --job-manager-task-id demotask \
    --job-manager-task-command-line "cmd /c  %AZ_BATCH_APP_PACKAGE_demoapp%/AzureBatchDemoApp.exe" \
    --pool-id $PoolName \
    --recurrence-interval PT5M \
    --start-window PT1M

ここまでの構築結果

2019-12-07_23h46_15.png

2019-12-07_23h46_54.png

2019-12-07_23h44_44.png

2019-12-07_23h51_55.png

Build Pipelines構築

実現すること

前準備では、AzureBatchにリリースする前にbin/Release内のAzureBatchDemoApp.exe.configを書き替えましたが、
やはり手動で本番情報に書き換えることや本番環境の設定ファイルで上書きするのは、ヒューマンエラーの原因となります。
それに、ビルドした後に書き換えるのは意味がありません。

そのため、デプロイしたい環境の設定ファイルでビルドを行い、成果物をZip化するまでを自動化したいと思います。

完成形

Build Pipelinesの完成形は下記となります。

2019-12-08_16h50_33.png

BuildPipelines
pool:
  name: Azure Pipelines
  demands:
  - msbuild
  - visualstudio

steps:
- task: NuGetToolInstaller@1
  displayName: 'Use NuGet 4.4.1'
  inputs:
    versionSpec: 4.4.1

- task: NuGetCommand@2
  displayName: 'NuGet restore'
  inputs:
    restoreSolution: '$(Parameters.solution)'

- task: DownloadSecureFile@1
  displayName: 'Download Production AppConfig'
  inputs:
    secureFile: 'a721f735-aa88-4e9c-9f33-5359a997294b'

- powershell: |
   # ダウンロードした設定ファイルを一時ソースディレクトリに配置
   Move-Item  $(appconfig.secureFilePath) $(Build.SourcesDirectory)/App.config -Force
   Get-ChildItem $(Build.SourcesDirectory)/

  displayName: 'OverWrite AppConfig PowerShell Script'

- task: VSBuild@1
  displayName: 'Build solution **\*.sln'
  inputs:
    solution: '$(Parameters.solution)'
    vsVersion: 16.0
    msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation="$(Build.SourcesDirectory)\\"'
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'
    msbuildArchitecture: x64

- task: ArchiveFiles@2
  displayName: 'Archive bin zip'
  inputs:
    rootFolderOrFile: '$(Build.SourcesDirectory)/bin/Release/*'

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
  condition: succeededOrFailed()


解説

Download Production AppConfig

本番環境の接続情報などのセキュアな情報をGitに含めたくないが、
Build Pipelinesで利用したいというケースの場合、Secure Filesを使います。
また、Secure FilesではRBACの設定が可能となっています。

例えば、本番用アプリケーション設定ファイルとして下記内容のものを用意します。

App_production.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <appSettings>
      <add key="ApplicationName" value="AzureBatchDemoApp" />
      <add key="ApplicationVersion" value="2.0" />
      <add key="Environment" value="PRODUCTION" />
    </appSettings>  
</configuration>

作成したファイルをアップロードします。

2019-12-08_16h44_39.png

以下のようにアップロードしたファイルを指定することで簡単にパイプラインで利用することができます。

2019-12-08_21h04_22.png

実行結果

このような実行結果になれば完了です。

2019-12-08_22h58_43.png

Release Pipelines構築

実現すること

Build Pipelinesで作成した成果物をアプリケーションパッケージにアップロードして適用するまでを自動化します。

完成形

2019-12-08_19h49_54.png

ReleasePipelines
steps:
    - task: AzurePowerShell@4
      displayName: 'Azure PowerShell script: login to Service Principal '
      inputs:
        azureSubscription: '<your azureSubscription>'
        ScriptType: InlineScript
        Inline: |
         # AzureDevOps用のユーザでAzureにログイン
         az login --service-principal -u $(Name) -p $(Password) --tenant $(Tenant)
        FailOnStandardError: true
        azurePowerShellVersion: LatestVersion

variables:
  ScheduleId: 'demoschedule'
  AccountEndpoint: 'https://yantzndemobatchaccount.japaneast.batch.azure.com'
  AccountName: 'yantzndemobatchaccount'

steps:
- task: AzurePowerShell@4
  displayName: 'Azure PowerShell script: Stop Batch Job Schedule'
  inputs:
    azureSubscription: '<your azureSubscription>'
    ScriptType: InlineScript
    Inline: |
     try {
       # スケジュールを無効化
       az batch job-schedule disable --job-schedule-id $(ScheduleId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName)

       # 現在の状態を確認
       $schedule = az batch job-schedule show --job-schedule-id $(ScheduleId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName) | ConvertFrom-Json | Select-Object state
     }catch{
       Write-Error($_.Exception)
     }

     # 無効化出来ていない場合は異常終了する
     If($schedule.state -ne "disabled"){
       exit 1
     }
    FailOnStandardError: true
    azurePowerShellVersion: LatestVersion

variables:
  ApplicationName: 'demoapp'
  AccountName: 'yantzndemobatchaccount'
  ResourceGroup: 'yantzn-azurebatch-je-demo'

steps:
- task: AzurePowerShell@4
  displayName: 'Azure PowerShell script: Application Deploy'
  inputs:
    azureSubscription: '<your azureSubscription>'
    ScriptType: InlineScript
    Inline: |
     try {
       # 現在の既定のバージョンを取得
       $app = az batch application show --application-name $(ApplicationName) --name $(AccountName) --resource-group $(ResourceGroup) | ConvertFrom-Json | Select-Object defaultVersion

       $oldversion = [int]$app.defaultVersion
       $newversion = ($oldversion + 1).ToString("0.0")

       # モジュールの適用
       az batch application package create --application-name $(ApplicationName) --name $(AccountName) --resource-group $(ResourceGroup) --version-name $newversion --package-file $(System.DefaultWorkingDirectory)/$(Build.DefinitionName)/drop/$(Build.BuildId).zip

       # 既定バージョンの変更
       az batch application set --application-name $(ApplicationName) --name $(AccountName) --resource-group $(ResourceGroup) --default-version $newversion

     }catch{
       Write-Error($_.Exception)
     }

    FailOnStandardError: true
    azurePowerShellVersion: LatestVersion

variables:
  NodeId: 'tvmps_df69768de9443ead39cceb5fa9a04bf6eacdad7e99115c2e7895ef9f70e07d46_d'
  PoolId: 'demopool'
  AccountEndpoint: 'https://yantzndemobatchaccount.japaneast.batch.azure.com'
  AccountName: 'yantzndemobatchaccount'

steps:
- task: AzurePowerShell@4
  displayName: 'Azure PowerShell script: Batch Node Reboot'
  inputs:
    azureSubscription: '<your azureSubscription>'
    ScriptType: InlineScript
    Inline: |
     try {
       # ノードの再起動を行う
       az batch node reboot --node-id $(NodeId) --pool-id $(PoolId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName) --node-reboot-option taskcompletion

       # アイドル状態になるまでポーリングを行う
       $i = 0
       while ($i -eq 0) {
         $node = az batch node show --node-id $(NodeId) --pool-id $(PoolId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName) | ConvertFrom-Json | Select-Object state

         write-host $node.state
         if($node.state -eq "idle"){
           write-host "再起動完了"
           exit 0
         }elseif($node.state -eq "creating" -or $node.state -eq "rebooting" -or $node.state -eq "reimaging" `
         -or $node.state -eq "running" -or $node.state -eq "starting" -or $node.state -eq "waitingForStartTask"){
           Start-Sleep -m 10000
         }else{
           Write-Error "再起動失敗"
           exit 1
         }
       }
     }catch{
       Write-Error($_.Exception)
     }

    FailOnStandardError: true
    azurePowerShellVersion: LatestVersion

variables:
  ScheduleId: 'demoschedule'
  AccountEndpoint: 'https://yantzndemobatchaccount.japaneast.batch.azure.com'
  AccountName: 'yantzndemobatchaccount'

steps:
- task: AzurePowerShell@4
  displayName: 'Azure PowerShell script: Start Batch Job Schedule'
  inputs:
    azureSubscription: '<your azureSubscription>'
    ScriptType: InlineScript
    Inline: |
     try {
       # スケジュールを有効化
       az batch job-schedule enable --job-schedule-id $(ScheduleId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName)

       # 現在の状態を確認
       $schedule = az batch job-schedule show --job-schedule-id $(ScheduleId) --account-endpoint $(AccountEndpoint) --account-name $(AccountName) | ConvertFrom-Json | Select-Object state

     }catch{
       Write-Error($_.Exception)
     }

     # 有効化出来ていない場合は異常終了する
     If($schedule.state -ne "active"){
       exit 1
     }
    FailOnStandardError: true
    azurePowerShellVersion: LatestVersion


解説

login to Service Principal

AzureBatchは、az loginを行ってから操作を行う必要となります。
そのため、今回はサービスプリンシバルを使ってログインを行いました。

参考:Azure CLI 2.0 でサービスプリンシパルが簡単に作れるようになっていた

Stop Batch Job Schedule

デプロイを行う前に、5分毎に稼働しているスケジュールを停止します。

Application Deploy

既定のバージョンに+1.0を足した値を新バージョンとして、Build Pipelinesで作成した成果物をアップロードします。

2019-12-08_22h15_33.png

2019-12-08_22h13_51.png

Batch Node Reboot

アップロードしたアプリをノードに適用するには、再起動が必要になります。

プールのアプリケーション パッケージを " 既存 " のプールに追加した場合は、そのコンピューティング ノードを再起動して、アプリケーション パッケージをノードにデプロイする必要があります。

引用:アプリケーション パッケージ

しかし、処理中に再起動をされてしまっては困るケースがあると思いますので、
そういう場合は、az batch node rebootのオプションに-node-reboot-option taskcompletionを設定することで、実行中のタスクが完了したタイミングでノードの再起動を行うことができます。

参考:az batch node reboot

スクリプトは、ノードがアイドル状態になるまでポーリングして実行され、以下のように現在のノードの状態を表示します。
2019-12-08_22h47_36.png

Start Batch Job Schedule)

ノードがアイドル状態になったら停止していたスケジュールを起動します。

実行結果

このような実行結果になれば完了です。

2019-12-08_23h05_04.png

2019-12-08_23h57_33.png

参考資料

Batch を使って大規模な並列コンピューティング ソリューションを開発する

Azure Pipelines を使用する HPC ソリューションの構築とデプロイ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした