Criar um pipeline de imagem do SO personalizada de maneira programática

É possível configurar e enviar um pipeline do Image Builder de maneira programática usando a CLI gcloud, o Terraform ou bibliotecas de cliente, como Go. A configuração programática do pipeline permite definir configurações de infraestrutura, imagens de SO de base, ações de personalização e testes de validação em arquivos de configuração declarativos.

Antes de começar

Funções exigidas

Para receber as permissões necessárias para criar e enviar pipelines de personalização de imagem de maneira programática, 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.

Crie os arquivos de configuração.

Para configurar o pipeline usando a CLI gcloud ou o Terraform, crie dois arquivos de configuração:

  • imagebuilder.yaml: define a receita de personalização da imagem, incluindo a imagem do SO de base, as configurações de infraestrutura da VM de worker, os detalhes de saída da imagem de destino e as etapas de personalização sequenciais, como execução de scripts de shell, transferência de arquivos ou reinicializações.
  • cloudbuild.yaml: orquestra as etapas do processo de build no Cloud Build, incluindo análise, validação, criação, teste e publicação da imagem do SO personalizada.

Se você enviar o build usando Go, só precisará criar o arquivo imagebuilder.yaml e fazer upload dele para o bucket do Cloud Storage, porque o exemplo de Go define as etapas de orquestração do Cloud Build no código.

Criar o arquivo de configuração da imagem

Para especificar a configuração da imagem, crie um arquivo chamado imagebuilder.yaml no diretório local. Para uma lista completa de todos os campos de esquema e ações de personalização compatíveis, consulte Esquema de receita de personalização e Ações de personalização compatíveis.

O exemplo de arquivo imagebuilder.yaml a seguir configura um pipeline que cria uma imagem personalizada do Ubuntu 22.04 LTS usando uma VM de worker e2-standard-4 na região e zona especificadas e executa uma atualização do pacote do sistema.

apiVersion: imagebuilder.gcp.com/v1
kind: OSImageCustomization
metadata:
  name: customized-ubuntu-baseline
  description: "Ubuntu 22.04 LTS custom OS baseline image"
infrastructureConfig:
  machineType: e2-standard-4
  zone: ZONE
  debug: false
source:
  imageFamily: projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts
destinations:
  - diskImage:
      name: custom-ubuntu-v1
      family: custom-ubuntu-family
      project: PROJECT_ID
      storageLocations:
        - REGION
spec:
  config:
    skipSystemTests: false
  steps:
    -   name: "System Package Update"
      action: Shell
      inputs:
        command: "apt-get update -y && apt-get upgrade -y"

Substitua os seguintes valores de marcador:

  • PROJECT_ID: o ID do projeto.
  • REGION: o local de armazenamento da imagem de destino, por exemplo, us-east1 ou europe-west1. Verifique se você atende aos seguintes requisitos regionais e zonais:
    • O Image Builder só é compatível com as regiões em que o Cloud Build está disponível.
    • A VM de worker ZONE precisa estar localizada na REGION especificada.
    • Para minimizar a latência de rede e evitar cobranças de saída entre regiões, verifique se a zona da VM de worker, o bucket de preparo do Cloud Storage, o repositório do Artifact Registry e o local de armazenamento da imagem de destino estão no mesmo local na mesma região.
  • ZONE: uma zona localizada no REGION especificado, por exemplo, us-east1-b ou europe-west1-b.

Criar o arquivo de build do orquestrador

Crie um arquivo chamado cloudbuild.yaml no mesmo diretório. Esse arquivo chama as etapas do contêiner do Image Builder para criar, validar e publicar a imagem do SO personalizada.

substitutions:
  _GCS_WORKDIR: 'gs://STAGING_BUCKET_NAME/workdir/'
  _IMAGE_BUILDER_CONFIG_PATH: 'imagebuilder.yaml'
  _SERVICE_ACCOUNT: 'projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL'
  _IMAGE_OUTPUT_PATH: 'image-builder/binaryOut'
  _ARTIFACT_REGISTRY_RESOURCE_URI: 'projects/PROJECT_ID/locations/REGION/repositories/REPOSITORY_NAME/packages/PACKAGE_NAME/versions/v${BUILD_ID}'

