GitHub Actions と HCP Packer を利用して Golden Image を作る

## ゴール

- 仮想マシンを上手く運用するために、管理や統率や配布を安全で安心で安定した仮想マシンにしたい 
- Azure で Golden Image の作成・管理プロセスを GitHub Actions や HPC Pakcer を用いた自動化

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/4744bb3c-8354-a774-95b4-69be41edb344.png)

GitHub: https://github.com/koudaiii/jp-packer-101

## 課題設定

仮想マシンは、個人の開発環境からクラウドのマネージドサービス、自社サービスまでたくさんの場所で動いています。本記事は仮想マシンをメインにサービス運営する企業を想定します。仮想マシンを 10 台くらいを運用している中で、カスタマイズされた複数のバージョンのアプリが本番環境で運用され、仮想マシンで直接手作業もしばしば行われている状況です。現在抱えている問題として、 **1.手作業による属人化や暗黙知があり、チームの共有含めて統制された運用が難しく、運用コストが高くなっている** **2.リリース時の影響確認やリリース前の確認も開発環境と本番環境が違っているために作業コストが高く、リリースサイクルがどんどん遅くなっている** **3.サービス運営を保つために不具合や要望があった際は影響範囲を限定させるために個別対応が中心になり、考慮事項が増えてテスト含めた開発の生産性や品質も下がっている状態** です。1,2,3 の完全負のループが出来上がりつつあります。

- 運用コストの増加
- リリースサイクルの鈍化
- サービス開発への影響

※この課題を解決するアプローチはいろいろありますが、タイトルの通り基本的に仮想マシン前提での解決を取り扱います。そもそもの作業発生における課題をなくすことが一番最初に検討されることが多く、その場合は PaaS へ行ったりフルで作り直したりといった話などでてくるため、収集つかないので割愛します。

GitHub Actions + HCP Packer + Azure を使用して、仮想マシンのイメージの作成、バージョニング、配布を効率的に行うプロセスをステップバイステップで解説します。

## Golden Image

### 仮想マシンは便利だけど、大変？

仮想マシンはPC同様に便利ですが、人の数や台数といった規模による影響で途端に難しくなります。手作業で対応したものをすべての仮想マシンに反映してミス漏れなく実施するのは大変な作業です。また元に戻すとなった場合も大変な作業になります。すぐ実施できるのであれば運用保守は回せますが、ターニングポイントになるのは反映するのはなにかの作業と合わせて行うくらいに実施作業が大変になってきたときです。このような状況でセキュリティ対応などで緊急でやらざるを得ないものが来たりすると途端に管理自体が困難になります。このような場合の特徴としてよくある環境の特徴は、本番環境と開発環境がどこまで一致しているのかがわからないといった状況です。きれいな状態の仮想マシンを維持するにはどのようにすると良いでしょうか？

### きれいな状態の仮想マシン

きれいじゃない状態とは環境の把握一致や暗黙知や属人化がなく、常にアップデートされた新品状態の仮想マシンを運用されている状態です。そのために最初の決断として直接手作業で編集や追加削除などを減らすことです。起動時から変わっていない状態が制約になると、どんどん改良をして配布する必要があります。どのようにして改良ときれいな状態を手に入れると良いでしょうか？

### Golden Image とは

この解決策として Golden Image という概念があります。具体的には仮想マシンのイメージを作成し、バージョニング、配布を効率的に行うことで統率とパイプラインを通してきれいな状態の仮想マシンを運用することができるという方法です。この仕組みができると開発環境で必要なオンボーディングと言ったキッティング作業が楽になります。さらに継続的にバージョンを上げて最新の状態で確認できればロールバックするコストも下がります。ここで属人化や個別対応はどんどん減らすことができます。またセキュリティパッチもすぐに確認してイメージを作ってしまえば各自反映も普段からの作業の一環なので安心して実施できます。

つまり、どんどん仮想マシンイメージをリリースをすることできれいな状態と改良の両方を取り込んでいます。Golden Image の実現は、次の３つが維持されている状態を指します。

- 一貫性と再現性: Golden Image は一貫性のある環境を提供します。これにより、開発、テスト、本番環境間での問題を減らすことができます。
- 時間の節約と効率性: 事前に設定されたイメージを使用することで、新しいサーバーを迅速にデプロイし、設定時間を削減できます。
- セキュリティとコンプライアンス: セキュリティ基準やコンプライアンス要件を組み込んだイメージを使用することで、これらの要件を満たすことが容易になります。

## Golden Image を作ってみよう

