カスタム OS イメージ パイプラインをプログラムで作成する

gcloud CLI、Terraform、または Go などのクライアント ライブラリを使用して、Image Builder パイプラインをプログラムで構成して送信できます。パイプラインをプログラムで構成すると、インフラストラクチャ設定、ベース OS イメージ、カスタマイズ アクション、検証テストを宣言型構成ファイルで定義できます。

始める前に

必要なロール

画像カスタマイズ パイプラインをプログラムで作成して送信するために必要な権限を取得するには、プロジェクトに対する次の IAM ロールを付与するよう管理者に依頼してください。

ロールの付与については、プロジェクト、フォルダ、組織へのアクセス権の管理をご覧ください。

必要な権限は、カスタムロールや他の事前定義ロールから取得することもできます。

構成ファイルを作成する

gcloud CLI または Terraform を使用してパイプラインを構成するには、次の 2 つの構成ファイルを作成します。

  • imagebuilder.yaml: イメージのカスタマイズ レシピを定義します。これには、ベース OS イメージ、ワーカー VM インフラストラクチャの設定、ターゲット イメージの出力の詳細、シェル スクリプトの実行、ファイルの転送、再起動などの順次カスタマイズ ステップが含まれます。
  • cloudbuild.yaml: Cloud Build でのビルドプロセスのステップ(カスタム OS イメージの解析、検証、ビルド、テスト、公開など)をオーケストレートします。

Go を使用してビルドを送信する場合は、imagebuilder.yaml ファイルを作成して Cloud Storage バケットにアップロードするだけで済みます。これは、Go サンプルがコードで Cloud Build オーケストレーション ステップを定義しているためです。

イメージ構成ファイルを作成する

イメージ構成を指定するには、ローカル ディレクトリに imagebuilder.yaml という名前のファイルを作成します。サポートされているすべてのスキーマ フィールドとカスタマイズ アクションの完全なリストについては、カスタマイズ レシピのスキーマとサポートされているカスタマイズ アクションをご覧ください。

次の imagebuilder.yaml ファイルのサンプルでは、指定したリージョンとゾーンで e2-standard-4 ワーカー VM を使用してカスタマイズされた Ubuntu 22.04 LTS イメージをビルドし、システム パッケージの更新を実行するパイプラインを構成します。

apiVersion: imagebuilder.gcp.com/v1
kind: OSImageCustomization
metadata:
  name: customized-ubuntu-baseline
  description: "Ubuntu 22.04 LTS custom OS baseline image"
infrastructureConfig:
  machineType: e2-standard-4
  zone: ZONE
  debug: false
source:
  imageFamily: projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts
destinations:
  - diskImage:
      name: custom-ubuntu-v1
      family: custom-ubuntu-family
      project: PROJECT_ID
      storageLocations:
        - REGION
spec:
  config:
    skipSystemTests: false
  steps:
    -   name: "System Package Update"
      action: Shell
      inputs:
        command: "apt-get update -y && apt-get upgrade -y"

次のプレースホルダの値を置き換えます。

  • PROJECT_ID: プロジェクト ID。
  • REGION: ターゲット イメージのストレージ ロケーション(us-east1、europe-west1 など)。次のリージョンとゾーンの要件を満たしていることを確認してください。
    • Image Builder は、Cloud Build が利用可能なリージョンでのみサポートされています。
    • ワーカー VM ZONE は、指定した REGION 内に配置する必要があります。
    • ネットワーク レイテンシを最小限に抑え、クロスリージョンの下り(外向き)料金を回避するには、ワーカー VM ゾーン、Cloud Storage ステージング バケット、Artifact Registry リポジトリ、ターゲット イメージのストレージ ロケーションが同じリージョンに配置されていることを確認します。
  • ZONE: 指定した REGION 内のゾーン(例: us-east1-b、europe-west1-b)。

オーケストレーター ビルドファイルを作成する

同じディレクトリに cloudbuild.yaml という名前のファイルを作成します。このファイルは、Image Builder コンテナのステップを呼び出して、カスタム OS イメージをビルド、検証、公開します。

