Google Cloud で Packer を使って カスタムイメージをビルドしてみる
Packer とは
Packer は Terraform で馴染みのある Hashicorp社が提供するサーバテンプレーティングツールです。
Terraform などのプロビジョニングツールは VMインスタンスやデータベースといったインフラリソースをコードによってデプロイできるものですが、Packer は OS、ソフトウェア、ライブラリ、設定ファイル等を含んだイメージをコードで管理できるのが特徴のツールです。
Terraform でもソースイメージを指定して起動スクリプトを実行することにより特定のソフトウェアのインストールや設定ファイルの編集を行うことはできますが以下のような課題が考えられます。
- 複数行にわたる起動スクリプト記述により Terraform コードによる管理が煩雑になる
- デプロイ時にスクリプト実行の余計な時間がかかる
- スクリプト失敗時のトレースやエラーハンドリング、再試行が困難
Packer で事前に必要なソフトウェアや設定がインストールされた構成済カスタムイメージを作成し、それを Terraform で参照してデプロイすることで、コード管理の簡素化やデプロイの効率化が実現できます。
Packer x Terraform
Packer でカスタムイメージを作成するには Packer テンプレート と呼ばれる設定ファイルを利用しますが、HCL2(Hashicorp Configuration Language 2) で記述できるので、 Terraform に馴染みがある方でしたら習得も容易ではないでしょうか。
Packer と Google Cloud
Packer で利用可能な googlecompute
プラグイン(Packer ビルダー) が Hashicorp より提供されています。これにより、Google Cloud 上でデプロイ可能なカスタムイメージを作成することができます。
また、Google Cloud の Cloud Shell には Packer がプリインストールされていました。今回の検証では Cloud Shell を Packer の実行環境として利用してみます。
$ packer --version
1.9.4
さらに、Google Cloud 上でビルドプロセスを容易に実現できる Cloud Build では、Packer コミュニティビルダーイメージを利用して packer コマンドを呼び出すこともできます。
これにより、Packer によるカスタムイメージ作成と Terraform によるインスタンスのデプロイを Cloud Build の一連のパイプラインで容易に実現することも可能です。
アーキテクチャ概要
検証の前に、Google Cloud での Packer を使った カスタムイメージビルドのアーキテクチャ概要を説明します。
Packer による Google Cloud でのカスタムイメージビルドアーキテクチャ概要
まずは Packer テンプレートを用意します。Packer テンプレートには、インストールする Packer ビルダー(カスタムイメージを作成するコンポーネント) や カスタムイメージの設定が記述されています。事前準備として、packer init
で Packerテンプレートを参照して Google Cloud 用の Packer ビルダーをインストールします。その後、packer build
により Google Cloud 上で VMインスタンスの起動と設定、カスタムイメージの作成を行います。
Packer テンプレート
Packer テンプレートは、ビルドするイメージやそのビルド方法などを定義する設定ファイルです。今回は以下の Packer テンプレートを用意します。
packer {
required_plugins {
googlecompute = {
version = ">= 1.1.6"
source = "github.com/hashicorp/googlecompute"
}
}
}
source "googlecompute" "default" {
project_id = "<Project Name>"
zone = "asia-northeast1-a"
machine_type = "e2-micro"
image_name = "packer-test-image"
disk_size = 10
source_image_family = "debian-12"
ssh_username = "packer"
}
build {
sources = ["source.googlecompute.default"]
provisioner "shell" {
inline = [
# NGINX のインストール
"sudo apt-get update",
"sudo apt-get install -y nginx",
# NGINX のサービスを開始
"sudo systemctl enable nginx"
]
}
}
Packer テンプレートは HCL2(Hashicorp Configuration Language 2) で記述できます。以下より各ブロックについて説明します。
packer ブロック
Packerバージョンの指定や Packer ビルダー(プラグイン)の指定など、Packerの設定を定義します。
required_plugins
: イメージのビルドに必要とするPacker ビルダーのプラグインを指定します。ここでは github.com/hashicorp/googlecompute
を指定しています。
source ブロック
仮想化の種類、インスタンスの起動方法、及びイメージへの接続方法などを定義します。後述する Build ブロックによって呼び出されます。
source ブロックの Required 項目について説明します。
project_id
: インスタンスの起動先及びカスタムイメージ保存先のプロジェクトID。source_image
またはsource_image_family
: ソースイメージまたはソースイメージファミリーを指定。ソースイメージファミリーを指定した場合は、非推奨ではない最新のイメージが選択される。※1zone
: Packer によるカスタムイメージを作成するためにインスタンスを起動するゾーン。ssh_username
: 起動したインスタンスに SSH ログインする際のユーザ。SSH にて接続する場合は必須。packer ビルダー は本設定に従って sudo アクセス権を持つユーザを作成。※2
※1: source_image
またはsource_image_family
に指定可能なリストはgcloud compute image list
で確認できます。--filter
を利用して特定のディストリビューションに絞りこむことも可能です。
$ gcloud compute images list --filter="name~'debian'"
NAME: debian-11-bullseye-arm64-v20240815
PROJECT: debian-cloud
FAMILY: debian-11-arm64
DEPRECATED:
STATUS: READY
NAME: debian-11-bullseye-v20240815
PROJECT: debian-cloud
FAMILY: debian-11
DEPRECATED:
STATUS: READY
NAME: debian-12-bookworm-arm64-v20240815
PROJECT: debian-cloud
FAMILY: debian-12-arm64
DEPRECATED:
STATUS: READY
NAME: debian-12-bookworm-v20240815
PROJECT: debian-cloud
FAMILY: debian-12
DEPRECATED:
STATUS: READY
※2: デフォルトのログイン方法は SSH となります。ssh_username のみを指定した場合、Packer ビルダーは SSH キーペアを作成して公開鍵を Compute Engine のインスタンスメタデータに保存します。Compute Engine のインスタンス起動時にメタデータを参照して公開鍵をインスタンスに保存します。なお、use_os_login
を利用して OS Login による SSH ログインとすることも可能です。
その他 optional の設定やデフォルト値については以下をご参照ください。
build ブロック
イメージ作成のためのソースの指定やプロビジョニング方法をなど、Packer が実行するビルドプロセスの具体的な手順を定義します。
sources
: イメージ作成のためのソースを指定。ここでは source ブロックで定義したgooglecompute.default
を指定。
provisioner
: パッケージのインストールやアプリケーションのダウンロード、ユーザの作成など、イメージのプロビジョニングに使用するスクリプトやツールを指定。ここで指定したshell
はシェルスクリプトを実行するためのプロビジョナー。
本検証では NGINX をインストールし、インスタンス起動時にサービス開始するスクリプトをプロビジョナーで実行するビルドプロセスを定義しています。
その他のプロビジョナーは以下をご参照ください。
やってみた
Packer テンプレートの準備
Google Cloud コンソールより Cloud Shell を起動し、先ほどの Packer テンプレート(googlecloud.pkr.hcl
)を検証用ディレクトリに配置します。
$ mkdir packer_test
$ cd packer_test
$ ls
googlecloud.pkr.hcl
packer init
packer init <Packer Template>
で Packer ビルダーのプラグイン googlecompute
をインストールします。
$ packer init googlecloud.pkr.hcl
Installed plugin github.com/hashicorp/googlecompute v1.1.6 in "/home/<Project Name>/.config/packer/plugins/github.com/hashicorp/googlecompute/packer-plugin-googlecompute_v1.1.6_x5.0_linux_amd64"
packer build
packer build <Packer Template>
でビルドを実行します。
$ packer build googlecloud.pkr.hcl
googlecompute.default: output will be in this color.
==> googlecompute.default: Checking image does not exist...
# SSHキーペアの作成
==> googlecompute.default: Creating temporary RSA SSH key for instance...
==> googlecompute.default: no persistent disk to create
# ソースイメージの参照
==> googlecompute.default: Using image: debian-12-bookworm-v20240815
# インスタンスの作成
==> googlecompute.default: Creating instance...
googlecompute.default: Loading zone: asia-northeast1-a
googlecompute.default: Loading machine type: e2-micro
googlecompute.default: Requesting instance creation...
googlecompute.default: Waiting for creation operation to complete...
googlecompute.default: Instance has been created!
# インスタンスの起動
==> googlecompute.default: Waiting for the instance to become running...
# 外部IPアドレスの作成
googlecompute.default: IP: 34.84.159.79
# 外部IPアドレスを宛先として SSH 接続
==> googlecompute.default: Using SSH communicator to connect: 34.84.159.79
==> googlecompute.default: Waiting for SSH to become available...
==> googlecompute.default: Connected to SSH!
# プロビジョナーを利用してシェルスクリプトを実行
==> googlecompute.default: Provisioning with shell script: /tmp/packer-shell4250237553
...
# インスタンスの削除
==> googlecompute.default: Deleting instance...
googlecompute.default: Instance has been deleted!
# イメージの作成
==> googlecompute.default: Creating image...
==> googlecompute.default: Deleting disk...
googlecompute.default: Disk has been deleted!
Build 'googlecompute.default' finished after 2 minutes 31 seconds.
==> Wait completed after 2 minutes 31 seconds
==> Builds finished. The artifacts of successful builds are:
--> googlecompute.default: A disk image was created in the '<Project Name>' project: packer-test-image
上記のビルドプロセスについて補足します。
Packer ビルダーは SSH キーペアを作成し、公開鍵をインスタンスメタデータに保存します。Packer テンプレートに設定したソースイメージやマシンタイプなどの設定、インスタンスメタデータの公開鍵などを参照してインスタンスを作成・起動します。インスタンスにアタッチする外部IPアドレスを作成し、Packer ビルダーは外部IPアドレスを宛先として SSH 接続します。SSH によるログイン後、プロビジョナーを利用してシェルスクリプトを実行しプロビジョニングを行います。最後にインスタンスの削除とカスタムイメージの作成を行いビルドプロセスが完了します。
Packer によるビルドプロセス
ビルドプロセス中に Packer によって作成されたインスタンスが Google Cloud コンソールの Compute Engine のページからも確認できます。ビルドプロセスが完了すると削除されています。
ビルドプロセス中に packer によって一時的に作成されたインスタンス
カスタムイメージは Google Cloud コンソールの [Compute Engine]->[イメージ] から確認できます。
Packer によってビルドされたイメージ
動作確認
カスタムイメージを利用して Compute Engine VMインスタンスを起動してみます。Cloud Shell から以下コマンドを実行します。
gcloud compute instances create packer-test \
--zone=asia-northeast1-a \
--image=packer-test-image \
--image-project=<Project Name>
インスタンスが起動したら SSH でログインし、Packer テンプレートで設定したスクリプト(NGINXのインストールと起動)の動作状況を確認します。
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Fri 2024-08-30 07:39:50 UTC; 4min 12s ago
Packer のビルドプロセスでスクリプトが実行された状態でイメージが作成されたことが確認できました。
補足: 認証について
開発環境から Packer で Google Cloud サービスにアクセスするためには、User Application Default Credentials, JSONサービスアカウントキー、アクセストークンのいずれかが必要となります。また、実行するユーザやサービスアカウントに Compute インスタンス管理者 (v1)roles/compute.instanceAdmin.v1
とサービス アカウント ユーザーroles/iam.serviceAccountUser
のロールが必要です。
今回は Cloud Shell から オーナー権限を持つユーザで検証しており上記の考慮は不要でした。
詳細は以下をご参照ください。
おわりに
様々な制限によりコンテナを利用せずにVMインスタンス上にワークロードをデプロイするケースは多いかと思います。このような状況において Packer によるカスタムイメージの作成は、柔軟で効率の良いインフラ構築を実現できそうです。Google Cloud との親和性も高く、Compute Engine を利用する環境で迅速な開発サイクルの実現に寄与できるかと思います。