イメージをたくさん作ることは結局手作業が多く入り、大変です。確認したり、振り返ったり、第三者が見てこれまでの経緯をわかるためにどのようにすると良いでしょうか？ここで出てくるのがコードとして管理として GitHub です。また仮想マシンのイメージを作成には Packer を用います。まずは仮想マシンのイメージは同じサブスクリプション内で共有する想定での手順を進めていきます。

### Packer テンプレートの作成

Packer https://developer.hashicorp.com/packer 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/4d9b7758-810c-8747-5dc3-ee91af16c174.png)

- 複数のプラットフォームにマシンイメージを作成するツール
- マシンイメージの形式はプラットフォームごとに出力可能 ※HCP Packer は hashicorp 社提供のマネージド・サービス
- Chef や Puppet や Ansible のような構成管理を置き換えるものではなく、ツールを一緒に活用してイメージを作成

### インストール方法

https://developer.hashicorp.com/packer/tutorials/docker-get-started/get-started-install-cli

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/59269562-b51d-9eb9-6661-af903c94a5c2.png)

```console
# MacOS 版
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/packer
```

### local PC から仮想マシンのイメージ作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/ee98e3e3-91d2-8f3d-e053-525129244b92.png)


windows についてはこちらの記事が詳細に書かれているためこちらをオススメします。 
[【Packer】日本語化したWindows Server 2022のVMイメージをAzureに作成する](https://qiita.com/m-oka-system/items/fc7428a566b3d6f4b724)

- 仮想マシンのイメージ作成場所を決める

いくつかファイルを作成していくのですが、仮想マシンのイメージ作成には `packer build` コマンドで実行します。実行した際に手元の local PC で仮想マシンのイメージを作成するのではなく、クラウド環境で仮想マシンのイメージが作成されます。そのため、仮想マシンを利用するために今回は Azure 上の設定を例に手順を踏んでいきます。

```console
$ az vm image list --location japaneast --publisher Canonical
You are viewing an offline list of images, use --all to retrieve an up-to-date list
Architecture    Offer                         Publisher    Sku             Urn                                                           UrnAlias    Version
--------------  ----------------------------  -----------  --------------  ------------------------------------------------------------  ----------  ---------
x64             0001-com-ubuntu-server-jammy  Canonical    22_04-lts-gen2  Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest  Ubuntu2204  latest
```

- `ubuntu.pkr.hcl` 

https://github.com/koudaiii/jp-packer-101/blob/c51b88edcb78d759fcf35b906e11bc9c9197b22a/ubuntu.pkr.hcl

```hcl
# Packer が使用するプラグインを定義する
packer {
  required_plugins {
    azure = {
      source  = "github.com/hashicorp/azure"
      version = "~> 2"
    }
  }
}

# Packer が使用する変数を定義する
locals {
  timestamp  = regex_replace(timestamp(), "[- TZ:]", "")
  image_name = "${var.prefix}-ubuntu22-base-${local.timestamp}"
}

# 使用する基盤、イメージの起動方法、イメージへの接続方法を定義する
source "azure-arm" "ubuntu" {
  use_azure_cli_auth                = true # client id などの設定が要らなくなったので一行追加のみ
  image_offer                       = "0001-com-ubuntu-server-jammy"
  image_publisher                   = "Canonical"
  image_sku                         = "22_04-lts-gen2"
  image_version                     = "latest"

  location                          = "Japan East"
  managed_image_name                = local.image_name
  managed_image_resource_group_name = var.az_resource_group
  os_type                           = "Linux"
  vm_size                           = "Standard_B2s"
  # 個人で利用する際には Azure 
  
  azure_tags = {
    owner      = var.owner
    department = var.department
    build-time = local.timestamp
  }

}

# 起動後に Packer が何を行うかを定義する
build {
  sources = ["source.azure-arm.ubuntu"]

  # Make sure cloud-init has finished
  provisioner "shell" {
    inline = ["echo 'Wait for cloud-init...' && /usr/bin/cloud-init status --wait"]
  }
}
```

- `variables.pkr.hcl`

https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/variables.pkr.hcl

```hcl
variable "az_region" {
  description = "The Azure region where the Resource Group exists."
  type        = string
}

variable "az_resource_group" {
  description = "An existing Azure Resource Group where the build will take place and images will be stored."
  type        = string
}

variable "az_subscription_id" {
  description = "Your Azure Subscription ID (required for the shared_image_gallery_destination block)."
  type        = string
  sensitive   = true
}

variable "department" {
  description = "Value for the department tag."
  type        = string
  default     = "PlatformEng"
}

variable "owner" {
  description = "Value for the owner tag."
  type        = string
  default     = "image.engineer"
}

variable "prefix" {
  description = "This prefix will be included in the name of most resources."
  type        = string
  default     = "gpsjptech"
}
```

- `packer.auto.pkrvars.hcl` という秘匿な情報を扱うファイル

https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/packer.auto.pkrvars.hcl.example

`packer.auto.pkrvars.hcl.examle`

```hcl
az_region          = "japaneast"
az_resource_group  = "<az_resource_group>"               # Must already exist
az_subscription_id = "<az_subscription_id>"             # Must already exist
department         = "GPS JP Tech"
owner              = "image.engineer"
prefix             = "gpsjptech"
```

- Azure 用 setup スクリプト

Azure のアカウントとサブスクリプションと適切な権限があれば、サブスクリプション指定して `script/setup -s <subscription_id>` をすると Packer on Azure に最低限必要変数を埋めた `packer.auto.pkrvars.hcl` を生成します。


`script/setup`

https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/script/setup

```shell
#!/bin/bash

set -e

PREFIX=$(date +%Y%m%d%H%M%S)

az_region=japaneast
az_resource_group=$PREFIX-packer

while getopts s: OPT; do
    case $OPT in
    "s")
        FLG_S="TRUE"
        SUBSCRIPTION_ID="$OPTARG"
        ;;
    *)
        echo -e "Usage: script/setup -s SUBSCRIPTION_ID" 1>&2
        exit 1
        ;;
    esac
done

if [ "$FLG_S" != "TRUE" ]; then
    echo -e "Usage: script/setup -s SUBSCRIPTION_ID" 1>&2
    exit 1
fi

az login

az provider register -n Microsoft.VirtualMachineImages
az provider register -n Microsoft.Compute
az provider register -n Microsoft.KeyVault
az provider register -n Microsoft.Storage
az provider register -n Microsoft.Network

az group create -n $az_resource_group -l $az_region

cp packer.auto.pkrvars.hcl.example packer.auto.pkrvars.hcl

# MacOS sed command
sed -i '' -e "s/<az_subscription_id>/$SUBSCRIPTION_ID/g" ./packer.auto.pkrvars.hcl
sed -i '' -e "s/<az_resource_group>/$az_resource_group/g" ./packer.auto.pkrvars.hcl

echo "ready to packer build"
```

- 実行

```console
$ script/setup -s xxxx-xxxx-xxxx-xxx-xxx
```

hcl ファイルや Azure の準備ができたら、 packer コマンド問題がないかチェック

```console
$ packer init . # 利用する pakcer plugin などのインストール(ここで Azure 専用をダウンロード)
$ packer fmt . # packer のフォーマッター
$ packer validate . # validate の確認。ただしクラウドのエラーまでは見ないため、　packer の構文としてのチェックがメイン
```

- `packer build .` 仮想マシンイメージ作成


```console
$ packer build .                                             
azure-arm.ubuntu: output will be in this color.

==> azure-arm.ubuntu: Running builder ...
    azure-arm.ubuntu: Creating Azure Resource Manager (ARM) client ...
==> azure-arm.ubuntu: Getting source image id for the deployment ...
==> azure-arm.ubuntu:  -> SourceImageName: '/subscriptions/<sensitive>/providers/Microsoft.Compute/locations/Japan East/publishers/Canonical/ArtifactTypes/vmimage/offers/0001-com-ubuntu-server-jammy/skus/22_04-lts-gen2/versions/latest'
==> azure-arm.ubuntu: Creating resource group ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> Location          : 'Japan East'
==> azure-arm.ubuntu:  -> Tags              :
==> azure-arm.ubuntu:  ->> build-time : 20231129175644
==> azure-arm.ubuntu:  ->> department : GPS JP Tech
==> azure-arm.ubuntu:  ->> owner : image.engineer
==> azure-arm.ubuntu: Validating deployment template ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> DeploymentName    : 'pkrdpsfs4hdkhyw'
==> azure-arm.ubuntu: Deploying deployment template ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> DeploymentName    : 'pkrdpsfs4hdkhyw'
==> azure-arm.ubuntu: Getting the VM's IP address ...
==> azure-arm.ubuntu:  -> ResourceGroupName   : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> PublicIPAddressName : 'pkripsfs4hdkhyw'
==> azure-arm.ubuntu:  -> NicName             : 'pkrnisfs4hdkhyw'
==> azure-arm.ubuntu:  -> Network Connection  : 'PublicEndpoint'
==> azure-arm.ubuntu:  -> IP Address          : '20.89.56.93'
==> azure-arm.ubuntu: Waiting for SSH to become available...
==> azure-arm.ubuntu: Connected to SSH!
==> azure-arm.ubuntu: Provisioning with shell script: /var/folders/jt/yc369n416rvd6l0d2zm7ng8h0000gn/T/packer-shell1619857953
    azure-arm.ubuntu: Wait for cloud-init...
    azure-arm.ubuntu:
    azure-arm.ubuntu: status: done
==> azure-arm.ubuntu: Querying the machine's properties ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu:  -> Managed OS Disk   : '/subscriptions/<sensitive>/resourceGroups/pkr-Resource-Group-sfs4hdkhyw/providers/Microsoft.Compute/disks/pkrossfs4hdkhyw'
==> azure-arm.ubuntu: Querying the machine's additional disks properties ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu: Powering off machine ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu:  -> Compute ResourceGroupName : 'pkr-Resource-Group-sfs4hdkhyw'
==> azure-arm.ubuntu:  -> Compute Name              : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu:  -> Compute Location          : 'Japan East'
==> azure-arm.ubuntu: Generalizing machine ...
==> azure-arm.ubuntu: Capturing image ...
==> azure-arm.ubuntu:  -> Image ResourceGroupName   : '20231130024359-packer'
==> azure-arm.ubuntu:  -> Image Name                : 'gpsjptech-ubuntu22-base-20231129175644'
==> azure-arm.ubuntu:  -> Image Location            : 'Japan East'
==> azure-arm.ubuntu: 
==> azure-arm.ubuntu: Deleting Virtual Machine deployment and its attatched resources...
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Compute/virtualMachines : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/networkInterfaces : 'pkrnisfs4hdkhyw'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/publicIPAddresses : 'pkripsfs4hdkhyw'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/virtualNetworks : 'pkrvnsfs4hdkhyw'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvnsfs4hdkhyw'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Compute/virtualMachines : 'pkrvmsfs4hdkhyw'
==> azure-arm.ubuntu: Waiting for deletion of all resources...
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/networkInterfaces : 'pkrnisfs4hdkhyw'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkripsfs4hdkhyw'
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/publicIPAddresses resource. Will retry.
==> azure-arm.ubuntu: Name: pkripsfs4hdkhyw
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/virtualNetworks resource. Will retry.
==> azure-arm.ubuntu: Name: pkrvnsfs4hdkhyw
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkripsfs4hdkhyw'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvnsfs4hdkhyw'
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/virtualNetworks resource. Will retry.
==> azure-arm.ubuntu: Name: pkrvnsfs4hdkhyw
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/publicIPAddresses resource. Will retry.
==> azure-arm.ubuntu: Name: pkripsfs4hdkhyw
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvnsfs4hdkhyw'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkripsfs4hdkhyw'
==> azure-arm.ubuntu:  Deleting -> Microsoft.Compute/disks : '/subscriptions/<sensitive>/resourceGroups/pkr-Resource-Group-sfs4hdkhyw/providers/Microsoft.Compute/disks/pkrossfs4hdkhyw'
==> azure-arm.ubuntu: Removing the created Deployment object: 'pkrdpsfs4hdkhyw'
==> azure-arm.ubuntu: 
==> azure-arm.ubuntu: Cleanup requested, deleting resource group ...
==> azure-arm.ubuntu: Resource group has been deleted.
Build 'azure-arm.ubuntu' finished after 3 minutes 36 seconds.

==> Wait completed after 3 minutes 36 seconds

==> Builds finished. The artifacts of successful builds are:
--> azure-arm.ubuntu: Azure.ResourceManagement.VMImage:

OSType: Linux
ManagedImageResourceGroupName: 20231130024359-packer
ManagedImageName: gpsjptech-ubuntu22-base-20231129175644
ManagedImageId: /subscriptions/<sensitive>/resourceGroups/20231130024359-packer/providers/Microsoft.Compute/images/gpsjptech-ubuntu22-base-20231129175644
ManagedImageLocation: Japan East
```

先程生成したファイルの秘匿情報は、ログ上では全部 <sensitive> に変換されるので安心です。単純に個人で Azure VM の image 作る上ではこれで最低限のセットアップが終了です。

また、イメージ作成からイメージ保存まで VM on Azure で行われています。このときの Packer はあくまでも Community Packer の利用範囲のため、 hashicorp のクラウドは利用していません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/89cbf01e-83f7-1040-2cae-86d18d1875a1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/d434911f-30f0-022b-8cfe-19707c179c0b.png)

