しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Packer を利用して GitHub Actions から Azure Compute Gallery へ VM Image を作成する

前までは最近は Azure VM を使うことがほぼ無くなっていたのですが、最近は GPU インスタンスが必要なケースで VMSS の利用を検討することが多くなってきたので、VM Image のカスタマイズを自動化する際に役立つ HashiCorp Packer を触っておきました。

Azure に限ると Packer がベースとなっている Azure Image Builder が用意されていますが、まずはベースとなっている Packer を直接触っておいた方が理解が深まるはずです。

公式ドキュメントにも Packer を使って VM Image を作成する手順が紹介されているので、基本はこのドキュメントを見ながらやればよいのですが、全体的に古い感じだったので定義の参考程度に留めておきました。

Packer が行っていることはテンポラリの VM を作成し、SSH などで接続しスクリプトを実行、完了後に VM を停止して VM Image をキャプチャの 3 ステップとシンプルです。当然ながら自動化に最適なので Packer と GitHub Actions を組み合わせて自動化するのが良いです。

ここから先は Marketplace で公開されている VM Image をベースにして、Azure Compute Gallery へカスタマイズした VM Image を作成した手順となります。

Marketplace で公開されている Image の情報を調べる

まずはベースとなる VM Image を決めて、必要な情報を集めるところがから始めました。Azure Portal からだと探しにくいのが VM Image の情報ですが、一応 Usage Information を開くと少しだけ確認出来ます。

Plan ID は SKU と同じになるのですが、いくつか確認したところ信頼できないので別途取得します。

この辺りの情報を基にして、以下のドキュメントで紹介されているように Azure CLI を使って調べると効率が良さそうでした。油断すると ARM64 向けや Gen 1 VM を勧められるので気を付けましょう。

今回は Ubuntu Server 22.04 LTS のイメージを使うので、以下のコマンドを叩きこんで SKU を取得します。

az vm image list-skus --location japaneast --publisher Canonical --offer 0001-com-ubuntu-server-jammy --query "[].name"

実行すると SKU の一覧が返ってくるので、その中から x64 かつ Gen 2 VM 向けの SKU を選べばよいです。

これでベースとなる VM Image の情報は全て集まったので、何処かにメモしておきます。後で Packer の定義ファイルを作成する際に必要となります。

次は作成した VM Image を保管する先となる Azure Compute Gallery と VM Image Definition を作成しておきます。Azure Compute Gallery は適当な名前を付けて作成すれば問題ありません。

これまでの Managed Image とは異なり、Azure Compute Gallery に VM Image を作成するには先に VM Image Definition を作成しておく必要があるのと、Packer では自動生成してくれないので注意が必要です。

VM Image Definition は Azure Portal から作成できるので、以下のように必要な情報を入力して作成すれば良いです。OS の種類と VM の世代、一般化・特殊化といった項目は選択に注意が必要です。

バージョン入力もありますが、そこは Packer が自動で作成するので空っぽのままで問題ありません。

これで VM Image Definition の作成は完了なので、後は Packer の定義ファイルを作成して実行するだけです。

Packer の定義ファイルを作成する

肝心な Packer の定義ファイルですが JSON と HCL2 で書くことが出来ますが、Terraform を使ったことがある人なら HCL2 で書いた方が楽だと思います。VS Code に HashiCorp HCL 拡張を入れるとシンタックスハイライトも動作しますし、Terraform と同様に Packer 自体にフォーマット機能も用意されています。

Azure VM に関係する設定値は全て以下のドキュメントにまとまっていますが、かなりプロパティが多いので Azure Compute Gallery に VM Image を作成する最小限の定義だけ用意することにしました。

今回作成した Packer の定義ファイルは以下のようになります。本来であればサブスクリプション ID などは環境変数などから与えますが、今回は簡略化のために直接埋め込みます。

Ubuntu Server 22.04 LTS の VM Image を使って、簡単なコマンドだけ実行して一般化を行ってから Azure Compute Gallery に VM Image を作成するという流れです。provisioner はサンプルのまま使っていますが、ファイルのアップロードなど高度なカスタマイズが可能です。