steps:
  #  Step 1: Parse configs and run OS customization on worker VM
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/builder:stable'
    script: |
      #!/usr/bin/env bash
      /build
    id: 'imagebuilder-customize'
    results:
      - name: image_builder_telemetry_metrics
      - name: base_image
        attestationType: "https://cloudbuild.googleapis.com/attestations/build_content_restrictions"
        attestationContent: base_image

  # Step 2: Validate by running system boot checks on a test VM
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/validator:stable'
    script: |
      #!/usr/bin/env bash
      /validate
    id: 'imagebuilder-validate'
    results:
      - name: image_builder_telemetry_metrics

  # Step 3: Register image in Compute Engine and upload tar files to Artifact Registry
  - name: 'REGION-docker.pkg.dev/image-builder-official/release/builder:stable'
    script: |
      #!/usr/bin/env bash
      /publish
    id: 'imagebuilder-publish'
    results:
      - name: image_builder_telemetry_metrics

options:
  automapSubstitutions: true
  requestedVerifyOption: VERIFIED
  substitutionOption: ALLOW_LOOSE
  dynamicSubstitutions: true
  logging: CLOUD_LOGGING_ONLY

artifacts:
  generic_artifacts:
    - folder: '${_IMAGE_OUTPUT_PATH}'
      registry_path: '${_ARTIFACT_REGISTRY_RESOURCE_URI}'

timeout: '3600s'

Substitua os seguintes valores de marcador:

  • STAGING_BUCKET_NAME: um bucket do Cloud Storage no projeto para usar como um espaço de trabalho de preparo temporário. Se você não tiver um bucket, crie um executando gcloud storage buckets create gs://STAGING_BUCKET_NAME. Se você implantar o pipeline usando o Terraform, ele vai criar esse bucket automaticamente.
  • PROJECT_ID: o ID do projeto Cloud de Confiance .
  • REGION: a região do Cloud de Confiance para o repositório do Artifact Registry, por exemplo, us-east1 ou europe-west1.
  • SERVICE_ACCOUNT_EMAIL: o e-mail da conta de serviço que você configurou com as permissões necessárias do IAM.
  • REPOSITORY e PACKAGE: o repositório de destino e o nome do pacote criados no Artifact Registry. Para configurar o Artifact Registry, consulte Configurar o Artifact Registry.

Criar e enviar o pipeline de build

Para executar o pipeline de personalização de imagens, envie um build usando a CLI gcloud, implante o pipeline usando o Terraform ou envie um build usando a biblioteca de cliente do Go. Selecione uma das seguintes guias:

gcloud

Para implantar e executar o pipeline de personalização de imagens, no diretório do terminal local que contém os dois arquivos de configuração, execute o comando gcloud builds submit:

gcloud builds submit . \
    --config=cloudbuild.yaml \
    --project=PROJECT_ID \
    --service-account="projects/PROJECT_ID/serviceAccounts/SERVICE_ACCOUNT_EMAIL" \
    --region=REGION

Substitua:

  • PROJECT_ID: o ID do projeto Cloud de Confiance .
  • REGION: a região do Cloud de Confiance em que o job do pipeline de personalização de imagem será executado.
  • SERVICE_ACCOUNT_EMAIL: o endereço de e-mail da conta de serviço configurada com as permissões do IAM necessárias.

Esse comando faz upload do espaço de trabalho de personalização, registra a execução do Cloud Build e inicia os contêineres de orquestração.

Terraform

Para provisionar a infraestrutura necessária para criar e validar automaticamente imagens do SO personalizadas, use o Terraform. Essa configuração do Terraform conclui as seguintes tarefas:

  • Ativa as APIs Cloud de Confiance necessárias.
  • Cria um bucket dedicado do Cloud Storage (workdir_bucket) para armazenar registros temporários e criar artefatos.
  • Configura um gatilho de build do Cloud Build (image_builder_trigger) vinculado à conexão do repositório do GitHub do Developer Connect.

Criar os arquivos de configuração do Terraform

