tech.guitarrapc.cóm

Technical updates

Deployment Groupを用いてManagedにVMへのデプロイを行う

現状、サーバーサイドの多くはコンテナで動かすことが可能です。そのため、VMに直接アプリケーションをデプロイする機会はかなり減りました。最高ですね。しかし、UWPをはじめとして一定の要件下においてはコンテナ対応が技術的に難しく、VMへのデプロイをせざるを得ないケースがあります。

VMへのデプロイ、古くからおこなわれていますがクラウドネイティブにマネージドにとなると、案外手が限られます。なるべくクラウド側の意図しているであろう方法に沿うように組むのは大事な選択です。

今回は、AzureのVMに対してアプリケーションをデプロイするにあたり、Azure DevOpsのDeployment Groupを使ってマネージドさを見ながら実現してみましょう。

TL;DR

Azure DevOps Pipelineを使うと、VMへのデプロイ時に動的にDeployment Targetを設定できます。 DevOps Pipelineを使わず、TerraformでVM作成時にDeployment Targetを設定できます。

動的な構成ならAzureDevOps Pipeline 、静的な構成ならTerraformがよいのでオススメです。 いずれの方法でも、VMへのDeployment Targetのインストール/アンインストールがマネージドにハンドルされるので、なるべくこちらを使うといいでしょう。いずれもにしても、「VMにリモートログインしてマニュアルでDeployment Targetをインストールする」のはなるべく避けるのが大事です。

なおDeployment Groupを用いるということは、VM Scalesetとは別のコンセプトになるので混ぜるな注意です。

概要

AWSにおいてVMへのデプロイとなると、Code Deployを用いてCode PipelineからのManagedなデプロイが定番です。VMに対してCI/CD側からパッケージを指定して、VM上のエージェントが指示された通り展開するわけです。

これと同様の仕組みをAzureで達成するのが、Azure DevOpsのDeployment Group / Deployment Targetです。 本稿では、ホストとなるVM群に対して、デプロイ時に停止したVMの開始、Deployment Targetでデプロイを実施、というコストも鑑みつつよくある感じの流れを見てみます。

Deployment Target とは

Deployment Targetは、Azure DevOpsの用語でデプロイ対象となるホスト(Windows/Linux) をさします。Build Agentと同様に、Deployment Agentがホスト上でサービス稼働し、Azure DevOpsでDeployment Groupとして複数のDeployment Targetがグルーピングさて、生存確認や最終リリースを確認できます。

Deployment Targetは、Azure DevOps Pipelineからのデプロイ指示を受けてデプロイタスクが実行されます。(Build Agent同様ですね) どんな処理を行うかは、DevOps Pipeline上でタスクとして定義、実行/停止指示ができ、Deployment Targetが入っているホスト上でその処理が直接実行されます。

WinRM/SSHなどを通したリモート展開と比べて考えることが少ない一方で、エージェントを展開しておく必要があります。とはいえ、AzureDevOps的にはDeployment Groupが処理の単位のため、デプロイ前にDeployment Groupを経由してホストの生存が確認できればデプロイに支障がありません。つまり、デプロイ前にインストールされていなくてもデプロイ時にDeployment Agentを動的に展開することで、ホストへの事前インストールを気にせず、デプロイに集中できます。

Use deployment groups in a release pipeline - Azure Pipelines | Microsoft Docs

Deployment Group の作成

Deployment Agentを作成する前に、デプロイ対象をグルーピングする単位であるDeployment Groupを作成しておきましょう。

このあたりはドキュメントの通りに進めれば問題ありません。

Use deployment groups in a release pipeline - Azure Pipelines | Microsoft Docs

Azure DevOpsでPipelinesからDeployment groupsを選択しましょう。

Pipelines > Deployment groups を選択

Deployment Group一覧が表示されるので、+ Newで追加しましょう。

Deployment Group の追加を行う

Azureあるあるですが、ここでつけるDeployment Groupの名前がDeployment Agentと直接紐づくため重要になります。(つまり変更が難しい)

Deployment group name を付けるのは慎重に