早速画面から VM を立ち上げることができます。

### HCP Packer とは何か？

- バージョン管理とコンプライアンス: イメージのバージョン管理、セキュリティとコンプライアンスの管理。細かい配布の設定
- クラウド間の管理: 複数のクラウドプロバイダーでの管理。terraform と連携することで配布までもマルチクラウド

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/9218ddc0-a1dc-6903-6814-ebc1a26909eb.png)

### HCP Packer のセットアップ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/1241c54a-a05b-8daf-c95a-50f5f72bb490.png)

https://developer.hashicorp.com/packer/tutorials/hcp-get-started

アカウントの登録後に HCP Packer に Registry します。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/b670c522-e2d0-eefa-32b8-6de5c6138e66.png)

アカウントの登録を終えると Client ID と Client secret を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/24cc6190-f347-4e69-1008-33b98ba0bdab.png)

- 環境変数

```console
 export HCP_CLIENT_ID=
 export HCP_CLIENT_SECRET=
```

ここで HCP Packer のつなぎ込みができる状態です。 HCP Packer は単純にメタデータを送るだけのため、どこにどんなメタデータを送るかを build{} に指定するだけです。

https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/ubuntu.pkr.hcl#L52-L64

```hcl
build {
  hcp_packer_registry {
    bucket_name = "goldenimage"
    description = ""
    bucket_labels = {
      "owner"          = var.owner
      "department"     = var.department
      "os"             = "Ubuntu",
      "ubuntu-version" = "22.04",
    }
    build_labels = {
      "build-time" = local.timestamp
    }
  }
  # ...
}
```