Para organizar e implantar a infraestrutura do pipeline usando o Terraform, siga estas etapas:

  1. Crie um diretório dedicado na estação de trabalho local ou no ambiente de CI/CD, separado do repositório do aplicativo, e mude para ele:

    mkdir terraform-image-builder && cd terraform-image-builder
    
  2. Nesse diretório, crie os cinco arquivos de configuração do Terraform a seguir:

    • terraform.tfvars: define valores para variáveis específicas do projeto.
    • main.tf: provisiona recursos no projeto do Cloud de Confiance , incluindo a ativação das APIs necessárias, a criação do bucket de preparo do Cloud Storage (workdir_bucket) e a implantação do gatilho de build do Cloud Build (image_builder_trigger).
    • outputs.tf: define os valores de saída exibidos no terminal após a implantação, como o ID do gatilho e o nome do bucket de preparo.
    • providers.tf: especifica a versão necessária do Terraform (>= 1.3) e configura o provedor do Cloud de Confiance by S3NS (hashicorp/google).
    • variables.tf: define variáveis de entrada, valores padrão e regras de validação para a implantação.

    Selecione uma das guias a seguir para visualizar e copiar a configuração de cada arquivo para o diretório local:

    terraform.tfvars

    Esse arquivo especifica valores de parâmetro para o ambiente das variáveis declaradas:

    project_id                = "PROJECT_ID"
    builder_service_account   = "SERVICE_ACCOUNT_EMAIL"
    github_repo_name          = "projects/PROJECT_ID/locations/LOCATION/connections/CONNECTION/repositories/REPO_NAME"
    region                    = "REGION"
    trigger_name              = "TRIGGER_NAME"
    cloudbuild_yaml_path      = "CLOUDBUILD_YAML_PATH"
    image_builder_config_path = "RECIPE_PATH"
    gcs_lifecycle_age_days    = LIFECYCLE_DAYS
    ar_repository_id          = "REPOSITORY_NAME"
    ar_package_name           = "PACKAGE_NAME"
    

    Substitua os seguintes marcadores de posição pelos recursos preexistentes:

    • PROJECT_ID: o ID do projeto doCloud de Confiance by S3NS .
    • SERVICE_ACCOUNT_EMAIL: o endereço de e-mail da conta de serviço de build configurada em Configurar conta de serviço do Image Builder.
    • LOCATION, CONNECTION e REPO_NAME: a região do host, o nome da conexão e o link do repositório do Developer Connect configurados em Conectar um repositório.
    • CLOUDBUILD_YAML_PATH: o caminho relativo para o arquivo cloudbuild.yaml no diretório local. Não é necessário verificar cloudbuild.yaml no repositório Git.
    • RECIPE_PATH: o caminho relativo para o arquivo de receita de personalização do imagebuilder.yaml verificado no repositório do Git.
    • REPOSITORY_NAME: o repositório genérico do Artifact Registry criado em Configurar o Artifact Registry.

    Substitua os seguintes marcadores de posição para os recursos que o Terraform cria:

    • REGION: a região de destino do Cloud de Confianceem que o Terraform provisiona o bucket de preparo do Cloud Storage e o gatilho de build do Cloud Build, por exemplo, us-central1.
    • TRIGGER_NAME: o nome do novo gatilho de repositório do Cloud Build criado pelo Terraform, por exemplo, git-push-os-builder.
    • LIFECYCLE_DAYS: o período de armazenamento em dias antes que os artefatos intermediários no bucket de preparo do Cloud Storage criado pelo Terraform sejam excluídos automaticamente, por exemplo, 30.
    • PACKAGE_NAME: o nome que você quer que o Terraform use para o pacote criado no repositório do Artifact Registry. Esse pacote armazena versões de imagem do SO publicadas, por exemplo, ubuntu-custom.

    main.tf

    Esse arquivo declara os recursos e as fontes de dados da infraestrutura principal para a implantação:

    # Main resource configurations for Image Builder.
    # 1. Enable Required APIs
    resource "google_project_service" "apis" {
      for_each = toset([
        "compute.googleapis.com",
        "cloudbuild.googleapis.com",
        "artifactregistry.googleapis.com",
        "serviceusage.googleapis.com",
        "cloudresourcemanager.googleapis.com",
        "iam.googleapis.com",
        "storage.googleapis.com"
      ])
      project = var.project_id
      service = each.key
      disable_on_destroy = false
    }
    
    # 2. Project data source to retrieve Project Number
    data "google_project" "project" {
      project_id = var.project_id
      depends_on = [google_project_service.apis]
    }
    
    locals {
      builder_sa = var.builder_service_account
    }
    
    # 3. Storage Bucket for Image Builder Workdir
    resource "google_storage_bucket" "workdir_bucket" {
      name                        = var.gcs_bucket_name != "" ? var.gcs_bucket_name : "${var.project_id}-vm-builder-workdir"
      project                     = var.project_id
      location                    = var.region
      force_destroy               = true
      uniform_bucket_level_access = true
      lifecycle_rule {
        action {
          type = "Delete"
        }
        condition {
          age = var.gcs_lifecycle_age_days
        }
      }
      depends_on = [google_project_service.apis]
    }
    
    # 4. Cloud Build Trigger
    resource "google_cloudbuild_trigger" "image_builder_trigger" {
      name        = var.trigger_name
      location    = var.region
      project     = var.project_id
      description = "Trigger that runs Image Builder customization"
    
      service_account = var.builder_service_account != "" ? "projects/${var.project_id}/serviceAccounts/${var.builder_service_account}" : null
    
      repository_event_config {
        repository = replace(var.github_repo_name, "gitRepositoryLinks", "repositories")
        push {
          branch = "^main$"
        }
      }
      filename = var.cloudbuild_yaml_path
    
      substitutions = {
        _GCS_WORKDIR                    = "gs://${google_storage_bucket.workdir_bucket.name}/workdir/"
        _SERVICE_ACCOUNT                = "projects/${var.project_id}/serviceAccounts/${local.builder_sa}"
        _IMAGE_OUTPUT_PATH              = "image-builder/binaryOut"
        _PROJECT_ID                     = var.project_id
        _LOCATION                       = var.region
        _REPOSITORY_NAME                = var.ar_repository_id
        _PACKAGE_NAME                   = var.ar_package_name
        _IMAGE_BUILDER_CONFIG_PATH      = var.image_builder_config_path
        _ARTIFACT_REGISTRY_RESOURCE_URI = "projects/${var.project_id}/locations/${var.region}/repositories/${var.ar_repository_id}/packages/${var.ar_package_name}/versions/v$${BUILD_ID}"
      }
      depends_on = [
        google_project_service.apis
      ]
    }
    

    outputs.tf

    Esse arquivo define os atributos de saída retornados ao terminal após a implantação:

    output "builder_service_account" {
      value       = local.builder_sa
      description = "The email representation of the resolved Image Builder service account."
    }
    
    output "workdir_bucket" {
      value       = google_storage_bucket.workdir_bucket.name
      description = "The name of the storage workdir bucket."
    }
    
    output "artifact_registry_repository" {
      value       = "projects/${var.project_id}/locations/${var.region}/repositories/${var.ar_repository_id}"
      description = "The fully qualified resource path of the Artifact Registry repository."
    }
    
    output "cloud_build_trigger_id" {
      value       = google_cloudbuild_trigger.image_builder_trigger.trigger_id
      description = "The unique ID for the created Cloud Build Trigger."
    }
    

    providers.tf

    Esse arquivo define as configurações necessárias de versão e região do Terraform:

    terraform {
      required_version = ">= 1.3"
      required_providers {
        google = {
          source  = "hashicorp/google"
          version = ">= 5.0, < 7.0"
        }
      }
    }
    
    provider "google" {
      project = var.project_id
      region  = var.region
    }
    

    variables.tf

    Esse arquivo declara todas as variáveis de entrada obrigatórias e opcionais e as regras de validação:

    variable "project_id" {
      type        = string
      description = "The target Project ID where resources will be created."
      validation {
        condition     = can(regex("^[a-z0-9-]{6,30}$", var.project_id))
        error_message = "The project_id must consist of lowercase letters, numbers, and hyphens, and be between 6 and 30 characters."
      }
    }
    
    variable "region" {
      type        = string
      default     = "us-central1"
      description = "Location used for cloud build triggers, storage buckets, and artifact registry."
    }
    
    variable "github_repo_name" {
      type        = string
      default     = ""
      description = "Developer Connect github repository details, format: projects/PROJECT_ID/locations/LOCATION/connections/CONNECTION/repositories/REPO_LINK"
      validation {
        condition     = can(regex("^projects/[^/]+/locations/[^/]+/connections/[^/]+/(gitRepositoryLinks|repositories)/[^/]+$", var.github_repo_name))
        error_message = "The github_repo_name must follow either the Cloud Build v2 repository link format (using '/repositories/') or the Developer Connect resource format (using '/gitRepositoryLinks/')."
      }
    }
    
    variable "builder_service_account" {
      type        = string
      default     = ""
      description = "The email representation of the pre-existing Image Builder service account. If omitted, the default Cloud Build service account will be used."
    }
    
    variable "gcs_bucket_name" {
      type        = string
      default     = ""
      description = "Custom name for the workdir storage bucket. If left empty, a default name using the project ID will be constructed."
    }
    
    variable "gcs_lifecycle_age_days" {
      type        = number
      default     = 30
      description = "The number of days after which temporary logs and artifacts in the storage workdir bucket are deleted."
    }
    
    variable "ar_repository_id" {
      type        = string
      default     = "vm-images"
      description = "The repository ID for the generic Artifact Registry hosting the final OS image tarballs."
    }
    
    variable "ar_package_name" {
      type        = string
      default     = "image-builder"
      description = "The package name under which the generic OS image artifact will be registered in Artifact Registry."
    }
    
    variable "trigger_name" {
      type        = string
      default     = "custom-os-image-builder"
      description = "The name of the Cloud Build trigger."
    }
    
    variable "cloudbuild_yaml_path" {
      type        = string
      default     = "cloudbuild.yaml"
      description = "The path to the cloudbuild.yaml configuration file relative to the repository root."
    }
    
    variable "image_builder_config_path" {
      type        = string
      default     = "imagebuilder.yaml"
      description = "The path to the imagebuilder.yaml configuration file relative to the repository root."
    }
    
  3. Para implantar as configurações, execute os seguintes comandos no diretório que contém os arquivos do Terraform:

    1. Inicialize o diretório:
      terraform init
    2. Valide a sintaxe:
      terraform validate
    3. Visualize a implantação:
      terraform plan
    4. Aplique a configuração:
      terraform apply