今回は、Hogemoge-Dev-Deployという名前でDeployment Groupを作成します。

作成したDeployment Groupを開くと、次のようにエージェントを展開するための設定が表示されます。

PowerShellやBash でエージェントをインストールするコマンドが表示されている

ポイントはPersonal Access Tokenで、Deployment AgentがAzure DevOpsと通信するときには現状必ずPATが必要です。このあたり、AWSのIAMのような統合ができておらず、本当にイケテナイです。PAT期限がることもあり、なかなか本当に厳しい。

Authenticate with personal access tokens - Azure DevOps | Microsoft Docs

Deployment Agent の展開

さて、AWSのCode Deployの場合はAWS提供ホストには事前にインストールされているのでインストールを考える必要はありません。

CodeDeploy とは - AWS CodeDeploy

しかし、Azure提供ホストにはDeployment Agentが事前にインストールされていません。Deployment Agentを展開するには2つの方法があります。

  1. Deployment Agentを手動で展開する
  2. VM拡張を使って展開する

Agent の展開に必要なPersonal Access Tokenの権限

インストールの前に、罠となるPATの権限について触れておきます。Deployment Group Agentの展開に関するドキュメントを見ると、エージェントのインストールにはPATを利用するとあります。

Deploy a build and release agent on Windows - Azure Pipelines | Microsoft Docs

ドキュメントにはDeployment GroupsRead & manage権限が必要とありますが、実際にエージェントを展開するときはこの権限だけでは足りず、「Deployment Groupが見つかりません」と怒られます。

Deployment Groups の Read & manage権限をつける

追加で、Project and TeamRead 権限をPATに与えてあげましょう。

Project and Team の Read 権限をつける

ではインストールを見ていきます。

Deployment Agentを手動で展開する

この記事の主題ではないので手動展開は無視してokです。しかし、Azure VMではなく、オンプレミスのホストにインストールする場合は手動で展開したいでしょう。その場合は、次の公式ドキュメントを見ればいいです。ただこの方法は、ホスト構成時に手作業やAnsibleなどでの余計な構成が必要となるため、Azure VMが対象の場合は仕方ないとき以外、受け入れたくない選択肢です。

Deploy a build and release agent on Windows - Azure Pipelines | Microsoft Docs

マニュアルで展開する場合は、Use a personal access token in the script for authenticationを有効にして、Deployment Groupの展開PowerShell(Windows)/Bash(Linux) をコピー&ペーストします。

当初、軽く流すつもりでしたがドキュメントとPowerShellスクリプトの両方に対応できていないトラップがあるので、簡単に触れておきます。自動生成されるPowerShellスクリプトは、--auth と --token 引数が間違っているため、これを削除して実行する必要があります。幸い、AuthとTokenは実行中に聞かれるので、クリップボードにコピーした値を入れなおしましょう。*1

こんな感じになります。タグは何もなしでok、アカウントもデフォルトでokです。

修正したコマンドで実行する

これでDeployment Targetが登録されます。おおむね、Build Agentと一緒です。

Deployment Targetが追加された

VM拡張を使って展開する

手動でインストールをしたくない時に、AzureがVMに対してマネージドに何かしらの処理を差し込むために提供している方法が「VM Extensions (VM拡張)」です。

Windows 用の Azure VM 拡張機能とその機能 | Microsoft Docs

Azure DevOpsのDeployment AgentもVM拡張が提供されており、ドキュメントも提供されています。この方法であれば、VM拡張の導入を定義をするだけで、Deployment Agentのインストール作業はマネージドに行われます。この記事は、マネージドを意図しているのでこちらを利用しましょう。

Provision agents for deployment groups - Azure Pipelines | Microsoft Docs

余談ですが、先ほどの手動エージェントインストールはConcepts-> Agents > Self-hosted <OS> agentsにあり、VM拡張でのインストールはConcepts > Releases > Provision Deployment Agentsにあるのなんというかドキュメントの作りが残念で実際にたどり着けてない人が周りに多いのでまずそう。

VM拡張でのDeployment Agentの展開

