はじめに
本記事は、Azure DevOps Advent Calendar 2019 10日目の記事です。
最近、Azure BatchのCI/CDを構築する機会があったので、
本記事でAzureBatchの作成からAzureDevOpsでCI/CDの構築手順を紹介したいと思います。
また、他に良い方法があればご指摘をお願い致します。
Azure Batchとは
大規模な並列コンピューティングやハイパフォーマンス コンピューティング (HPC) のバッチ ジョブを実行が可能なサービス。
AzureBatchのワークフローとしては、コンピューティング ノード (仮想マシン) のプールを作成を行い、
実行するアプリケーションをAzureStorageにアップロードしてノードにインストールをして、
ノードで実行するジョブ定義してタスクを実行する流れになります。
前準備
まずは、CI/CDパイプラインを構築する前にAzureBatchの環境と
5分毎に稼働しデプロイされている環境をログに出力するアプリを作成します。
アプリの構築手順
AzureBatchで稼働するアプリケーションの作成
コンソールアプリ(.NETFramework)でバージョン.NETFramework,Version=v4.7.2
で作成を行います。
対象のファイルを以下のように変更してください。
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"]);
}
}
}
<?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>
ローカルの実行結果がコンソールに以下のように表示されれば完了です。
成果物の作成
ビルドを行いAzureBatchにアップロードする成果物を作成します。
以下のファイルを LOCAL
⇒ PRODUCTION
に書き換え、bin\Release
内の成果物をZip化します。
<?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アカウントとストレージアカウントを作成します。
###################################################
# 変数
###################################################
# リソースグループ名
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で稼働させるアプリケーションをアップロードします。
プールの作成
Cloud Shellに下記コマンドを張り付けて、プールとスケジュールされたジョブを作成します。
###################################################
# 変数
###################################################
# リソースグループ名
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
ここまでの構築結果
Build Pipelines構築
実現すること
前準備では、AzureBatchにリリースする前にbin/Release
内のAzureBatchDemoApp.exe.config
を書き替えましたが、
やはり手動で本番情報に書き換えることや本番環境の設定ファイルで上書きするのは、ヒューマンエラーの原因となります。
それに、ビルドした後に書き換えるのは意味がありません。
そのため、デプロイしたい環境の設定ファイルでビルドを行い、成果物をZip化するまでを自動化したいと思います。
完成形
Build Pipelinesの完成形は下記となります。
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の設定が可能となっています。
例えば、本番用アプリケーション設定ファイルとして下記内容のものを用意します。
<?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>
作成したファイルをアップロードします。
以下のようにアップロードしたファイルを指定することで簡単にパイプラインで利用することができます。
実行結果
このような実行結果になれば完了です。
Release Pipelines構築
実現すること
Build Pipelinesで作成した成果物をアプリケーションパッケージにアップロードして適用するまでを自動化します。
完成形
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で作成した成果物をアップロードします。
Batch Node Reboot
アップロードしたアプリをノードに適用するには、再起動が必要になります。
プールのアプリケーション パッケージを " 既存 " のプールに追加した場合は、そのコンピューティング ノードを再起動して、アプリケーション パッケージをノードにデプロイする必要があります。
しかし、処理中に再起動をされてしまっては困るケースがあると思いますので、
そういう場合は、az batch node reboot
のオプションに-node-reboot-option taskcompletion
を設定することで、実行中のタスクが完了したタイミングでノードの再起動を行うことができます。
スクリプトは、ノードがアイドル状態になるまでポーリングして実行され、以下のように現在のノードの状態を表示します。
Start Batch Job Schedule)
ノードがアイドル状態になったら停止していたスケジュールを起動します。
実行結果
このような実行結果になれば完了です。