Programmer la création d'images d'OS personnalisées

Vous pouvez planifier des exécutions récurrentes d'Image Builder à l'aide de Cloud Scheduler et de déclencheurs de compilation Cloud Build. Lorsque vous planifiez des compilations d'images d'OS personnalisées, Cloud Build met automatiquement à jour vos images de référence avec des correctifs de système d'exploitation et des règles de sécurité organisationnelles, sans intervention manuelle.

Avant de commencer

Rôles requis

Pour obtenir les autorisations nécessaires pour créer des déclencheurs et planifier des builds, demandez à votre administrateur de vous accorder les rôles IAM suivants sur votre projet :

Pour en savoir plus sur l'attribution de rôles, consultez la page Gérer l'accès aux projets, aux dossiers et aux organisations.

Vous pouvez également obtenir les autorisations requises avec des rôles personnalisés ou d'autres rôles prédéfinis.

Créer un pipeline de compilation planifié

Vous pouvez configurer des compilations d'images récurrentes à l'aide de la gcloud CLI ou de la bibliothèque cliente Go :

gcloud

Pour planifier des compilations d'images d'OS récurrentes à l'aide de la gcloud CLI, procédez comme suit :

  1. Créez un déclencheur de compilation Cloud Build manuel qui spécifie votre fichier cloudbuild.yaml cible en exécutant la commande gcloud builds triggers create manual. Étant donné que Cloud Scheduler appelle ce déclencheur manuellement plutôt que sur des événements de dépôt, ne spécifiez pas de filtres de demande d'extraction ou de transfert :

    gcloud builds triggers create manual \
        --name="TRIGGER_NAME" \
        --region=REGION \
        --build-config=CLOUDBUILD_YAML_PATH \
        --project=PROJECT_ID
    
  2. Récupérez l'identifiant unique (trigger_id) généré pour votre nouveau déclencheur en exécutant la commande gcloud builds triggers describe :

    gcloud builds triggers describe TRIGGER_NAME \
        --region=REGION \
        --format="value(id)" \
        --project=PROJECT_ID
    
  3. Pour créer un job Cloud Scheduler qui envoie une requête HTTP POST à l'API Cloud Build (projects.locations.triggers.run) selon la planification cron Unix requise (par exemple, tous les lundis à 6h00 à l'aide de '0 6 * * 1'), exécutez la commande 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
    

Remplacez les éléments suivants :

  • TRIGGER_NAME : nom de votre déclencheur de compilation Cloud Build manuel, par exemple weekly-ubuntu-builder.
  • REGION : région dans laquelle créer le déclencheur, par exemple us-central1.
  • CLOUDBUILD_YAML_PATH : chemin d'accès à votre fichier cloudbuild.yaml dans votre répertoire local, par exemple cloudbuild.yaml.
  • PROJECT_ID : ID de votre projet.
  • JOB_NAME : nom de votre job Cron Cloud Scheduler, par exemple weekly-custom-os-patching.
  • CRON_SCHEDULE : expression de programmation cron unix, par exemple '0 6 * * 1'.
  • TRIGGER_ID : identifiant unique (trigger_id) généré pour votre déclencheur.
  • SERVICE_ACCOUNT_EMAIL : adresse e-mail du compte de service qui autorise Cloud Scheduler à appeler le déclencheur. Ce compte de service nécessite des autorisations roles/cloudbuild.builds.editor.

Go

Pour planifier des pipelines de manière programmatique à l'aide des bibliothèques clientes Go Cloud Build et Cloud Scheduler, utilisez la fonction scheduleBuild.

Avant d'exécuter cet exemple, assurez-vous de remplir les conditions préalables suivantes :

  • Importez votre fichier de configuration imagebuilder.yaml dans votre bucket Cloud Storage, par exemple gs://STAGING_BUCKET/imagebuilder.yaml.
  • Attribuez au compte de service de planification cloud-build-trigger-scheduler@PROJECT_ID.s3ns.iam.gserviceaccount.com le rôle d'éditeur Cloud Build (roles/cloudbuild.builds.editor) sur votre projet et le rôle d'utilisateur du compte de service (roles/iam.serviceAccountUser) sur le compte de service qui exécute la compilation. Pour en savoir plus, consultez Planifier des builds dans la documentation 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
}

Étapes suivantes