Packer + Terraform でイメージを管理する
Packer は Terraform と同じく HashiCorp が提供する、マシンイメージ (EC2 AMI, GCE Machine Image など) を構成管理するツールです。実用上は Terraform の構成のなかで Packer で作成されたマシンイメージを data で参照することが多いです。これを発展させて、terraform apply しただけで (Packer による) イメージの作成まで実行されるとより体験がよいですが、Packer と Terraform は直接のインテグレーションがありません。
これは terraform_data を利用して Terraform から packer コマンドを実行することで実現可能です。
# Google Cloud Compute Engine machine image の例
# Packer によるマシンイメージの作成
# data/my_image.pkr.hcl が存在すると仮定する
resource "terraform_data" "packer_my_image" {
triggers_replace = {
image_name = "my-image-${substr(filesha256("${path.module}/data/my_image.pkr.hcl"), 0, 8)}"
project_id = var.project_id
region = var.region
zone = var.zone
}
provisioner "local-exec" {
when = create
working_dir = "${path.module}/data"
command = <<-EOT
packer init my_image.pkr.hcl
packer build \
-var 'image_name=${self.triggers_replace.image_name}' \
-var 'project_id=${self.triggers_replace.project_id}' \
-var 'region=${self.triggers_replace.region}' \
-var 'zone=${self.triggers_replace.zone}' \
my_image.pkr.hcl
EOT
}
provisioner "local-exec" {
when = destroy
command = <<-EOT
gcloud compute images delete \
--project ${self.triggers_replace.project_id} \
--quiet \
${self.triggers_replace.image_name}
EOT
}
}
# 作成されたイメージの参照
data "google_compute_image" "my_image" {
name = terraform_data.packer_my_image.triggers_replace.image_name
project = terraform_data.packer_my_image.triggers_replace.project_id
}
この実装では Packer ソースファイルの編集 (hash の変化) をトリガーにイメージを作成しなおします。Destroy provisioner により旧イメージは replace/destroy タイミングで削除されます。
注意点として destroy provisioner は configuration が残っている状態でしか実行されないという制限があります。すなわち、構成を削除することを意図して terraform_data リソースの記述を削除またはコメントアウトして apply しても destroy provisioner は実行されません。これは通常のリソースと挙動が異なるので改善を期待したいところです。
上記は GCE の例ですが、AWS でも同様に構成することができるでしょう。
resource "terraform_data" "packer_my_ami" {
triggers_replace = {
ami_name = "my-ami-${substr(filesha256("${path.module}/data/my_ami.pkr.hcl"), 0, 8)}"
profile = var.aws_profile
region = var.aws_region
}
provisioner "local-exec" {
when = create
...
}
provisioner "local-exec" {
when = destroy
command = <<-EOT
IMAGE_ID=$(aws ec2 describe-images \
--profile ${self.triggers_replace.profile} \
--region ${self.triggers_replace.region} \
--filters 'Name=name,Values=${self.triggers_replace.ami_name}' \
--query 'Images[0].ImageId' \
--output text)
aws ec2 deregister-image \
--profile ${self.triggers_replace.profile} \
--region ${self.triggers_replace.region} \
--image-id "$IMAGE_ID" \
--delete-associated-snapshots
EOT
}
}
data "aws_ami" "my_ami" {
owners = ["self"]
region = terraform_data.packer_my_ami.triggers_replace.region
filter {
name = "name"
values = [terraform_data.packer_my_ami.triggers_replace.ami_name]
}
}
このようにすることで、例えば GitHub Actions を利用した Terraform のための CI/CD ワークフローに Packer をシームレスに統合可能です。
name: Terraform Apply for Google Cloud
on:
push:
branches:
- main
paths:
- "**.tf"
- "**.pkr.hcl" # Packer ソースファイルの変更でもトリガーする
jobs:
apply:
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: hashicorp/setup-terraform@v3
- uses: hashicorp/setup-packer@v3 # Packer のインストール
- uses: google-github-actions/auth@v3
- uses: google-github-actions/setup-gcloud@v3 # Packer で gcloud コマンドを利用しているためインストールする
# このステップで Packer によるイメージの作成 + IaC 反映がすべて行われる
- run: terraform apply -auto-approve