カスタム OS イメージのビルドをスケジュールする

Cloud Scheduler と Cloud Build トリガーを使用して、Image Builder の定期的な実行をスケジュールできます。カスタム OS イメージのビルドをスケジュールすると、Cloud Build は、オペレーティング システムのパッチと組織のセキュリティ ポリシーを使用して、ベースライン イメージを自動的に更新します。手動での操作は必要ありません。

始める前に

  • 環境を準備するの手順に沿って環境を設定します。
  • まだ設定していない場合は、認証を設定します。認証では、 Google Cloud サービスと API にアクセスするための ID が確認されます。ローカル開発環境からコードまたはサンプルを実行するには、次のいずれかのオプションを選択して Compute Engine に対する認証を行います。

    このページのサンプルをどのように使うかに応じて、タブを選択してください。

    gcloud

    1. Google Cloud CLI をインストールします。 インストール後、次のコマンドを実行して Google Cloud CLI を初期化します。

      gcloud init

      外部 ID プロバイダ(IdP)を使用している場合は、まず連携 ID を使用して gcloud CLI にログインする必要があります。

  • デフォルトのリージョンとゾーンを設定します。
  • Go

    ローカル開発環境でこのページの Go サンプルを使用するには、gcloud CLI をインストールして初期化し、ユーザー認証情報を使用してアプリケーションのデフォルト認証情報を設定します。

    1. Google Cloud CLI をインストールします。

    2. 外部 ID プロバイダ(IdP)を使用している場合は、まず フェデレーション ID を使用して gcloud CLI にログインする必要があります。

    3. ローカルシェルを使用している場合は、ユーザー アカウントのローカル認証情報を作成します。

      gcloud auth application-default login

      Cloud Shell を使用している場合は、この操作を行う必要はありません。

      認証エラーが返され、外部 ID プロバイダ(IdP)を使用している場合は、 フェデレーション ID を使用して gcloud CLI にログインしていることを確認します。

    詳細については、 ローカル開発環境の認証を設定するをご覧ください。

必要なロール

トリガーの作成とビルドのスケジュール設定に必要な権限を取得するには、プロジェクトに対する次の IAM ロールを付与するよう管理者に依頼してください。

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

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

スケジュール設定されたビルド パイプラインを作成する

定期的なイメージビルドは、gcloud CLI または Go クライアント ライブラリを使用して構成できます。

gcloud

gcloud CLI を使用して OS イメージの定期的なビルドをスケジュールするには、次の操作を行います。

  1. gcloud builds triggers create manual コマンドを実行して、ターゲットの cloudbuild.yaml ファイルを指定する手動の Cloud Build ビルドトリガーを作成します。Cloud Scheduler は、リポジトリ イベントではなく手動でこのトリガーを呼び出すため、push リクエスト フィルタまたは pull リクエスト フィルタを指定しないでください。

    gcloud builds triggers create manual \
        --name="TRIGGER_NAME" \
        --region=REGION \
        --build-config=CLOUDBUILD_YAML_PATH \
        --project=PROJECT_ID
    
  2. gcloud builds triggers describe コマンドを実行して、新しいトリガー用に生成された一意の ID(trigger_id)を取得します。

    gcloud builds triggers describe TRIGGER_NAME \
        --region=REGION \
        --format="value(id)" \
        --project=PROJECT_ID
    
  3. 必要な Unix cron スケジュール('0 6 * * 1' を使用して毎週月曜日の午前 6 時など)に従って HTTP POST リクエストを Cloud Build API(projects.locations.triggers.run)に送信する Cloud Scheduler ジョブを作成するには、gcloud scheduler jobs create http コマンドを実行します。

    gcloud scheduler jobs create http JOB_NAME \
        --schedule="CRON_SCHEDULE" \
        --uri="https://cloudbuild.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/triggers/TRIGGER_ID:run" \
        --message-body="{}" \
        --oauth-service-account-email="SERVICE_ACCOUNT_EMAIL" \
        --location=REGION \
        --project=PROJECT_ID
    

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

  • TRIGGER_NAME: 手動の Cloud Build ビルドトリガーの名前(例: weekly-ubuntu-builder)。
  • REGION: トリガーを作成するリージョン(例: us-central1)。
  • CLOUDBUILD_YAML_PATH: ローカル ディレクトリ内の cloudbuild.yaml ファイルのパス(例: cloudbuild.yaml)。
  • PROJECT_ID: プロジェクト ID。
  • JOB_NAME: Cloud Scheduler cron ジョブの名前(例: weekly-custom-os-patching)。
  • CRON_SCHEDULE: Unix cron スケジュール式(例: '0 6 * * 1')。
  • TRIGGER_ID: トリガー用に生成された一意の ID(trigger_id)。
  • SERVICE_ACCOUNT_EMAIL: Cloud Scheduler がトリガーを呼び出すことを認可するサービス アカウントのメールアドレス。このサービス アカウントには roles/cloudbuild.builds.editor 権限が必要です。