substitutions:
  _GCS_WORKDIR: 'gs://STAGING_BUCKET_NAME/workdir/'
  _IMAGE_BUILDER_CONFIG_PATH: 'imagebuilder.yaml'
  _SERVICE_ACCOUNT: 'projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL'
  _IMAGE_OUTPUT_PATH: 'image-builder/binaryOut'
  _ARTIFACT_REGISTRY_RESOURCE_URI: 'projects/PROJECT_ID/locations/REGION/repositories/REPOSITORY_NAME/packages/PACKAGE_NAME/versions/v${BUILD_ID}'

steps:
  #  Step 1: Parse configs and run OS customization on worker VM
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/builder:stable'
    script: |
      #!/usr/bin/env bash
      /build
    id: 'imagebuilder-customize'
    results:
      - name: image_builder_telemetry_metrics
      - name: base_image
        attestationType: "https://cloudbuild.googleapis.com/attestations/build_content_restrictions"
        attestationContent: base_image

  # Step 2: Validate by running system boot checks on a test VM
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/validator:stable'
    script: |
      #!/usr/bin/env bash
      /validate
    id: 'imagebuilder-validate'
    results:
      - name: image_builder_telemetry_metrics

  # Step 3: Register image in Compute Engine and upload tar files to Artifact Registry
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/builder:stable'
    script: |
      #!/usr/bin/env bash
      /publish
    id: 'imagebuilder-publish'
    results:
      - name: image_builder_telemetry_metrics

options:
  automapSubstitutions: true
  requestedVerifyOption: VERIFIED
  substitutionOption: ALLOW_LOOSE
  dynamicSubstitutions: true
  logging: CLOUD_LOGGING_ONLY

artifacts:
  generic_artifacts:
    - folder: '${_IMAGE_OUTPUT_PATH}'
      registry_path: '${_ARTIFACT_REGISTRY_RESOURCE_URI}'

timeout: '3600s'

次のプレースホルダの値を置き換えます。

  • STAGING_BUCKET_NAME: 一時的なステージング ワークスペースとして使用する、プロジェクト内の既存の Cloud Storage バケット。バケットがない場合は、gcloud storage buckets create gs://STAGING_BUCKET_NAME を実行して作成できます。Terraform を使用してパイプラインをデプロイすると、Terraform によってこのバケットが自動的に作成されます。
  • PROJECT_ID: 実際の Google Cloud プロジェクト ID。
  • REGION: Artifact Registry リポジトリの Google Cloud リージョン(例: us-east1、europe-west1)。
  • SERVICE_ACCOUNT_EMAIL: 必要な IAM 権限で構成したサービス アカウントのメールアドレス。
  • REPOSITORY と PACKAGE: Artifact Registry で作成されたターゲット リポジトリとパッケージ名。Artifact Registry レジストリを設定するには、Artifact Registry を構成するをご覧ください。

ビルド パイプラインを作成して送信する

イメージ カスタマイズ パイプラインを実行するには、gcloud CLI を使用してビルドを送信するか、Terraform を使用してパイプラインをデプロイするか、Go クライアント ライブラリを使用してビルドを送信します。次のいずれかのタブを選択します。

gcloud

イメージ カスタマイズ パイプラインをデプロイして実行するには、両方の構成ファイルを含むローカル ターミナル ディレクトリから gcloud builds submit コマンドを実行します。

gcloud builds submit . \
    --config=cloudbuild.yaml \
    --project=PROJECT_ID \
    --service-account="projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL" \
    --region=REGION

次のように置き換えます。

  • PROJECT_ID: 実際の Google Cloud プロジェクト ID。
  • REGION: 画像カスタマイズ パイプライン ジョブを実行する Google Cloud リージョン。
  • SERVICE_ACCOUNT_EMAIL: 必要な IAM 権限で構成されたサービス アカウントのメールアドレス。

このコマンドは、カスタマイズ ワークスペースをアップロードし、Cloud Build の実行を登録して、オーケストレーション コンテナを起動します。

Terraform

カスタマイズされた OS イメージを自動的にビルドして検証するために必要なインフラストラクチャをプロビジョニングするには、Terraform を使用します。この Terraform 構成は、次のタスクを完了します。

  • 必要な Google Cloud API を有効にします。
  • 一時ログとビルド アーティファクトを保存するための専用の Cloud Storage バケット(workdir_bucket)を作成します。
  • Developer Connect GitHub リポジトリ接続にリンクされた Cloud Build トリガー(image_builder_trigger)を構成します。

