📦

HashiCorp Packer が HCP (HashiCorp Cloud Platform) で利用可能になりました

2022/03/19に公開

HCP (HashiCorp Cloud Platform) ってなに?

HashiCorp のプロダクトが SaaS モデルで利用可能なクラウドサービスです。
https://cloud.hashicorp.com/

  • Terraform Cloud
  • HCP Vault
  • HCP Consul

というラインナップがこれまで提供されていましたが、先日新たに HCP Packer が正式に GA (General Availability) として仲間に加わりました。
利用するためには HCP アカウントの作成が必要です。

HashiCorp プロダクトといえば OSS をまず想像しますが、近年はクラウドサービスにも力を入れており、今後も新たなプロダクトが追加されていく予定です。

Packer おさらい

Packer は HashiCorp の中でもかなり初期から存在する OSS プロダクトで (最初のリリースは2013年5月)、事前に定義された設定ファイルから "ゴールデンイメージ" を作成し、さらにそれを複数のプラットフォームへ簡単に展開することが可能です。

Amazon EC2 (AMI), Azure VM, GCP (GCE VM) などの主要なクラウドベンダーはもちろん、その他にも VMware や Docker など、様々な種類のイメージ作成に対応しています。

https://www.packer.io/plugins

豊富な plugin も用意されており、例えば Ansible と組み合わせることにり、任意のアプリケーションのインストールや設定を行った上でイメージ化する、という一連の動作を簡単にコード化して自動化できます。

HCP Packer

現時点で HCP Packer が提供する唯一の機能は、HCP Packer Registry とよばれる、マシンイメージの metadata をクラウド上で管理するためのレジストリです。
*現時点では、HCP Packer == HCP Packer Registry だと思っても大丈夫です (いずれ HCP Packer ファミリーとして別の機能が提供されるまでは)。

重要なのは、HCP Packer Registry はあくまでもマシンイメージの metadata 保管場所であり、マシンイメージそのものが push される場所ではない、ということです。HCP Packer Registry は、既存の OSS Packer と組み合わせて使うことでそのメリットが最大限に発揮される、いわば extention のようなツールといえます。

HCP Packer Registry の何が嬉しいのか

ここまでの説明だけでは、単なる metadata レジストリの何が嬉しいのか、という感想に落ち着きます。
これにはまず HCP Packer Registry が解決しようとした問題を理解する必要があります。

特に大規模な環境での Packer 利用において、作成されたマシンイメージを "ちゃんと" 管理することの難しさが浮き彫りになってきました。イメージを簡単に作成し展開できるようになった一方で、実際には、

  • どのマシンイメージが最新なのか
  • そのマシンイメージはどの時点での Git commit に紐付いているのか
  • IaC ツールに対してのイメージ ID (e.g., AMI ID) の更新作業

など、マシンイメージの提供者、マシンイメージの利用者、両者にとって管理コストが問題になるケースが多く聞かれました。
これを解決するため、Packer での build 時に HCP Packer Registry に対してイメージと紐づく metadata を同時に登録し、各イメージに対して任意の属性情報を持たせることで管理性を高め、外部ツールから目的のイメージ ID を簡単に取得するインターフェイスを提供する、というアイデアが生まれ、HCP Packer Registry が誕生しました。

Iterations と Channels

HCP Packer Registry の重要な概念として、IterationsChannels があります。

Iterations は、Packer での build 毎にインクリメントされ、それぞれの iteration にそのビルドによって作成されたマシンイメージの情報が追加されます。例えば下記の画像の例では、3つの iteration が存在する (3回ビルドが行われた) ことが分かります。

各 iteration をクリックすると、そのビルドの詳細が確認できます。
下記の画像の例では、AWS EC2 (AMI) および GCP (GCE VM) に対してそれぞれマシンイメージの作成が行われたことが分かります。

Channels は、特定の channel に対して現在有効な iteration を1つアサインすることができるオブジェクトです。これにより、外部のツールは iteration のバージョンを気にすることなく、目的の channel 名さえ知っていれば、常に有効なマシンイメージの情報を取得できるようになります。

先ほどの interation 一覧の画面をもう一度見てみます。

この画面では、development, staging channel は iteration version 2 を利用、production channel は iteration version 1 を利用、という設定が行われていることが分かります。

この状態で、例えば staging でマシンイメージが問題ないことが確認されたら、iteration version 2production channel にもプロモートする、といった操作が簡単に行なえます。

