커스텀 OS 이미지 빌드 예약

Cloud Scheduler 및 Cloud Build 트리거를 사용하여 반복되는 Image Builder 실행을 예약할 수 있습니다. 맞춤 OS 이미지 빌드를 예약하면 Cloud Build가 수동 개입 없이 운영체제 패치와 조직 보안 정책으로 기준 이미지를 자동으로 업데이트합니다.

시작하기 전에

  • 환경 준비의 환경 설정 단계를 완료합니다.
  • 아직 인증을 설정하지 않았다면 설정합니다. 인증은 Cloud de Confiance by S3NS 서비스 및 API에 액세스하기 위해 ID를 확인합니다. 로컬 개발 환경에서 코드 또는 샘플을 실행하려면 다음 옵션 중 하나를 선택하여 Compute Engine에 인증하면 됩니다.

    이 페이지의 샘플 사용 방법에 대한 탭을 선택하세요.

    gcloud

    1. Google Cloud CLI를 설치한 후 제휴 ID로 gcloud CLI에 로그인합니다. 로그인한 후 다음 명령어를 실행하여 Google Cloud CLI를 초기화합니다.

      gcloud init
  • 기본 리전 및 영역을 설정합니다.
  • Go

    로컬 개발 환경에서 이 페이지의 Go 샘플을 사용하려면 gcloud CLI를 설치 및 초기화한 다음 사용자 인증 정보로 애플리케이션 기본 사용자 인증 정보를 설정하세요.

    1. Google Cloud CLI를 설치합니다.

    2. gcloud CLI에서 제휴 ID를 사용하도록 구성합니다.

      자세한 내용은 제휴 ID로 gcloud CLI에 로그인을 참고하세요.

    3. 사용자 계정에 대한 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

      인증 오류가 반환되고 외부 ID 공급업체(IdP)를 사용하는 경우 제휴 ID로 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 명령어를 실행하여 타겟 cloudbuild.yaml 파일을 지정하는 수동 Cloud Build 빌드 트리거를 만듭니다. Cloud Scheduler는 저장소 이벤트가 아닌 수동으로 이 트리거를 호출하므로 푸시 또는 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시에 매주)에 따라 Cloud Build API(projects.locations.triggers.run)에 HTTP POST 요청을 전송하는 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 크론 작업의 이름입니다(예: weekly-custom-os-patching).
  • CRON_SCHEDULE: 유닉스 크론 일정 표현식입니다(예: '0 6 * * 1').
  • TRIGGER_ID: 트리거에 대해 생성된 고유 ID (trigger_id)입니다.
  • SERVICE_ACCOUNT_EMAIL: Cloud Scheduler가 트리거를 호출하도록 승인하는 서비스 계정 이메일입니다. 이 서비스 계정에는 roles/cloudbuild.builds.editor 권한이 필요합니다.

Go

Cloud de Confiance 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'

다음 단계