コンテンツにスキップ

packer を使って VM イメージを作成する

packer とは

Packer は、マシン・イメージの自動生成や管理をするコマンドライン・ツール。 Packer はテンプレートに基づいて仮想マシンイメージを作成し、そのイメージをプロビジョニングし、最終的にはイメージをクラウドサービスにデプロイする。 このプロセスは、手動で行うよりも効率的で、エラーを減らすことができる。Packer は、AWS、Google Cloud、Azure などの主要なクラウドプロバイダーと互換性がある。

Packer の主な機能は次のとおり:

  • テンプレートを使用して仮想マシンイメージを作成
  • プロビジョニングスクリプトを実行して、イメージをカスタマイズ
  • イメージをクラウドサービスにデプロイ

Packer は、AWS、Google Cloud、Azure、DigitalOcean などの主要なクラウドプロバイダーと互換性がある。これにより、異なるクラウドプロバイダー間で同じイメージを簡単に作成し、デプロイすることが可能。

Packer のインストール

Packer は、公式サイトからダウンロードできる。また、Homebrew などのパッケージマネージャーを使用してインストールできる。

brew install packer

Packer の基本的な使い方

Packer の使用方法は次のとおり:

  1. まず、Packer テンプレートを作成する。このテンプレートは、仮想マシンイメージを作成するための指示を含む JSON もしくは HCL で記述する。
  2. 次に Packer を実行してテンプレートに基づいてイメージを作成する。これは、packer build template.json のようなコマンドを使用して行う。
  3. 最後に、作成されたイメージをクラウドサービスにデプロイする。これは、テンプレート内のデプロイ設定によって異なる

Packer は、開発環境の設定やテスト環境の準備など、繰り返し行う必要がある作業を自動化するのに特に役立つ。また、一貫性のある環境を保つことで、開発者間の差異を減らし、バグの発見を簡単にできる。

GCE の VM イメージを作成する

Deep Learning VM イメージを作成する例が次になる。この例では、VM イメージの作成に必要な設定を記述したテンプレートファイルと、VM イメージの作成に必要なスクリプトを記述したシェルスクリプトを使用している。 シェルスクリプトで GPU driver のインストールやコンテナイメージの pull などを行っている。 GPU driver をインストールする際は作業する(ssh 先)の VM に GPU が搭載されている必要がある。

example.pkr.hcl
packer {
  required_plugins {
    googlecompute = {
      source  = "github.com/hashicorp/googlecompute"
      version = "~> 1"
    }
  }
}

source "googlecompute" "basic-example" {
  project_id = "working-project-id"
  image_project_id = "destination-project-id"
  source_image = "c0-deeplearning-common-cu121-v20231105-debian-11"
  ssh_username = "packer"
  zone         = "asia-northeast1-a"
  network      = "foo"
  subnetwork   = "bar"
  image_name   = "deep-learning-example-{{timestamp}}"
  image_family = "deep-learning-example"
  disk_size    = 50
  preemptible  = true
  image_description = "deep learning image"
  accelerator_type = "projects/working-project/zones/asia-northeast1-a/acceleratorTypes/nvidia-tesla-t4"
  accelerator_count = 1
}

build {
  sources = ["sources.googlecompute.basic-example"]
  provisioner "shell" {
    scripts = ["./foo.sh"]
    execute_command = "echo 'packer' | sudo -S env {{ .Vars }} {{ .Path }}"
  }
}

sudo 権限がないと編集できないファイルを編集するのであればexecute_commandの設定で sudo を使う。 ref

foo.sh
#!/bin/bash
sudo /opt/deeplearning/install-driver.sh
sudo cat << 'EOF' > /etc/docker/daemon.json
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "args": [],
            "path": "nvidia-container-runtime"
        }
    },
    "exec-opts": ["native.cgroupdriver=cgroupfs"],
    "log-driver": "gcplogs"
}
EOF

gcloud auth configure-docker asia-northeast1-docker.pkg.dev --quiet
docker pull \
    asia-northeast1-docker.pkg.dev/working-project-id/deep-learning-example/hello-world-gpu:latest

Terraform plugin の Packer を使う

terraform-provider-packerを使うと、Terraform から Packer を実行できる。

公式の例を参考に VM イメージ内のファイルに変数の値を書き込む例。

main.tf
terraform {
  required_providers {
    packer = {
      source = "toowoxx/packer"
      version = "0.1.0"
    }
  }
}

provider "packer" {}

data "packer_version" "version" {}

data "packer_files" "files1" {
  file = "demo.pkr.hcl"
}

resource "packer_image" "image1" {
  file = data.packer_files.files1.file
  variables = {
    test_var1 = "test 1"
  }

  triggers = {
    packer_version = data.packer_version.version.version
    files_hash     = data.packer_files.files1.files_hash
  }
}

output "packer_version" {
  value = data.packer_version.version.version
}

output "build_uuid_1" {
  value = resource.packer_image.image1.build_uuid
}

output "file_hash_1" {
  value = data.packer_files.files1.files_hash
}
demo.pkr.hcl
variable "test_var1" {
  type = string
}

packer {
  required_plugins {
    googlecompute = {
      source  = "github.com/hashicorp/googlecompute"
      version = "~> 1"
    }
  }
}

source "googlecompute" "basic-example" {
  project_id = "myproject-123456"
  source_image = "ubuntu-2004-focal-v20231130"
  ssh_username = "packer"
  zone         = "asia-northeast1-a"
  network      = "mynetwork"
  subnetwork   = "mysubnet"
  image_name   = "demo-1"
  image_family = "demo"
  disk_size    = 15
  preemptible  = true
}

build {
  sources = ["sources.googlecompute.basic-example"]
  provisioner "shell" {
    env = {
      "test_var1" = var.test_var1
    }
    scripts = ["./demo.sh"]
    execute_command = "echo 'packer' | sudo -S env {{ .Vars }} {{ .Path }}"
  }
}
demo.sh
1
2
3
cat <<EOF > hello.txt
$test_var1
EOF

hcl ファイルに差分があると apply をできるが、依存する shell script の変更は検知できないので差分を検知するには工夫がいるかもしれない。 state の管理では、すでにある VM イメージを消さずに新しいものを作るという挙動になる。 file hashを使うと依存するファイルの変更を検知できるかもしれない。

GPU driver とコンテナを含んだ VM イメージの setup 時間比較

VM 起動時に gpu driver と container pull を行う

  • gpu driver setup 3.5min
  • container image (展開後 10GB)の pull 3.5min

依存を全て含んだ VM 起動

GPU driver とコンテナイメージを含んだ VM イメージを作成し、その VM イメージから VM を起動すると、VM 起動時に GPU driver のインストールやコンテナイメージの pull が必要ないため、起動時間が短縮できる。 VM 作成からコンテナ実行までが 1~2min になる。