- `packer build .` 実行

```console
$ packer build .
Tracking build on HCP Packer with fingerprint "xxxxx"
azure-arm.ubuntu: output will be in this color.

==> azure-arm.ubuntu: Running builder ...
    azure-arm.ubuntu: Creating Azure Resource Manager (ARM) client ...
==> azure-arm.ubuntu: Getting source image id for the deployment ...
==> azure-arm.ubuntu:  -> SourceImageName: '/subscriptions/<sensitive>/providers/Microsoft.Compute/locations/Japan East/publishers/Canonical/ArtifactTypes/vmimage/offers/0001-com-ubuntu-server-jammy/skus/22_04-lts-gen2/versions/latest'
==> azure-arm.ubuntu: Creating resource group ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> Location          : 'Japan East'
==> azure-arm.ubuntu:  -> Tags              :
==> azure-arm.ubuntu:  ->> department : GPS JP Tech
==> azure-arm.ubuntu:  ->> owner : image.engineer
==> azure-arm.ubuntu:  ->> build-time : 20231130083429
==> azure-arm.ubuntu: Validating deployment template ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> DeploymentName    : 'pkrdp4tvzpjepw8'
==> azure-arm.ubuntu: Deploying deployment template ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> DeploymentName    : 'pkrdp4tvzpjepw8'
==> azure-arm.ubuntu: Getting the VM's IP address ...
==> azure-arm.ubuntu:  -> ResourceGroupName   : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> PublicIPAddressName : 'pkrip4tvzpjepw8'
==> azure-arm.ubuntu:  -> NicName             : 'pkrni4tvzpjepw8'
==> azure-arm.ubuntu:  -> Network Connection  : 'PublicEndpoint'
==> azure-arm.ubuntu:  -> IP Address          : '20.222.66.97'
==> azure-arm.ubuntu: Waiting for SSH to become available...
==> azure-arm.ubuntu: Connected to SSH!
==> azure-arm.ubuntu: Provisioning with shell script: /var/folders/jt/yc369n416rvd6l0d2zm7ng8h0000gn/T/packer-shell2183682659
    azure-arm.ubuntu: Wait for cloud-init...
    azure-arm.ubuntu:
    azure-arm.ubuntu: status: done
==> azure-arm.ubuntu: Querying the machine's properties ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu:  -> Managed OS Disk   : '/subscriptions/<sensitive>/resourceGroups/pkr-Resource-Group-4tvzpjepw8/providers/Microsoft.Compute/disks/pkros4tvzpjepw8'
==> azure-arm.ubuntu: Querying the machine's additional disks properties ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu: Powering off machine ...
==> azure-arm.ubuntu:  -> ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> ComputeName       : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu:  -> Compute ResourceGroupName : 'pkr-Resource-Group-4tvzpjepw8'
==> azure-arm.ubuntu:  -> Compute Name              : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu:  -> Compute Location          : 'Japan East'
==> azure-arm.ubuntu: Generalizing machine ...
==> azure-arm.ubuntu: Capturing image ...
==> azure-arm.ubuntu:  -> Image ResourceGroupName   : '20231130024359-packer'
==> azure-arm.ubuntu:  -> Image Name                : 'gpsjptech-ubuntu22-base-20231130083429'
==> azure-arm.ubuntu:  -> Image Location            : 'Japan East'
==> azure-arm.ubuntu: 
==> azure-arm.ubuntu: Deleting Virtual Machine deployment and its attatched resources...
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Compute/virtualMachines : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/networkInterfaces : 'pkrni4tvzpjepw8'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/publicIPAddresses : 'pkrip4tvzpjepw8'
==> azure-arm.ubuntu: Adding to deletion queue -> Microsoft.Network/virtualNetworks : 'pkrvn4tvzpjepw8'
==> azure-arm.ubuntu: Waiting for deletion of all resources...
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvn4tvzpjepw8'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Compute/virtualMachines : 'pkrvm4tvzpjepw8'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkrip4tvzpjepw8'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/networkInterfaces : 'pkrni4tvzpjepw8'
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/virtualNetworks resource. Will retry.
==> azure-arm.ubuntu: Name: pkrvn4tvzpjepw8
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/publicIPAddresses resource. Will retry.
==> azure-arm.ubuntu: Name: pkrip4tvzpjepw8
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvn4tvzpjepw8'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkrip4tvzpjepw8'
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/virtualNetworks resource. Will retry.
==> azure-arm.ubuntu: Name: pkrvn4tvzpjepw8
==> azure-arm.ubuntu: Couldn't delete Microsoft.Network/publicIPAddresses resource. Will retry.
==> azure-arm.ubuntu: Name: pkrip4tvzpjepw8
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/virtualNetworks : 'pkrvn4tvzpjepw8'
==> azure-arm.ubuntu: Attempting deletion -> Microsoft.Network/publicIPAddresses : 'pkrip4tvzpjepw8'
==> azure-arm.ubuntu:  Deleting -> Microsoft.Compute/disks : '/subscriptions/<sensitive>/resourceGroups/pkr-Resource-Group-4tvzpjepw8/providers/Microsoft.Compute/disks/pkros4tvzpjepw8'
==> azure-arm.ubuntu: Removing the created Deployment object: 'pkrdp4tvzpjepw8'
==> azure-arm.ubuntu: 
==> azure-arm.ubuntu: Cleanup requested, deleting resource group ...
==> azure-arm.ubuntu: Resource group has been deleted.
Build 'azure-arm.ubuntu' finished after 4 minutes 15 seconds.

==> Wait completed after 4 minutes 15 seconds

==> Builds finished. The artifacts of successful builds are:
--> azure-arm.ubuntu: Azure.ResourceManagement.VMImage:

OSType: Linux
ManagedImageResourceGroupName: 20231130024359-packer
ManagedImageName: gpsjptech-ubuntu22-base-20231130083429
ManagedImageId: /subscriptions/<sensitive>/resourceGroups/20231130024359-packer/providers/Microsoft.Compute/images/gpsjptech-ubuntu22-base-20231130083429
ManagedImageLocation: Japan East

--> azure-arm.ubuntu: Published metadata to HCP Packer registry packer/goldenimage/iterations/01HGFNV2SZ2W3FF5DSQWQ6S602
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/951ce2a2-5189-a3e9-a36a-5a18a8cec488.png)

ここから Golden Image として、仮想マシンの修正を繰り返しながらイメージを作っていくことができます。 :clap: 

### GitHub Actions を使って仮想マシンイメージ作成を自動化させてみよう

このままだと各自で `packer build .` することになります。GitHub Actions を用いて `packer build .` を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/48b0b9b8-bfd5-f0e5-019f-f710ab5f37f8.png)

1. ローカル環境で確認、git push
2. GitHub Actions が push をトリガーに Packer セットアップし、 packer build 
3. packer build を行うに当たり、イメージとなる VM を立ち上げ build 開始
4. build 完了後、 image 化と metadata 作成を行い、 HCP Packer にも metadata 送信

これを実現するためには GitHub Actions から Azure を利用できなければなりません。2つの方法があります。権限を上げすぎず secret key を渡さなくてもいいように OIDC の設定が安全です。下記より詳細な手順があります。

- https://learn.microsoft.com/ja-jp/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux
- https://qiita.com/m-oka-system/items/eb88e0e93aa322342f34

これまでの一連の操作を `.github/workflows` 配下に書いていきます。

https://github.com/koudaiii/packer_tutorial/blob/775e25847b3a19598b70df8d4e7f96efa906f273/.github/workflows/build-and-push.yaml#L26C1-L48C110

```yaml
      - name: Run `packer init`
        id: init
        run: "packer init ."

      - name: Run set vars
        id: vars
        run: |
          cp packer.auto.pkrvars.hcl.example packer.auto.pkrvars.hcl
          sed -i "s/<subscription_id>/$AZURE_SUBSCRIPTION_ID/" packer.auto.pkrvars.hcl
        env:
          AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Run `packer validate`
        id: validate
        run: "packer validate ."

      - name: Build Artifact
        run: packer build -color=true -on-error=abort .
        env:
            HCP_CLIENT_ID: ${{ secrets.HCP_CLIENT_ID }}
            HCP_CLIENT_SECRET: ${{ secrets.HCP_CLIENT_SECRET }}
            HCP_PACKER_BUILD_FINGERPRINT: "run.id.${{ github.run_id }}.run.attempt.${{ github.run_attempt }}"