Terraform 構成ファイルを作成する

Terraform を使用してパイプライン インフラストラクチャを整理してデプロイするには、次の手順を完了します。

  1. ローカル ワークステーションまたは CI/CD 環境に、アプリケーション リポジトリとは別に専用のディレクトリを作成し、そのディレクトリに移動します。

    mkdir terraform-image-builder && cd terraform-image-builder
    
  2. このディレクトリに、次の 5 つの Terraform 構成ファイルを作成します。

    • terraform.tfvars: プロジェクト固有の変数の値を設定します。
    • main.tf: 必要な API の有効化、Cloud Storage ステージング バケット(workdir_bucket)の作成、Cloud Build トリガー(image_builder_trigger)のデプロイなど、 Google Cloud プロジェクトにリソースをプロビジョニングします。
    • outputs.tf: デプロイ後にターミナルに表示される出力値(トリガー ID、ステージング バケット名など)を定義します。
    • providers.tf: 必要な Terraform バージョン(>= 1.3)を指定し、 Google Cloud プロバイダ(hashicorp/google)を構成します。
    • variables.tf: デプロイの入力変数、デフォルト値、検証ルールを定義します。

    次のいずれかのタブを選択して、各ファイルの構成を表示し、ローカル ディレクトリにコピーします。

    terraform.tfvars

    このファイルでは、宣言された変数の環境のパラメータ値を指定します。

    project_id                = "PROJECT_ID"
    builder_service_account   = "SERVICE_ACCOUNT_EMAIL"
    github_repo_name          = "projects/PROJECT_ID/locations/LOCATION/connections/CONNECTION/repositories/REPO_NAME"
    region                    = "REGION"
    trigger_name              = "TRIGGER_NAME"
    cloudbuild_yaml_path      = "CLOUDBUILD_YAML_PATH"
    image_builder_config_path = "RECIPE_PATH"
    gcs_lifecycle_age_days    = LIFECYCLE_DAYS
    ar_repository_id          = "REPOSITORY_NAME"
    ar_package_name           = "PACKAGE_NAME"
    

    既存のリソースの次のプレースホルダを置き換えます。

    • PROJECT_ID: 既存のGoogle Cloud プロジェクト ID。
    • SERVICE_ACCOUNT_EMAIL: Image Builder サービス アカウントを構成するで構成したビルド サービス アカウントのメールアドレス。
    • LOCATION、CONNECTION、REPO_NAME: リポジトリを接続するで構成された Developer Connect ホスト リージョン、接続名、リポジトリ リンク。
    • CLOUDBUILD_YAML_PATH: ローカル ディレクトリ内の cloudbuild.yaml ファイルの相対パス。cloudbuild.yaml を Git リポジトリにチェックインする必要はありません。
    • RECIPE_PATH: Git リポジトリにチェックインされた imagebuilder.yaml カスタマイズ レシピ ファイルの相対パス。
    • REPOSITORY_NAME: Artifact Registry を構成するで作成した既存の汎用 Artifact Registry リポジトリ。

    Terraform が作成するリソースの次のプレースホルダを置き換えます。

    • REGION: Terraform が Cloud Storage ステージング バケットと Cloud Build ビルドトリガーをプロビジョニングするターゲット Google Cloudリージョン(例: us-central1)。
    • TRIGGER_NAME: Terraform によって作成された新しい Cloud Build リポジトリ トリガーの名前(例: git-push-os-builder)。
    • LIFECYCLE_DAYS: Terraform によって作成された Cloud Storage ステージング バケット内の中間アーティファクトが自動的に削除されるまでの保持期間(日数)。例: 30。
    • PACKAGE_NAME: Terraform が Artifact Registry リポジトリ内に作成されたパッケージに使用する名前。このパッケージには、公開された OS イメージ バージョン(ubuntu-custom など)が保存されます。

    main.tf

    このファイルでは、デプロイのコア インフラストラクチャ リソースとデータソースを宣言します。

    # Main resource configurations for Image Builder.
    # 1. Enable Required APIs
    resource "google_project_service" "apis" {
      for_each = toset([
        "compute.googleapis.com",
        "cloudbuild.googleapis.com",
        "artifactregistry.googleapis.com",
        "serviceusage.googleapis.com",
        "cloudresourcemanager.googleapis.com",
        "iam.googleapis.com",
        "storage.googleapis.com"
      ])
      project = var.project_id
      service = each.key
      disable_on_destroy = false
    }
    
    # 2. Project data source to retrieve Project Number
    data "google_project" "project" {
      project_id = var.project_id
      depends_on = [google_project_service.apis]
    }
    
    locals {
      builder_sa = var.builder_service_account
    }
    
    # 3. Storage Bucket for Image Builder Workdir
    resource "google_storage_bucket" "workdir_bucket" {
      name                        = var.gcs_bucket_name != "" ? var.gcs_bucket_name : "${var.project_id}-vm-builder-workdir"
      project                     = var.project_id
      location                    = var.region
      force_destroy               = true
      uniform_bucket_level_access = true
      lifecycle_rule {
        action {
          type = "Delete"
        }
        condition {
          age = var.gcs_lifecycle_age_days
        }
      }
      depends_on = [google_project_service.apis]
    }
    
    # 4. Cloud Build Trigger
    resource "google_cloudbuild_trigger" "image_builder_trigger" {
      name        = var.trigger_name
      location    = var.region
      project     = var.project_id
      description = "Trigger that runs Image Builder customization"
    
      service_account = var.builder_service_account != "" ? "projects/${var.project_id}/serviceAccounts/${var.builder_service_account}" : null
    
      repository_event_config {
        repository = replace(var.github_repo_name, "gitRepositoryLinks", "repositories")
        push {
          branch = "^main$"
        }
      }
      filename = var.cloudbuild_yaml_path
    
      substitutions = {
        _GCS_WORKDIR                    = "gs://${google_storage_bucket.workdir_bucket.name}/workdir/"
        _SERVICE_ACCOUNT                = "projects/${var.project_id}/serviceAccounts/${local.builder_sa}"
        _IMAGE_OUTPUT_PATH              = "image-builder/binaryOut"
        _PROJECT_ID                     = var.project_id
        _LOCATION                       = var.region
        _REPOSITORY_NAME                = var.ar_repository_id
        _PACKAGE_NAME                   = var.ar_package_name
        _IMAGE_BUILDER_CONFIG_PATH      = var.image_builder_config_path
        _ARTIFACT_REGISTRY_RESOURCE_URI = "projects/${var.project_id}/locations/${var.region}/repositories/${var.ar_repository_id}/packages/${var.ar_package_name}/versions/v$${BUILD_ID}"
      }
      depends_on = [
        google_project_service.apis
      ]
    }
    

    outputs.tf

    このファイルは、デプロイ後にターミナルに返される出力属性を定義します。

    output "builder_service_account" {
      value       = local.builder_sa
      description = "The email representation of the resolved Image Builder service account."
    }
    
    output "workdir_bucket" {
      value       = google_storage_bucket.workdir_bucket.name
      description = "The name of the storage workdir bucket."
    }
    
    output "artifact_registry_repository" {
      value       = "projects/${var.project_id}/locations/${var.region}/repositories/${var.ar_repository_id}"
      description = "The fully qualified resource path of the Artifact Registry repository."
    }
    
    output "cloud_build_trigger_id" {
      value       = google_cloudbuild_trigger.image_builder_trigger.trigger_id
      description = "The unique ID for the created Cloud Build Trigger."
    }
    

    providers.tf

    このファイルでは、必要な Terraform のバージョンとリージョン設定を構成します。

    terraform {
      required_version = ">= 1.3"
      required_providers {
        google = {
          source  = "hashicorp/google"
          version = ">= 5.0, < 7.0"
        }
      }
    }
    
    provider "google" {
      project = var.project_id
      region  = var.region
    }
    

    variables.tf

    このファイルでは、必須およびオプションの入力変数と検証ルールをすべて宣言します。

    variable "project_id" {
      type        = string
      description = "The target Project ID where resources will be created."
      validation {
        condition     = can(regex("^[a-z0-9-]{6,30}$", var.project_id))
        error_message = "The project_id must consist of lowercase letters, numbers, and hyphens, and be between 6 and 30 characters."
      }
    }
    
    variable "region" {
      type        = string
      default     = "us-central1"
      description = "Location used for cloud build triggers, storage buckets, and artifact registry."
    }
    
    variable "github_repo_name" {
      type        = string
      default     = ""
      description = "Developer Connect github repository details, format: projects/PROJECT_ID/locations/LOCATION/connections/CONNECTION/repositories/REPO_LINK"
      validation {
        condition     = can(regex("^projects/[^/]+/locations/[^/]+/connections/[^/]+/(gitRepositoryLinks|repositories)/[^/]+$", var.github_repo_name))
        error_message = "The github_repo_name must follow either the Cloud Build v2 repository link format (using '/repositories/') or the Developer Connect resource format (using '/gitRepositoryLinks/')."
      }
    }
    
    variable "builder_service_account" {
      type        = string
      default     = ""
      description = "The email representation of the pre-existing Image Builder service account. If omitted, the default Cloud Build service account will be used."
    }
    
    variable "gcs_bucket_name" {
      type        = string
      default     = ""
      description = "Custom name for the workdir storage bucket. If left empty, a default name using the project ID will be constructed."
    }
    
    variable "gcs_lifecycle_age_days" {
      type        = number
      default     = 30
      description = "The number of days after which temporary logs and artifacts in the storage workdir bucket are deleted."
    }
    
    variable "ar_repository_id" {
      type        = string
      default     = "vm-images"
      description = "The repository ID for the generic Artifact Registry hosting the final OS image tarballs."
    }
    
    variable "ar_package_name" {
      type        = string
      default     = "image-builder"
      description = "The package name under which the generic OS image artifact will be registered in Artifact Registry."
    }
    
    variable "trigger_name" {
      type        = string
      default     = "custom-os-image-builder"
      description = "The name of the Cloud Build trigger."
    }
    
    variable "cloudbuild_yaml_path" {
      type        = string
      default     = "cloudbuild.yaml"
      description = "The path to the cloudbuild.yaml configuration file relative to the repository root."
    }
    
    variable "image_builder_config_path" {
      type        = string
      default     = "imagebuilder.yaml"
      description = "The path to the imagebuilder.yaml configuration file relative to the repository root."
    }
    
  3. 構成をデプロイするには、Terraform ファイルを含むディレクトリで次のコマンドを実行します。

    1. ディレクトリを初期化します。
      terraform init
    2. 構文を検証します。
      terraform validate
    3. デプロイをプレビューします。
      terraform plan
    4. 構成を適用します。
      terraform apply