source "azure-arm" "autogenerated_1" {
  use_azure_cli_auth = true

  subscription_id = "<SUBSCRIPTION_ID>"
  tenant_id       = "<TENANT_ID>"
  image_publisher = "canonical"
  image_offer     = "0001-com-ubuntu-server-jammy"
  image_sku       = "22_04-lts-gen2"
  os_type         = "Linux"
  location        = "Japan East"
  vm_size         = "Standard_D2s_v3"

  shared_image_gallery_destination {
    subscription         = "<SUBSCRIPTION_ID>"
    resource_group       = "rg-packer-demo"
    gallery_name         = "galpackerdemo"
    image_name           = "vmi-packer-demo"
    image_version        = "1.0.0"
    replication_regions  = ["Japan East"]
    storage_account_type = "Standard_LRS"
  }
}

build {
  sources = ["source.azure-arm.autogenerated_1"]

  provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline = [
      "apt-get update",
      "apt-get upgrade -y",
      "apt-get -y install nginx",
      "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
    ]
    inline_shebang = "/bin/sh -x"
  }
}

Azure Compute Gallery に VM Image を作成するためには shared_image_gallery_destination ブロックが必要になりますが、ここだけは旧名称になっているので注意が必要です。前述したように VM Image Definition は作成してくれませんので、未作成の場合はエラーとなります。

実行するために重要な Azure の認証情報ですが、公式ドキュメントでは Service Principal を使う方法が紹介されているのですが、実際には Azure CLI を使うのが一番楽なので use_azure_cli_auth を追加しておきます。それ以外にもいくつかの方法がありますので、公式ドキュメントを参照してください。

ちなみに Packer は Azure VM を作成するのにテンポラリのリソースグループから作成するため、サブスクリプションレベルでの権限が必要となります。地味にはまるポイントなので注意してください。権限周りの設定が完了すれば packer build ***.pkr.hcl を実行すればローカルで動きます。

Terraform とは異なり Packer はまだ OpenID Connect に対応していないようなので、GitHub Actions から使う場合には Azure CLI 経由で使うことになります。

GitHub Actions で packer build を実行する

ローカルで動かしていた packer build コマンドをそのまま GitHub Actions のワークフローで実行するようにすれば、問題なく Packer を使った VM Image の CI/CD が行えるようになります。

実行に必要な認証情報は強い権限が必要になるので OpenID Connect を使って守っておきたいです。

ワークフロー自体は難しいことが全くないため、サクッと紹介だけしておきます。Packer のセットアップ自体は HashiCorp から専用の Action が公開されているので、これを使えば簡単に終わります。

後は Azure CLI で OpenID Connect を使ったログインを行えば、packer build が動くようになります。

name: Packer Build

on:
  push:
    branches: [ "master" ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v3

      - name: Setup HashiCorp Packer
        uses: hashicorp/setup-packer@v2.0.0
        
      - name: Azure Login
        uses: Azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Run packer build
        run: packer build ubuntu.pkr.hcl

作成したワークフローを実行すると、以下のようなログと共に VM の作成から Azure Compute Gallery への VM Image の作成まで自動的に行われます。VM Image 作成後の各リソースの削除に割と時間がかかるので、何か高速化の余地がありそうな気はします。

実行完了後に Azure Compute Gallery を確認すると、指定したバージョンで VM Image が作成されていることが確認出来ます。本来ならバージョンはタグやコミットハッシュなどを上手く組み合わせたいところです。

後はこの VM Image を利用して VMSS などを作成するだけです。VMSS はスケーリングが非常に簡単なのが特徴なので、Azure Compute Gallery に必要な処理が完了済みの VM Image を作っておくと有利です。

補足 : Marketplace の購入プランが必要な場合

Marketplace には購入プランが必要な VM Image が公開されていますが、その VM Image をベースにする際にはプラン情報を Packer や VM Image Definition にも追加する必要があります。

Packer のドキュメントにあるように plan_info ブロックを追加して定義します。

購入プランについては以下のドキュメントを参照してください。必要なプラン情報を取得する方法から説明が記載されているので、取得した情報を Packer 定義と VM Image Definition に設定します。

VM Image Definition に設定を忘れた場合には、VM Image は作成されても起動できないイメージが完成するため、地味にはまることになります。あまり使うことはないかも知れませんが、知っておいて損はないです。