Go

Antes de executar essa amostra, faça upload do arquivo de configuração imagebuilder.yaml para o bucket de preparo do Cloud Storage, por exemplo, gs://STAGING_BUCKET_NAME/imagebuilder.yaml.

Para enviar um build do Cloud Build que executa o pipeline do Image Builder usando a biblioteca de cliente do Go do Cloud Build, use o seguinte exemplo:

import (
	"context"
	"encoding/json"
	"fmt"
	"io"

	"google.golang.org/api/cloudbuild/v1"
)

// submitBuild submits a Cloud Build build that uses Image Builder to
// customize, validate, and publish a VM image to Compute Engine and Artifact
// Registry.
//
// 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.
func submitBuild(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"

	ctx := context.Background()
	cbService, err := cloudbuild.NewService(ctx)
	if err != nil {
		return fmt.Errorf("cloudbuild.NewService: %w", err)
	}

	sa := fmt.Sprintf("projects/%s/serviceAccounts/%s", projectID, serviceAccount)
	req := &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",
	}

	parent := fmt.Sprintf("projects/%s/locations/%s", projectID, region)
	op, err := cbService.Projects.Locations.Builds.Create(parent, req).Context(ctx).Do()
	if err != nil {
		return fmt.Errorf("Builds.Create: %w", err)
	}

	// The operation metadata contains the build that was created.
	var metadata cloudbuild.BuildOperationMetadata
	if err := json.Unmarshal(op.Metadata, &metadata); err != nil {
		return fmt.Errorf("json.Unmarshal: %w", err)
	}
	fmt.Fprintf(w, "Build submitted. ID: %s\n", metadata.Build.Id)
	fmt.Fprintf(w, "Logs: %s\n", metadata.Build.LogUrl)
	return nil
}

Verificar e monitorar o build

Para acompanhar o progresso do pipeline de build, siga estas etapas:

  1. No console Cloud de Confiance , acesse a página do Cloud Build.

    Acessar o Cloud Build

  2. No menu de navegação, clique em Histórico para ver os jobs ativos ou concluídos.

  3. Na lista Builds, clique no ID do build para inspecionar os registros de execução do contêiner. Os registros mostram as etapas que estão sendo executadas na VM de worker, como atualizações de pacotes do sistema ou comandos de shell personalizados, seguidas pelos resultados do teste de validação da VM de teste e pelo registro de saída final.

A seguir