安排自定义操作系统映像构建

您可以使用 Cloud Scheduler 和 Cloud Build 触发器安排定期运行 Image Builder。当您安排自定义操作系统映像构建时,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 Editor (roles/cloudbuild.builds.editor)
  • Cloud Scheduler Job Runner (roles/cloudscheduler.jobRunner) 或 Cloud Scheduler Admin (roles/cloudscheduler.admin)
  • Service Account User (roles/iam.serviceAccountUser)

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限。

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

创建预定构建流水线

您可以使用 gcloud CLI 或 Go 客户端库配置周期性映像构建:

gcloud

如需使用 gcloud CLI 安排定期操作系统映像构建,请完成以下步骤:

  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 存储桶,例如 gs://STAGING_BUCKET/imagebuilder.yaml。
  • 向调度服务账号 cloud-build-trigger-scheduler@PROJECT_ID.s3ns.iam.gserviceaccount.com 授予项目的 Cloud Build Editor 角色 (roles/cloudbuild.builds.editor) 以及运行构建的服务账号的 Service Account User 角色 (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
}

后续步骤