커스텀 OS 이미지 빌드 예약

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

시작하기 전에

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

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

    gcloud

    1. Google Cloud CLI를 설치합니다. 설치 후 다음 명령어를 실행하여 Google Cloud CLI를 초기화합니다.

      gcloud init

      외부 ID 공급업체(IdP)를 사용하는 경우 먼저 제휴 ID로 gcloud CLI에 로그인해야 합니다.

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

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

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

    2. 외부 ID 공급업체 (IdP)를 사용하는 경우 먼저 제휴 ID로 gcloud CLI에 로그인해야 합니다.

    3. 로컬 셸을 사용하는 경우 사용자 계정에 대한 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

      Cloud Shell을 사용하는 경우 이 작업을 수행할 필요는 없습니다.

      인증 오류가 반환되고 외부 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 클라이언트 라이브러리를 사용하여 반복 이미지 빌드를 구성할 수 있습니다.

gcloud

gcloud CLI를 사용하여 반복되는 OS 이미지 빌드를 예약하려면 다음 단계를 완료합니다.

  1. gcloud builds triggers create manual 명령어를 실행하여 대상 cloudbuild.yaml 파일을 지정하는 수동 Cloud Build 빌드 트리거를 만듭니다. Cloud Scheduler는 저장소 이벤트가 아닌 수동으로 이 트리거를 호출하므로 push 또는 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 일정에 따라 Cloud Build API(projects.locations.triggers.run)에 HTTP POST 요청을 전송하는 Cloud Scheduler 작업을 만들려면(예: '0 6 * * 1'를 사용하여 매주 월요일 오전 6시) 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: unix 크론 일정 표현식입니다(예: '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.iam.gserviceaccount.com, 프로젝트의 Cloud Build 편집자 역할 (roles/cloudbuild.builds.editor), 빌드를 실행하는 서비스 계정의 서비스 계정 사용자 역할(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
}

다음 단계