Go

Cloud Build と Cloud Scheduler の Go クライアント ライブラリを使用してパイプラインをプログラムでスケジュールするには、scheduleBuild 関数を使用します。

このサンプルを実行する前に、次の前提条件を満たす必要があります。

  • imagebuilder.yaml 構成ファイルを Cloud Storage バケット(gs://STAGING_BUCKET/imagebuilder.yaml など)にアップロードします。
  • スケジューリング サービス アカウント cloud-build-trigger-scheduler@PROJECT_ID.iam.gserviceaccount.com に、プロジェクトの Cloud Build 編集者ロール(roles/cloudbuild.builds.editor)と、ビルドを実行するサービス アカウントのサービス アカウント ユーザーロール(roles/iam.serviceAccountUser)を付与します。詳細については、Cloud Build ドキュメントのビルドのスケジュール設定をご覧ください。
import (
	"context"
	"fmt"
	"io"

	scheduler "cloud.google.com/go/scheduler/apiv1"
	"cloud.google.com/go/scheduler/apiv1/schedulerpb"
	"google.golang.org/api/cloudbuild/v1"
)

// scheduleBuild creates a manual Cloud Build trigger that runs an Image
// Builder pipeline, and a Cloud Scheduler job that runs the trigger every day.
//
// 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.
//
// The job runs the trigger as the default scheduling service account,
// cloud-build-trigger-scheduler@PROJECT_ID.iam.gserviceaccount.com. Grant that
// service account the Cloud Build Editor role, and the Service Account User
// role on the service account that runs the build. For details, see
// https://cloud.google.com/build/docs/schedule-builds.
func scheduleBuild(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"
	triggerName := "scheduled-image-builder"
	jobName := "scheduled-image-builder-job"
	schedule := "0 9 * * *"

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

	parent := fmt.Sprintf("projects/%s/locations/%s", projectID, region)
	sa := fmt.Sprintf("projects/%s/serviceAccounts/%s", projectID, serviceAccount)

	trigger := &cloudbuild.BuildTrigger{
		Name:        triggerName,
		Description: "Manual trigger for scheduled VM image builds",
		EventType:   "MANUAL",
		Build: &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",
		},
	}
	createdTrigger, err := cbService.Projects.Locations.Triggers.Create(parent, trigger).
		Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("Triggers.Create: %w", err)
	}
	fmt.Fprintf(w, "Created trigger %s (ID: %s)\n", createdTrigger.Name, createdTrigger.Id)

	schedulerSA := fmt.Sprintf(
		"cloud-build-trigger-scheduler@%s.iam.gserviceaccount.com", projectID,
	)
	job := &schedulerpb.Job{
		Name:        fmt.Sprintf("%s/jobs/%s", parent, jobName),
		Description: "Runs the Image Builder trigger on a schedule",
		Schedule:    schedule,
		Target: &schedulerpb.Job_HttpTarget{
			HttpTarget: &schedulerpb.HttpTarget{
				Uri: fmt.Sprintf(
					"https://cloudbuild.googleapis.com/v1/projects/%s/locations/%s/triggers/%s:run",
					projectID, region, createdTrigger.Id,
				),
				HttpMethod: schedulerpb.HttpMethod_POST,
				AuthorizationHeader: &schedulerpb.HttpTarget_OauthToken{
					OauthToken: &schedulerpb.OAuthToken{
						ServiceAccountEmail: schedulerSA,
					},
				},
			},
		},
	}
	req := &schedulerpb.CreateJobRequest{Parent: parent, Job: job}
	createdJob, err := schedulerClient.CreateJob(ctx, req)
	if err != nil {
		return fmt.Errorf("CreateJob: %w", err)
	}
	fmt.Fprintf(w, "Created job %s with schedule %q\n", createdJob.Name, createdJob.Schedule)
	return nil
}

次のステップ