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

您可以使用 Cloud Scheduler 和 Cloud Build 触发器来安排定期运行 Image Builder。在安排自定义操作系统映像构建时,Cloud Build 会自动使用操作系统补丁和组织安全政策更新基准映像,而无需手动干预。

准备工作

  • 完成 准备环境中的环境设置步骤。
  • 如果您尚未设置身份验证,请进行设置。 身份验证用于验证您的身份,以便访问 Google Cloud 服务和 API。如需从本地开发环境运行 代码或示例,您可以通过选择以下选项之一向 Compute Engine 进行身份验证: {1

    选择标签页以了解您打算如何使用本页面上的示例:

    gcloud

    1. 安装 Google Cloud CLI。 安装完成后, 初始化 Google Cloud CLI,方法是运行以下命令:

      gcloud init

      如果您使用的是外部身份提供方 (IdP),则必须先 使用联合身份登录 gcloud CLI

  • 设置默认区域和可用区
  • Go

    如需在本地开发环境中使用本页面上的 Go 示例,请安装并 初始化 gcloud CLI,然后使用您的用户凭证设置应用默认凭据。

    1. 安装 Google Cloud CLI。

    2. 如果您使用的是外部身份提供方 (IdP),则必须先 使用联合身份登录 gcloud CLI

    3. 如果您使用的是本地 shell,请为您的用户 账号创建本地身份验证凭证:

      gcloud auth application-default login

      如果您使用的是 Cloud Shell,则无需执行此操作。

      如果返回了身份验证错误,并且您使用的是外部身份提供方 (IdP),请确认您已 使用联合身份登录 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 客户端 SDK 配置定期映像构建:

gcloud

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

  1. 运行 gcloud builds triggers create manual 命令,创建一个手动 Cloud Build 构建触发器,以指定目标 cloudbuild.yaml 文件。由于 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 时间表(例如每周一上午 6:00 使用 '0 6 * * 1')向 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

您可以使用 Google Cloud Go API 客户端库 (cloudbuild/v1cloudscheduler/v1)以编程方式在企业 应用中安排流水线。

以下完整的 Go 示例演示了如何解析命令行标志,以及如何根据 imagebuilder.yaml 定义构建 Build 请求。然后,当您提供 --cron_schedule 标志时,该程序会注册手动 Cloud Build 触发器并预配 Cloud Scheduler 作业。

// Program main demonstrates how to submit or schedule a Image Builder pipeline using Go SDKs.
package main

import (
    "context"
    "flag"
    "fmt"
    "os"

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

var (
    projectID        = flag.String("project_id", "", "The target Project ID")
    region           = flag.String("region", "us-central1", "Region of resources")
    gcsWorkdir       = flag.String("gcs_workdir", "", "The storage workspace directory URI, e.g. gs://my-bucket/workdir/")
    serviceAccount   = flag.String("service_account", "", "Service account email to run the worker VM")
    imageBuilderYAML = flag.String("config_path", "imagebuilder.yaml", "Path to the imagebuilder.yaml configuration file")
    arRepositoryID   = flag.String("ar_repo_id", "os-images", "Name of the target generic Artifact Registry repository")
    arPackageName    = flag.String("ar_package_name", "custom-os", "Package identifier for OS images")
    cronSchedule     = flag.String("cron_schedule", "", "Optional cron schedule to run this build periodically (e.g. '0 6 * * 1')")
)

func main() {
    flag.Parse()

    if *projectID == "" || *serviceAccount == "" || *gcsWorkdir == "" {
        fmt.Fprintln(os.Stderr, "Error: --project_id, --gcs_workdir, and --service_account are required flags")
        flag.Usage()
        os.Exit(1)
    }

    ctx := context.Background()

    if *cronSchedule != "" {
        fmt.Printf("Scheduling build with cron schedule: %s\n", *cronSchedule)
        if err := scheduleBuild(ctx); err != nil {
            fmt.Fprintf(os.Stderr, "Failed to schedule Cloud Build: %v\n", err)
            os.Exit(1)
        }
    } else {
        fmt.Println("Submitting build request immediately to Cloud Build API...")
        if err := triggerBuild(ctx); err != nil {
            fmt.Fprintf(os.Stderr, "Failed to trigger Cloud Build: %v\n", err)
            os.Exit(1)
        }
    }
}

func createBuildRequest() *cloudbuild.Build {
    subs := map[string]string{
        "_GCS_WORKDIR":                    *gcsWorkdir,
        "_SERVICE_ACCOUNT":                fmt.Sprintf("projects/%s/serviceAccounts/%s", *projectID, *serviceAccount),
        "_IMAGE_OUTPUT_PATH":              "image-builder/binaryOut",
        "_IMAGE_BUILDER_CONFIG_PATH":      *imageBuilderYAML,
        "_ARTIFACT_REGISTRY_RESOURCE_URI": fmt.Sprintf("projects/%s/locations/%s/repositories/%s/packages/%s/versions/v${BUILD_ID}", *projectID, *region, *arRepositoryID, *arPackageName),
    }

    return &cloudbuild.Build{
        Steps: []*cloudbuild.BuildStep{
            {
                Name:   "us-central1-docker.pkg.dev/image-builder-official/release/builder:stable",
                Script: "#!/usr/bin/env bash\n/build",
                Id:     "imagebuilder-customize",
            },
            {
                Name:   "us-central1-docker.pkg.dev/image-builder-official/release/validator:stable",
                Script: "#!/usr/bin/env bash\n/validate",
                Id:     "imagebuilder-validate",
            },
            {
                Name:   "us-central1-docker.pkg.dev/image-builder-official/release/builder:stable",
                Script: "#!/usr/bin/env bash\n/publish",
                Id:     "imagebuilder-publish",
            },
        },
        Substitutions: subs,
        Options: &cloudbuild.BuildOptions{
            AutomapSubstitutions:  true,
            RequestedVerifyOption: "VERIFIED",
            SubstitutionOption:    "ALLOW_LOOSE",
            DynamicSubstitutions:  true,
        },
        Timeout: "3600s",
    }
}

func triggerBuild(ctx context.Context) error {
    cbService, err := cloudbuild.NewService(ctx)
    if err != nil {
        return err
    }
    build := createBuildRequest()
    op, err := cbService.Projects.Locations.Builds.Create(fmt.Sprintf("projects/%s/locations/%s", *projectID, *region), build).Do()
    if err != nil {
        return err
    }
    fmt.Printf("Build submitted: %s\n", op.Name)
    return nil
}

func scheduleBuild(ctx context.Context) error {
    cbService, err := cloudbuild.NewService(ctx)
    if err != nil {
        return err
    }
    csService, err := cloudscheduler.NewService(ctx)
    if err != nil {
        return err
    }

    // 1. Create a manual Cloud Build trigger
    trigger := &cloudbuild.BuildTrigger{
        Build:       createBuildRequest(),
        Name:        "scheduled-image-builder-trigger",
        Description: "Manual trigger invoked periodically via Cloud Scheduler for Image Builder",
    }
    createdTrigger, err := cbService.Projects.Locations.Triggers.Create(fmt.Sprintf("projects/%s/locations/%s", *projectID, *region), trigger).Do()
    if err != nil {
        return fmt.Errorf("failed creating trigger: %w", err)
    }
    fmt.Printf("Created Cloud Build trigger with ID: %s\n", createdTrigger.Id)

    // 2. Create the Cloud Scheduler job
    targetURI := fmt.Sprintf("https://cloudbuild.googleapis.com/v1/projects/%s/locations/%s/triggers/%s:run", *projectID, *region, createdTrigger.Id)
    job := &cloudscheduler.Job{
        Name:        fmt.Sprintf("projects/%s/locations/%s/jobs/weekly-image-builder-job", *projectID, *region),
        Schedule:    *cronSchedule,
        Description: "Scheduled job to run custom OS Image Builder pipeline",
        HttpTarget: &cloudscheduler.HttpTarget{
            Uri:        targetURI,
            HttpMethod: "POST",
            OauthToken: &cloudscheduler.OAuthToken{
                ServiceAccountEmail: *serviceAccount,
            },
        },
    }

    createdJob, err := csService.Projects.Locations.Jobs.Create(fmt.Sprintf("projects/%s/locations/%s", *projectID, *region), job).Do()
    if err != nil {
        return fmt.Errorf("failed creating scheduler job: %w", err)
    }
    fmt.Printf("Successfully scheduled job: %s\n", createdJob.Name)
    return nil
}

执行已安排的 Go 应用

如需运行已编译的 Go 二进制文件并安排每周一 上午 6:00 定期执行,请运行以下命令:

go run main.go \
  --project_id PROJECT_ID \
  --region us-central1 \
  --gcs_workdir gs://STAGING_BUCKET/workdir/ \
  --service_account SERVICE_ACCOUNT_EMAIL \
  --config_path gs://STAGING_BUCKET/imagebuilder.yaml \
  --cron_schedule '0 6 * * 1'

后续步骤