Packer で .NET Framework 4.8 をインストールした Windows Server 2019 の Azure VM イメージを作成

はじめに

Azure にある公式の Windows Server 2019 Datacenter の VM イメージは、記事執筆時点で .NET Framework 4.8 が入っていなかったので、 .NET Framework 4.8 入りの VM イメージをビルドした。Packer で。

www.packer.io

Azure リソース グループを作成

ビルドした VM イメージの置き場となるリソースグループを作成しておく。PowerShell ではなく Azure CLI で行った。

az group create -n rg-example -l japaneast

Azure 資格情報の作成

Packer で Azure VM イメージを作成する際に必要な、Azure の資格情報を取得。

az ad sp create-for-rbac --query "{ client_id: appId, client_secret: password, tenant_id: tenant }"

サブスクリプションの ID も。

az account show --query "{ subscription_id: id }"

Packer テンプレートを定義

Packer は HCL に対応しているので、JSON ではなく HCL でテンプレートを記述した。ファイル名は windows.pkr.hcl。

variable "client_id" {
  type = string
}
variable "client_secret" {
  type = string
}
variable "tenant_id" {
  type = string
}
variable "subscription_id" {
  type = string
}
variable "managed_image_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "managed_image_name" {
  type = string
  default = "vmi-example"
}
variable "build_resource_group_name" {
  type = string
  default = "rg-example"
}
variable "vm_size" {
  type = string
  default = "Standard_D2_v2"
}

source "azure-arm" "windowsserver2019" {
  client_id = var.client_id
  client_secret = var.client_secret
  tenant_id = var.tenant_id
  subscription_id = var.subscription_id

  managed_image_resource_group_name = var.managed_image_resource_group_name
  managed_image_name = var.managed_image_name

  os_type = "Windows"
  image_publisher = "MicrosoftWindowsServer"
  image_offer = "WindowsServer"
  image_sku = "2019-Datacenter"

  communicator = "winrm"
  winrm_use_ssl = true
  winrm_insecure = true
  winrm_timeout = "10m"
  winrm_username = "packer"

  azure_tags = {
    dept = "Engineering"
    task = "Image deployment"
  }

  build_resource_group_name = var.build_resource_group_name
  vm_size = var.vm_size
}

build {
  sources = [
    "sources.azure-arm.windowsserver2019"
  ]

  provisioner "powershell" {
    inline = [
      "while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
      "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }",
    ]
  }

  # .NET Framework 4.8 をインストール
  provisioner "windows-shell" {
    inline = [
      "curl -fSLo dotnet-framework-installer.exe https://download.visualstudio.microsoft.com/download/pr/7afca223-55d2-470a-8edc-6a1739ae3252/abd170b4b0ec15ad0222a809b761a036/ndp48-x86-x64-allos-enu.exe",
      ".\\dotnet-framework-installer.exe /q",
      "del .\\dotnet-framework-installer.exe",
    ]
  }
  provisioner "windows-restart" {}

  provisioner "powershell" {
    inline = [
      "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit",
      "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10  } else { break } }",
    ]
  }
}

Packer イメージをビルド

取得しておいた Azure の資格情報を指定して packer build 実行。

packer build \
  -var "client_id=<Azure 資格情報の client_id>" \
  -var "client_secret=<Azure 資格情報の client_secret>" \
  -var "tenant_id=<Azure 資格情報の tenant_id>" \
  -var "subscription_id=<Azure 資格情報の subscription_id>" \
   .\windows.pkr.hcl

ビルドは結構時間がかかる。10分以上、30分未満くらい。コーヒーでも飲みながら待つといい。

おわりに

今回は powershell provisioner を使って、 .NET Framework 4.8 のインストーラーを VM 内にダウンロードし、インストールした。

記事には書かなかったが、ローカルにあらかじめ用意したインストーラーを、file provisioner を使ってアップロードする方法も試したが、WinRM でのアップロードが激遅でまったく実用的ではなかった。

大きいファイルを VM イメージ内に含めたい場合は、Azure ファイル共有を介した方がいいだろうな。