さて、一口にVM拡張でのDeployment Agentの展開といっても方法は2つあります。

  1. VMを構成時にDeployment Agentを展開する
  2. デプロイ時にResource Group単位でホストにDeployment Agentを展開する

それぞれどう違うのでしょうか。

もし、Resource Group内でVMが1つのDeployment Groupにまとめてokなら、「デプロイ時にResource Group単位でホストにDeployment Agentを展開する」を選択するのがオススメです。この方法なら、デプロイ時に自動的にエージェントが展開されて、VMの構成とデプロイが完全に分離されるだけでなく、Deployment Targetの管理が完全にマネージドになります。

一方で、Resource Gorup内の対象VMごとにDeployment Groupを分けたい場合は、構成時にDeployment Agentを展開する必要があります。TerraformやPlumiなどどの方法を使っても、VMに対してエージェント展開を管理する必要があるので避けたいところです。

順番にそれぞれの展開を見てみましょう。

VMを構成時にDeployment Agentを展開する

TerraformのModuleでさくっとやります。Windowsを例にしますが、LinuxやWindowsも変わりません。

resource "azurerm_virtual_machine" "main" {
  name                          = "${var.name_prefix}-vm"
  location                      = "${var.location}"
  resource_group_name           = "${var.resource_group_name}"
  network_interface_ids         = ["${azurerm_network_interface.main.id}"]
  vm_size                       = "${var.vm_size}"
  delete_os_disk_on_termination = true

  storage_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2016-Datacenter"
    version   = "latest"
  }

  storage_os_disk {
    name              = "${var.name_prefix}-os-disk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "${var.managed_disk_type}"
    disk_size_gb      = "${var.os_disk_size_gb}"
  }

  os_profile {
    computer_name  = "${var.computer_name}"
    admin_username = "${var.vm_username}"
    admin_password = "${var.vm_password}"
  }

  os_profile_windows_config = {
    provision_vm_agent = true

    winrm {
      protocol = "HTTP"
    }
  }

  tags = "${var.tags}"
}

resource "azurerm_virtual_machine_extension" "main" {
  name                       = "TeamServicesAgent"
  resource_group_name        = "${data.azurerm_resource_group.current.name}"
  location                   = "${var.location}"
  virtual_machine_name       = "${module.assetgenerator.vm_name}"
  publisher                  = "Microsoft.VisualStudio.Services"
  type                       = "TeamServicesAgent"
  type_handler_version       = "1.0"
  auto_upgrade_minor_version = true
  tags                       = "${local.tags}"

  settings = <<SETTINGS
    {
        "VstsAccountName": "${var.VstsAccountName}",
        "TeamProject": "${var.TeamProject}",
        "DeploymentGroup": "${var.DeploymentGroup}",
        "AgentName": "${module.assetgenerator.vm_name}"
    }
SETTINGS

  protected_settings = <<SETTINGS
    {
        "PATToken": "${var.PAT_DEPLOYMENT_AGENT}"
    }
SETTINGS
}

適当にvariablesは公開してもらうとして、azurerm_virtual_machine_extensionを使うことでVM作成時にDeployment Agentが自動的に展開されて、Deployment GroupにTargetが登録されます。手動での展開に比べて、圧倒的に楽なうえに自動展開できます。

最も重要なのは、azurerm_virtual_machineリソースのos_profile_windows_configにあるprovision_vm_agent = trueです。(os_profile_linux_configにはない) 有効でないとVM拡張が利用できず、デフォルトがfalseなので項目が抜けないようにします。provision_vm_agentが有効化できるのは「VM作成時」だけなので作ってから気づいても手遅れです。

Azure Resource Manager: azurerm_virtual_machine - Terraform by HashiCorp

デプロイ時にResource Group単位でホストにDeployment Agentを展開する

Azure DevOps Pipelinesのリリース時に、Resource Group内のVMに対してDeployment Agentをインストールします。この方法を使うと、デプロイの流れの中で対象が動的に定まるため、スケールアウト時も考慮不要になります。

リリースパイプラインのタスクを見てみましょう。