Packer templates の書き方

HCP Packer Registry を使うには、build block 内で hcp_packer_registry を宣言します。下記は build block のみ抜粋しています。全体を含むサンプルはこちら

bucket_labels はバケットレベルでの任意の label (bucket -> iteration という階層構造)、build_labels は各ビルドに対する任意の label です。

build {
  hcp_packer_registry {
    bucket_name = "learn-packer-ubuntu"
    description = <<EOT
Some nice description about the image being published to HCP Packer Registry.
    EOT
    bucket_labels = {
      "owner"          = "platform-team"
      "os"             = "Ubuntu",
      "ubuntu-version" = "Focal 20.04",
    }

    build_labels = {
      "build-time"   = timestamp()
      "build-source" = basename(path.cwd)
      "git-commit-hash" = "${local.truncated_sha}"
      "git-commit-author" = "${local.author}"
      "foo" = "bar"
    }
  }
  sources = [
    "source.amazon-ebs.basic-example-west"
  ]
}

なお、全ての Builder plugin が HCP Packer Registry に対応している訳ではありません。利用する際には、対象の plugin ページに HCP Packer Ready のマークが表示されているかどうかを確認してみてください。

例えば下記の Docker plugin では HCP Packer Ready のマーク表示によって HCP Packer Registry に対応していることが分かります。
https://www.packer.io/plugins/builders/docker

Terraform 連携

実際に HCP Packer Registry を Terraform の任意のワークフローから利用する際には、HashiCorp hcp terraform provider の Data Source を利用します。

# This assumes HCP_CLIENT_ID and HCP_CLIENT_SECRET env variables are set
provider "hcp" { }

data "hcp_packer_iteration" "ubuntu" {
  bucket_name = "learn-packer-ubuntu"
  channel     = "development"
}

data "hcp_packer_image" "ubuntu_us_west_1" {
  bucket_name    = "learn-packer-ubuntu"
  cloud_provider = "aws"
  iteration_id   = data.hcp_packer_iteration.ubuntu.ulid
  region         = "us-west-1"
}

output "ubuntu_iteration" {
  value = data.hcp_packer_iteration.ubuntu
}

output "ubuntu_us_west_1" {
  value = data.hcp_packer_image.ubuntu_us_west_1
}

hcp_packer_iteration data source 内で、対象のバケット名、そして最も重要な channel 名を指定します。今回の例では、development channel を指定しています。つまり、development channel に現在アサインされている iteration のビルドを利用することになります。

hcp_packer_image では、上記の hcp_packer_iteration data source から取得した情報を元に iteration_id を設定します。さらに cloud_provider, region を設定することにより、その iteration に含まれる目的のマシンイメージを指定します。

これを実行すると、下記のような出力が得られます (一部省略)。

$ terraform plan

Changes to Outputs:
  + ubuntu_iteration = {
      + author_id           = "HCP-Packer"
      + bucket_name         = "learn-packer-ubuntu"
      + channel             = "development"
      
      ...
    }
  + ubuntu_us_west_1 = {
      + bucket_name     = "learn-packer-ubuntu"
      + build_id        = "01FYEXDSAXGJASSS9C2J0M61ZJ"
      + cloud_image_id  = "ami-0b3c20f2fd2b1e282" // ← ここ
      + cloud_provider  = "aws"
      + component_type  = "amazon-ebs.basic-example-west"
      + created_at      = "2022-03-18T16:23:00.701Z"
      + id              = "01FYEXM8F2416Y3E44GK9TABZ2"
      + iteration_id    = "01FYEXDRTZNVF4PVXHSYN6A752"
      + labels          = {
          + "build-source"      = "learn-hcp-packer-get-started"
          + "build-time"        = "2022-03-18T16:22:57Z"
          + "git-commit-author" = "Shohei Maeda <xxxx>"
          + "git-commit-hash"   = "e7a54117"
        }
      + organization_id = "xxxx"
      + packer_run_uuid = "219ea822-334e-df64-541d-95705ebb536f"
      + project_id      = "xxxx"
      + region          = "us-west-1"
      + timeouts        = null
    }

hcp_packer_image Data Source の出力に含まれる cloud_image_id に、目的の AMI ID が含まれていることが分かります。この値を Terraform 内から参照することで、動的に任意の channel から対象のイメージ ID を取得することができ、Terraform 側で AMI ID を静的に管理する必要がなくなります。