```

Golden Image 作成する際に　VM の中身をより細かく設定したい場合に Ansible と言ったものを活用してやると、開発チームとインフラチームで上手く責任分界含めた改善細工を回せるようになります。リポジトリ https://github.com/koudaiii/packer_tutorial は、実際に private-isu という社内 IUSUCON で作られたものを利用して Ansible 実行しています。

### コスト

Golden Image は、繰り返し使用できるため、新しい環境のセットアップにかかるコストはなくイメージのデータ量が支配的です。価格モデルとしても Packer を用いたビルド中の Azure 上で使用したリソースに応じた課金のみです。HCP Packer の価格モデルは[こちら](https://www.hashicorp.com/products/packer/pricing)
測定の費用感でも、11 個バージョンを持っていた場合の値段($0.003/day)です。Azure Compute Gallery を使わないか、 managed_image を使わないかであればイメージが単純に半分になります。(両方作って残している例になっています)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/4d480802-7416-1ff4-9a79-18e4014d04cb.png)

この金額で冒頭部分に設定した課題が解決できるのでぜひためしたい！となると思います。

## まとめと次のステップ Immutable Infrastructure

今回、きれいな状態の仮想マシンの作り方にフォーカスして書きました。この GitHub 上に置くことでコラボレーションが生まれ、イメージ作成は Packer で行い、 build のトリガーは GitHub Actions に任せ、 build はクラウドで行われ、最後に meta data を HCP Packer に送ります。

Golden Image は、サービスのインフラ基盤でも活用できます。 Immutable Infrastructure という考えを組み合わせるとより安全で安心で安定して仮想マシンのリリースサイクルを回せます。例えば仮想マシンイメージをまるっと入れ替える Blue Green Deployment、Rolling Deployment もやりやすくなります。

## 追加 Azure Compute Gallery でリソースを格納、共有する

https://learn.microsoft.com/ja-jp/azure/virtual-machines/azure-compute-gallery

> Azure Compute Gallery を使用すると、Azure リソース (イメージ、アプリケーション) の構造と体系を構築することができます。 Azure Compute Gallery には次の特長があります。

> - グローバル レプリケーション。1
> - 容易な管理のためのリソースのバージョン管理とグループ化。
> - Availability Zones がサポートされるリージョンでのゾーン冗長ストレージ (ZRS) アカウントによる高可用性リソース。 ZRS では、ゾーンの障害に対する回復性の向上が提供されます。
> - Premium Storage のサポート (Premium_LRS)。
> - コミュニティへの共有、サブスクリプション間の共有、Active Directory (AD) テナント間の共有。
> - 各リージョン内のリソース レプリカを使用したデプロイのスケーリング。
> - ギャラリーを使用すると、全員にリソースを共有したり、組織内のさまざまなユーザー、サービス プリンシパル、AD グループに限定してリソースを共有したりできます。 リソースを複数のリージョンにレプリケートすることで、デプロイのスケーリングにかかる時間を短縮することができます。

仮想マシンのイメージとは別に Azure Compute Gallery 用のイメージを追加しています。ただこの場合、2つ残す理由がないため、 Azure Compute Gallery に絞ることもできます。

![cicd-hcp-packer (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/3738de84-d7e1-53a6-58ee-371eac4d2fac.png)

GitHub: https://github.com/koudaiii/packer_tutorial
スライド: https://speakerdeck.com/koudaiii/hcp-packer-plus-github-actions-on-azure

- Azure 側の準備

```console
$ az sig create \
    -g $az_resource_group \
    --gallery-name $az_image_gallery

