Programar builds de imagens do SO personalizadas

É possível programar execuções recorrentes do Image Builder usando os gatilhos do Cloud Scheduler e do Cloud Build. Quando você programa builds de imagens de SO personalizadas, o Cloud Build atualiza automaticamente as imagens de referência com patches do sistema operacional e políticas de segurança organizacionais sem exigir intervenção manual.

Antes de começar

  • Conclua as etapas de configuração do ambiente em Preparar o ambiente.
  • Configure a autenticação, caso ainda não tenha feito isso. Com isso, você confirma sua identidade para acesso a serviços e APIs do Google Cloud . Para executar códigos ou amostras de um ambiente de desenvolvimento local, autentique-se no Compute Engine selecionando uma das seguintes opções:

    Selecione a guia para como planeja usar as amostras nesta página:

    gcloud

    1. Instale a CLI do Google Cloud. Após a instalação, inicialize a Google Cloud CLI executando o seguinte comando:

      gcloud init

      Ao usar um provedor de identidade (IdP) externo, primeiro faça login na CLI gcloud com sua identidade federada.

  • Defina uma região e uma zona padrão.
  • Go

    Para usar as amostras do Go nesta página em um ambiente de desenvolvimento local, instale e inicialize a CLI gcloud e configure o Application Default Credentials com suas credenciais de usuário.

    1. Instale a CLI do Google Cloud.

    2. Ao usar um provedor de identidade (IdP) externo, primeiro faça login na CLI gcloud com sua identidade federada.

    3. Se você estiver usando um shell local, crie credenciais de autenticação local para sua conta de usuário:

      gcloud auth application-default login

      Não é necessário fazer isso se você estiver usando o Cloud Shell.

      Se um erro de autenticação for retornado e você estiver usando um provedor de identidade externo (IdP), confirme se você fez login na CLI gcloud com sua identidade federada.

    Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento local.

Funções exigidas

Para receber as permissões necessárias para criar gatilhos e programar builds, peça ao administrador para conceder a você os seguintes papéis do IAM no projeto:

Para mais informações sobre a concessão de papéis, consulte Gerenciar o acesso a projetos, pastas e organizações.

Também é possível conseguir as permissões necessárias usando papéis personalizados ou outros papéis predefinidos.

Criar um pipeline de build programado

É possível configurar builds de imagem recorrentes usando a CLI gcloud ou a biblioteca de cliente do Go:

gcloud

Para programar builds recorrentes de imagens do SO usando a CLI gcloud, siga estas etapas:

  1. Crie um gatilho de build manual do Cloud Build que especifique o arquivo cloudbuild.yaml de destino executando o comando gcloud builds triggers create manual. Como o Cloud Scheduler invoca esse gatilho manualmente, em vez de em eventos de repositório, não especifique filtros de solicitação de push ou pull:

    gcloud builds triggers create manual \
        --name="TRIGGER_NAME" \
        --region=REGION \
        --build-config=CLOUDBUILD_YAML_PATH \
        --project=PROJECT_ID
    
  2. Recupere o ID exclusivo (trigger_id) gerado para o novo gatilho executando o comando gcloud builds triggers describe:

    gcloud builds triggers describe TRIGGER_NAME \
        --region=REGION \
        --format="value(id)" \
        --project=PROJECT_ID
    
  3. Para criar um job do Cloud Scheduler que envie uma solicitação HTTP POST à API Cloud Build (projects.locations.triggers.run) de acordo com a programação cron do Unix necessária, por exemplo, semanalmente às segundas-feiras às 6h usando '0 6 * * 1', execute o comando 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
    

Substitua:

  • TRIGGER_NAME: o nome do gatilho de build manual do Cloud Build, por exemplo, weekly-ubuntu-builder.
  • REGION: a região em que o gatilho será criado, por exemplo, us-central1.
  • CLOUDBUILD_YAML_PATH: o caminho para o arquivo cloudbuild.yaml no diretório local, por exemplo, cloudbuild.yaml.
  • PROJECT_ID: o ID do projeto.
  • JOB_NAME: o nome do cron job do Cloud Scheduler, por exemplo, weekly-custom-os-patching.
  • CRON_SCHEDULE: a expressão de programação cron do Unix, por exemplo, '0 6 * * 1'.
  • TRIGGER_ID: o ID exclusivo (trigger_id) gerado para o gatilho.
  • SERVICE_ACCOUNT_EMAIL: o e-mail da conta de serviço que autoriza o Cloud Scheduler a invocar o gatilho. Essa conta de serviço requer permissões roles/cloudbuild.builds.editor.

Go

Para programar pipelines de maneira programática usando as bibliotecas de cliente do Cloud Build e do Cloud Scheduler para Go, use a função scheduleBuild.

Antes de executar este exemplo, conclua os seguintes pré-requisitos:

  • Faça upload do arquivo de configuração imagebuilder.yaml para o bucket do Cloud Storage, por exemplo, gs://STAGING_BUCKET/imagebuilder.yaml.
  • Conceda à conta de serviço de programação cloud-build-trigger-scheduler@PROJECT_ID.iam.gserviceaccount.com o papel de Editor do Cloud Build (roles/cloudbuild.builds.editor) no projeto e o papel de Usuário da conta de serviço (roles/iam.serviceAccountUser) na conta de serviço que executa o build. Para mais informações, consulte Programar builds na documentação do 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
}

A seguir