必要となるのはあくまでもターゲットとなる channel 名であり、この channel にどの iteration がアサインされているかを利用者は意識する必要がありません。

セキュリティ・コンプライアンス対策としての iteration revocation 機能

HCP Packer Registry では iteration によって各ビルドを管理していることは説明しました。そしてこの iteration は 必要に応じて revoke (取り消し) することもできます。revoke された iteration は、ユーザーによって完全に削除されない限り再度 restore することも可能です。

例えば作成したマシンイメージにセキュリティ上の問題が見つかり、このイメージの利用を中止したい場合は Revoke immediately によって即座に任意の iteration を revoke するができます。また、組織でのコンプライアンスを目的として、作成された iteration に対して、スケジュールされた未来日の revocation date を設定することもできます。これにより、作成したイメージに対して EOL (end of life) を設定するといった運用も可能になります。

この revoke された iteration がまだ任意の channel にアサインされている場合、その channel の利用者はマシンイメージの情報を得る事ができなくなります。試しに、先ほどの Terraform が参照していた developer channel に現在アサインされている iteration を revoke し、再度実行してみます。

$ terraform plan

Changes to Outputs:
  + ubuntu_iteration = {
      + author_id           = "HCP-Packer"
      + bucket_name         = "learn-packer-ubuntu"
      + channel             = "development"
      
      ...
    }
  + ubuntu_us_west_1 = {
      + bucket_name     = "learn-packer-ubuntu"
      + build_id        = "01FYEXDSAXGJASSS9C2J0M61ZJ"
      + cloud_image_id  = "error_revoked" // ← ここ
      + cloud_provider  = "aws"
      + component_type  = "amazon-ebs.basic-example-west"
      + created_at      = "2022-03-18T16:23:00.701Z"
      + id              = "01FYEXM8F2416Y3E44GK9TABZ2"
      + iteration_id    = "01FYEXDRTZNVF4PVXHSYN6A752"
      + labels          = {
          + "build-source"      = "learn-hcp-packer-get-started"
          + "build-time"        = "2022-03-18T16:22:57Z"
          + "git-commit-author" = "Shohei Maeda <xxxx>"
          + "git-commit-hash"   = "e7a54117"
        }
      + organization_id = "xxxx"
      + packer_run_uuid = "219ea822-334e-df64-541d-95705ebb536f"
      + project_id      = "xxxx"
      + region          = "us-west-1"
      + timeouts        = null
    }

分かりにくいですが、cloud_image_id の値が error_revoked となっています。
このように、revoke された後はイメージ ID は返されません。

この特性を条件分岐に使うことで、エラー時にはリソースの作成をスキップすることもできるでしょう。

resource "aws_instance" "app_server" {
  count = data.hcp_packer_image.ubuntu_us_west_1.cloud_image_id == "error_revoked" ? 0 : 1

  ami           = data.hcp_packer_image.ubuntu_us_west_1.cloud_image_id
  instance_type = "t2.micro"
  tags = {
    Name = "Learn-HCP-Packer"
  }
}

Terraform Cloud Run Tasks との連携

Terraform Cloud の Business プランを利用している場合、HCP Packer が提供している Terraform Cloud Run Tasks 用の Task を利用することができます。

https://cloud.hashicorp.com/docs/packer/manage-image-use/terraform-cloud-run-tasks

これにより、Terraform apply 時に HCP Packer が提供する Run Tasks のポリシーを強制することができます。例えば上記で説明した、イメージが既に revoke されている場合、plan と apply の間のステージで明確なエラーメッセージと共に実行を失敗させたり (このステージで Run Tasks が実行される)、HCP Packer "Plus" tier を利用している場合には、さらに Terraform の定義されたリソース内 (現時点でサポートされている対象リソースは一部のみ) でハードコードされたイメージ ID の検出を試み、見つかった場合には失敗させる、警告を出す、といった事も可能になります。

利用料金

詳細は下記をご覧ください。Standard Free という無料のプランも用意されており、このプランでは最大で10個のイメージ、250 requests/month (API) まで利用可能です。
https://cloud.hashicorp.com/products/packer/pricing

その他リソース

https://learn.hashicorp.com/collections/packer/hcp-get-started

https://cloud.hashicorp.com/docs/packer

https://cloud.hashicorp.com/api-docs/packer

Discussion