$ az sig image-definition create \
   -g $az_resource_group \
   --gallery-name $az_image_gallery \
   --gallery-image-definition $az_image_def_name \
   --publisher myIbPublisher \
   --offer myOffer \
   --sku mySKU \
   --hyper-v-generation V2 \
   --os-type Linux

```

- Packer 側の準備

https://github.com/koudaiii/packer_tutorial/blob/775e25847b3a19598b70df8d4e7f96efa906f273/hcp-packer-ubuntu22-base/ubuntu-focal-base.pkr.hcl#L31-L39

```hcl
source "azure-arm" "ubuntu" {
  # ・・・
  shared_image_gallery_destination {
    subscription         = var.az_subscription_id
    resource_group       = var.az_resource_group
    gallery_name         = var.az_image_gallery
    image_name           = "ubuntu22-base"
    image_version        = formatdate("YYYY.MMDD.hhmm", timestamp())
    replication_regions  = [var.az_region]
    storage_account_type = "Standard_LRS"
  }
  # ・・・
}
```

### 追加2 マルチクラウドでの仮想マシンのイメージ

マルチクラウドで色々な組織で仮想マシンのイメージを共有していくのに HCP Packer は非常に有効活用できます。たとえば、さくらのクラウド https://github.com/sacloud/packer-plugin-sakuracloud などもあり、packer に plugin を入れることで様々環境で仮想マシンイメージを作成することができます。

```console
$ export SAKURACLOUD_ACCESS_TOKEN=[APIトークン]
$ export SAKURACLOUD_ACCESS_TOKEN_SECRET=[APIシークレット]
```

https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/sakuracloud/ubuntu-sakuracloud.pkr.hcl

```hcl
# Packer が使用するプラグインを定義する
packer {
  required_plugins {
    sakuracloud = {
      version = ">= 0.7"
      source  = "github.com/sacloud/sakuracloud"
    }
  }
}

