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どうするの?隙が多い