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

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

事前準備

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

    選取這個頁面上您打算如何使用範例的分頁:

    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. 如果您使用本機殼層,請為使用者帳戶建立本機驗證憑證:

      gcloud auth application-default login

      如果您使用 Cloud Shell,則不需要執行這項操作。

      如果系統傳回驗證錯誤,且您使用外部識別資訊提供者 (IdP),請確認您已 使用聯合身分登入 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 用戶端 SDK,設定週期性映像檔建構作業:

gcloud

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

  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 點使用 '0 6 * * 1'),將HTTP POST要求傳送至 Cloud Build API (projects.locations.triggers.run),請執行 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 點重複執行,請執行下列指令:

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'

後續步驟