排定自訂 OS 映像檔建構作業

您可以使用 Cloud Scheduler 和 Cloud Build 自動建構觸發條件,排定週期性 Image Builder 執行作業。排定自訂 OS 映像檔建構作業後,Cloud Build 會自動使用作業系統修補程式和組織安全性政策更新基準映像檔,不需手動介入。

事前準備

  • 完成「準備環境」中的環境設定步驟。
  • 如果尚未設定驗證,請先完成設定。驗證程序會確認您的身分,以便授予 Cloud de Confiance by S3NS 服務和 API 的存取權。如要從本機開發環境執行程式碼或範例,您可以選取下列其中一個選項,向 Compute Engine 進行驗證:

    請根據您打算如何使用本頁範例,選取對應的分頁標籤:

    gcloud

    1. 安裝 Google Cloud CLI,然後 使用聯合身分登入 gcloud CLI。 登入後,執行下列指令來初始化 Google Cloud CLI:

      gcloud init
  • 設定預設區域和可用區。
  • Go

    如要在本機開發環境中使用本頁面的 Go 範例,請安裝並初始化 gcloud CLI,然後使用使用者憑證設定應用程式預設憑證。

    1. 安裝 Google Cloud CLI。

    2. 設定 gcloud CLI,使用您的聯合身分。

      詳情請參閱「 使用聯合身分登入 gcloud CLI」。

    3. 為使用者帳戶建立本機驗證憑證:

      gcloud auth application-default login

      如果傳回驗證錯誤,請確認您已 使用聯合身分登入 gcloud CLI。

    詳情請參閱「 為本機開發環境設定驗證機制」。

必要的角色

如要取得建立觸發條件及排定建構作業所需的權限,請要求管理員在專案中授予您下列 IAM 角色:

  • Cloud Build 編輯者 (roles/cloudbuild.builds.editor)
  • Cloud Scheduler 工作執行者 (roles/cloudscheduler.jobRunner) 或 Cloud Scheduler 管理員 (roles/cloudscheduler.admin)
  • 服務帳戶使用者 (roles/iam.serviceAccountUser)

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

建立排定的建構管道

您可以使用 gcloud CLI 或 Go 用戶端程式庫,設定週期性映像檔建構作業:

gcloud

如要使用 gcloud CLI 排定週期性 OS 映像檔建構作業,請完成下列步驟:

  1. 執行 gcloud builds triggers create manual 指令,建立指定目標 cloudbuild.yaml 檔案的手動 Cloud Build 自動建構觸發條件。由於 Cloud Scheduler 是手動叫用這項觸發條件,而非在存放區事件中叫用,因此請勿指定推送或提取要求篩選器:

    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. 如要建立 Cloud Scheduler 工作,根據所需的 Unix cron 排程 (例如使用 '0 6 * * 1' 在每週一上午 6:00) 向 Cloud Build API (projects.locations.triggers.run) 傳送 HTTP POST 要求,請執行 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 bucket,例如 gs://STAGING_BUCKET/imagebuilder.yaml。
  • 將專案的 Cloud Build 編輯者角色 (roles/cloudbuild.builds.editor) 和執行建構作業的服務帳戶的服務帳戶使用者角色 (roles/iam.serviceAccountUser),授予排程服務帳戶 cloud-build-trigger-scheduler@PROJECT_ID.s3ns.iam.gserviceaccount.com。詳情請參閱 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
}

後續步驟