Go

このサンプルを実行する前に、imagebuilder.yaml 構成ファイルを Cloud Storage ステージング バケット(gs://STAGING_BUCKET_NAME/imagebuilder.yaml など)にアップロードします。

Cloud Build Go クライアント ライブラリを使用して、Image Builder パイプラインを実行する Cloud Build ビルドを送信するには、次のサンプルを使用します。

import (
	"context"
	"encoding/json"
	"fmt"
	"io"

	"google.golang.org/api/cloudbuild/v1"
)

// submitBuild submits a Cloud Build build that uses Image Builder to
// customize, validate, and publish a VM image to Compute Engine and Artifact
// Registry.
//
// Before you run this sample, upload your imagebuilder.yaml configuration file
// to Cloud Storage, create a generic Artifact Registry repository, and create a
// service account to run the build.
func submitBuild(w io.Writer, projectID, gcsWorkdir, configPath, serviceAccount string) error {
	// projectID := "my-project-id"
	region := "us-central1"
	// gcsWorkdir := "gs://my-bucket/workdir/"
	// configPath := "gs://my-bucket/imagebuilder.yaml"
	repository := "vm-images"
	packageName := "custom-os-images"
	// serviceAccount := "image-builder@my-project-id.iam.gserviceaccount.com"

	ctx := context.Background()
	cbService, err := cloudbuild.NewService(ctx)
	if err != nil {
		return fmt.Errorf("cloudbuild.NewService: %w", err)
	}

	sa := fmt.Sprintf("projects/%s/serviceAccounts/%s", projectID, serviceAccount)
	req := &cloudbuild.Build{
		ServiceAccount: sa,
		// AutomapSubstitutions exposes these substitutions to every step as
		// environment variables. See
		// https://cloud.google.com/build/docs/configuring-builds/substitute-variable-values#mapping_substitutions_to_environment_variables
		Substitutions: map[string]string{
			"_GCS_WORKDIR":               gcsWorkdir,
			"_SERVICE_ACCOUNT":           sa,
			"_IMAGE_OUTPUT_PATH":         "image-builder/binaryOut",
			"_IMAGE_BUILDER_CONFIG_PATH": configPath,
			"_ARTIFACT_REGISTRY_RESOURCE_URI": fmt.Sprintf(
				"projects/%s/locations/%s/repositories/%s/packages/%s/versions/v${BUILD_ID}",
				projectID, region, repository, packageName,
			),
		},
		Steps: []*cloudbuild.BuildStep{
			{
				Id:     "imagebuilder-customize",
				Name:   "us-docker.pkg.dev/image-builder-official/release/builder:stable",
				Script: "#!/usr/bin/env bash\n/build",
				Results: []*cloudbuild.StepResult{
					{
						Name:            "base_image",
						AttestationType: "https://cloudbuild.googleapis.com/attestations/build_content_restrictions",
					},
					{Name: "image_builder_telemetry_metrics"},
				},
			},
			{
				Id:      "imagebuilder-validate",
				Name:    "us-docker.pkg.dev/image-builder-official/release/validator:stable",
				Script:  "#!/usr/bin/env bash\n/validate",
				Results: []*cloudbuild.StepResult{{Name: "image_builder_telemetry_metrics"}},
			},
			{
				Id:      "imagebuilder-publish",
				Name:    "us-docker.pkg.dev/image-builder-official/release/builder:stable",
				Script:  "#!/usr/bin/env bash\n/publish",
				Results: []*cloudbuild.StepResult{{Name: "image_builder_telemetry_metrics"}},
			},
		},
		Options: &cloudbuild.BuildOptions{
			AutomapSubstitutions:  true,
			DynamicSubstitutions:  true,
			RequestedVerifyOption: "VERIFIED",
			SubstitutionOption:    "ALLOW_LOOSE",
			// Builds that run as a user-specified service account must set a
			// logging option.
			Logging: "CLOUD_LOGGING_ONLY",
		},
		Artifacts: &cloudbuild.Artifacts{
			GenericArtifacts: []*cloudbuild.GenericArtifact{
				{
					Folder:       "${_IMAGE_OUTPUT_PATH}",
					RegistryPath: "${_ARTIFACT_REGISTRY_RESOURCE_URI}",
				},
			},
		},
		Timeout: "3600s",
	}

	parent := fmt.Sprintf("projects/%s/locations/%s", projectID, region)
	op, err := cbService.Projects.Locations.Builds.Create(parent, req).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("Builds.Create: %w", err)
	}

	// The operation metadata contains the build that was created.
	var metadata cloudbuild.BuildOperationMetadata
	if err := json.Unmarshal(op.Metadata, &metadata); err != nil {
		return fmt.Errorf("json.Unmarshal: %w", err)
	}
	fmt.Fprintf(w, "Build submitted. ID: %s\n", metadata.Build.Id)
	fmt.Fprintf(w, "Logs: %s\n", metadata.Build.LogUrl)
	return nil
}

ビルドを検証してモニタリングする

ビルド パイプラインの進行状況を追跡するには、次の手順を行います。

  1. Google Cloud コンソールで、[Cloud Build] ページに移動します。

    Cloud Build に移動

  2. ナビゲーション メニューで [履歴] をクリックして、アクティブなジョブまたは完了したジョブを表示します。

  3. [ビルド] リストで、ビルドのビルド ID をクリックして、コンテナ実行ログを検査します。ログには、システム パッケージの更新やカスタムシェル コマンドなど、ワーカー VM 内で実行されているステップが表示されます。その後に、テスト VM からの検証テストの結果と最終出力の登録が表示されます。

次のステップ