# Packer が使用する変数を定義する
locals {
  password = "TestUserPassword01"
}

# 使用する基盤、イメージの起動方法、イメージへの接続方法を定義する on SakuraCloud
source "sakuracloud" "example" {
  zone = "is1b"

  os_type   = "ubuntu"
  user_name = "ubuntu"
  password  = local.password

  core        = 2
  memory_size = 4

  archive_name        = "packer-example-ubuntu"
  archive_description = "description of archive"
}


# 起動後に Packer が何を行うかを定義する
build {
  sources = ["source.sakuracloud.example"]

  # Faild: setting a build to DONE with no published images is not currently supported.
  # hcp_packer_registry {
  # }

  provisioner "shell" {
    inline = ["date"]
  }
}
```

- `packer build sakuracloud/ubuntu-sakuracloud.pkr.hcl` 実行

```console
sakuracloud.example: output will be in this color.

==> sakuracloud.example: --> step: CreateSSHKey start
==> sakuracloud.example: 	Creating temporary SSH key for instance...
==> sakuracloud.example: <-- step: CreateSSHKey end
==> sakuracloud.example: --> step: CreateServer start
==> sakuracloud.example: 	Creating server...
==> sakuracloud.example: <-- step: CreateServer end
==> sakuracloud.example: --> step: BootWait start
==> sakuracloud.example: <-- step: BootWait end
==> sakuracloud.example: --> step: Read Server Info start
==> sakuracloud.example: 	Waiting for server to become active...
==> sakuracloud.example: <-- step: Read Server Info end
==> sakuracloud.example: Using SSH communicator to connect: 153.127.219.118
==> sakuracloud.example: Waiting for SSH to become available...
==> sakuracloud.example: Connected to SSH!
==> sakuracloud.example: Provisioning with shell script: /var/folders/jt/yc369n416rvd6l0d2zm7ng8h0000gn/T/packer-shell2096019283
    sakuracloud.example: Fri Dec  1 02:38:19 AM JST 2023