リリースパイプラインのタスクはAgent Job と Deployment group job で分けるのがコツ

ポイントは、「Build Agent」と「Deployment Group」の処理が分かれることです。実際のデプロイは、Deployment Group単位で実行されますが、そのデプロイ先をBuild Agentで動的に作成します。

Build Agentでやることは2つです。

  • 停止したVMのStart
  • VM Extensions経由でDeployment Agentのインストール

YAMLを見ていきましょう。

「停止したVMのStart」は、AzureResourceGroupDeploymentを使うとstart actionでAzure DevOpsから実行できます。ポイントは、Resource Group単位の処理になります。止めたままにしたいVMとか混じるのだめ。融通があんまり利きません。

steps:
- task: AzureResourceGroupDeployment@2
  displayName: 'Azure Deployment:Start action on VM'
  inputs:
    azureSubscription: YOUR_SUBSCRIPTION
    action: Start
    resourceGroupName: 'YOUR_RESOURCE_GROUP'

「VM Extensions経由でDeployment Agentのインストール」もAzureResourceGroupDeploymentを使うことで、VM拡張でインストールされます。先ほど作ったDeployment Groupを特定できるようにAzureDevOpsのチーム名、Deployment Group名を指定しましょう。

steps:
- task: AzureResourceGroupDeployment@2
  displayName: 'Azure Deployment:Select Resource Group action on ResourceGroup'
  inputs:
    azureSubscription: YOUR_SUBSCRIPTION
    action: 'Select Resource Group'
    resourceGroupName: 'YOUR_RESOURCE_GROUP'
    enableDeploymentPrerequisites: ConfigureVMWithDGAgent
    teamServicesConnection: 'YOUR_SERVICE_CONNECTION'
    teamProject: 'YOUR_AZUREDEVOPS_TEAM'
    deploymentGroupName: 'YOUR_DEPLOYMENT_GROUP_NAME'

これらの処理がデプロイ前に実行されることで、実際にDeployment Groupでデプロイする前にVMの起動とエージェントの展開が約束されます。

ポイントは、このタスク自体にはPATの指定がなく、Azure DevOps Team Service Connectionで設定します。

Azure DevOps Team Service ConnectionでDeployment Group の追加に必要な接続URLやPATを設定する

Connection URLには、https://YOUR_TEAM.visualstudio.com/と指定します。 PATの権限に注意してください。VMへのエージェントインストール、デプロイ実施の両方が必要なので、

Service connections in Azure Pipelines and Team Foundation Server - Azure Pipelines | Microsoft Docs

あとはいい感じでデプロイしてあげればokです。

Q&A

いくつかやっている中ではまりどころがあったものの、文脈とずれるのでここで。

VM拡張で Deployment Agent をインストールしたあとアンインストールしたい

現時点では、アンインストールしようとするとエラーが出ます。VM拡張でのアンインストールは一部しか対応されていないのでそれに該当するらしい.... つまりVM作り直しましょう。

Failed to delete the virtual machine extension in Azure ARM VM - Stack Overflow

VM拡張のARM Templateはないの

あるようですが、私はARM Teamplteが好きじゃないので使っていません。

VSTS Agent Extension in ARM template · Issue #1044 · Microsoft/azure-pipelines-agent

PATの認証って365日だけどどうするの

永続できないのはセキュリティモデル的にはいいですが、マシンユーザー的な利用を想定していないあたりAzureの融通の利かなさ.... Azure DevOps的には、Service Connectionを複数作れるので、次のいずれかがいいでしょう。

  • PATのRegenerateで期限を新しくしてPATを再生成して、今のService ConnectionのPATを更新
  • 期限前にPATを新しく発行して別にService Connectionを作って割り当てを変える

Authenticate with personal access tokens - Azure DevOps | Microsoft Docs

Ref

Deploying to Azure VM using Deployment Groups | Azure DevOps Hands-on-Labs

How to install VSTS deployment group agents on Azure VMs | Mickaël Derriey's blog

*1:お、AnsibleやTerraformのprovisionerどうするの?隙が多い