==> sakuracloud.example: --> step: PowerOff start
==> sakuracloud.example: 	Shutting down the server...
==> sakuracloud.example: <-- step: PowerOff end
==> sakuracloud.example: --> step: CreateArchive start
==> sakuracloud.example: 	Creating archive...
==> sakuracloud.example: <-- step: CreateArchive end
==> sakuracloud.example: --> step: TransferArchive start
==> sakuracloud.example: 	Transferring archive to other zones...
==> sakuracloud.example: <-- step: TransferArchive end
==> sakuracloud.example: 	Destroying server...
Error: failed to complete HCP-enabled build "sakuracloud.example"

failed to update Packer registry with image artifacts for "sakuracloud.example":
setting a build to DONE with no published images is not currently supported.


Build 'sakuracloud.example' finished after 7 minutes 4 seconds.

==> Wait completed after 7 minutes 4 seconds

==> Builds finished. The artifacts of successful builds are:
--> sakuracloud.example: A archive was created: packer-example-ubuntu (ID: "113501937271") transferred: 

Iteration "01HGGM84BTHDCAN6Z0VFERAV6F" is not complete, the following builds are not done:

* "sakuracloud.example": RUNNING
```

HCP Packer の仕様上、公開URLが払い出される前に DONE するのをサポートしていないらしく HCP Packer からみていると RUNNING -> Failed になっていました。 https://github.com/koudaiii/jp-packer-101/blob/ed821c985004112350a3d583dd0d34502bf74159/sakuracloud/ubuntu-sakuracloud.pkr.hcl#L36-L38 hcp_packer_registry{} を外してもう一度 `packer build` で手に入るため、同じコードベースで十分管理できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/f6e5e5c2-b8c0-58b3-497c-116fa507309d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18843/5440247e-2a15-4c37-5494-9fc976c22d02.png)