Crea una canalización de imagen de SO personalizada de manera programática

Puedes configurar y enviar una canalización de Image Builder de manera programática con gcloud CLI, Terraform o bibliotecas cliente como Go. La configuración programática de la canalización te permite definir la configuración de la infraestructura, las imágenes de SO base, las acciones de personalización y las pruebas de validación en archivos de configuración declarativos.

Antes de comenzar

Roles obligatorios

Para obtener los permisos que necesitas para crear y enviar canalizaciones de personalización de imágenes de forma programática, pídele a tu administrador que te otorgue los siguientes roles de IAM en tu proyecto:

Para obtener más información sobre cómo otorgar roles, consulta Administra el acceso a proyectos, carpetas y organizaciones.

También puedes obtener los permisos necesarios a través de roles personalizados o cualquier otro rol predefinido.

Crea los archivos de configuración.

Para configurar tu canalización con gcloud CLI o Terraform, crea dos archivos de configuración:

  • imagebuilder.yaml: Define la receta de personalización de la imagen, incluida la imagen de SO base, la configuración de la infraestructura de la VM de trabajador, los detalles de salida de la imagen de destino y los pasos de personalización secuenciales, como la ejecución de secuencias de comandos de shell, la transferencia de archivos o la realización de reinicios.
  • cloudbuild.yaml: Organiza los pasos del proceso de compilación en Cloud Build, incluidos el análisis, la validación, la compilación, las pruebas y la publicación de la imagen de SO personalizada.

Si envías tu compilación con Go, solo necesitas crear el archivo imagebuilder.yaml y subirlo a tu bucket de Cloud Storage, ya que la muestra de Go define los pasos de organización de Cloud Build en el código.

Crea el archivo de configuración de la imagen

Para especificar la configuración de la imagen, crea un archivo llamado imagebuilder.yaml en tu directorio local. Para obtener una lista completa de todos los campos de esquema y las acciones de personalización compatibles, consulta Esquema de recetas de personalización y Acciones de personalización compatibles.

En el siguiente archivo imagebuilder.yaml de ejemplo, se configura una canalización que compila una imagen personalizada de Ubuntu 22.04 LTS con una VM de trabajador de e2-standard-4 en la región y la zona que especificaste, y realiza una actualización del paquete del 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"

Reemplaza los siguientes valores de marcador de posición:

  • PROJECT_ID: Es el ID del proyecto.
  • REGION: la ubicación de almacenamiento de la imagen de destino, por ejemplo, us-east1 o europe-west1. Asegúrate de cumplir con los siguientes requisitos regionales y zonales:
    • Image Builder solo se admite en regiones en las que Cloud Build está disponible.
    • La VM de trabajador ZONE debe estar ubicada dentro de la REGION que especificaste.
    • Para minimizar la latencia de red y evitar los cargos de salida entre regiones, asegúrate de que la zona de la VM de trabajador, el bucket de etapa de pruebas de Cloud Storage, el repositorio de Artifact Registry y la ubicación de almacenamiento de la imagen de destino se encuentren en la misma región.
  • ZONE: Es una zona ubicada dentro de la REGION que especificaste, por ejemplo, us-east1-b o europe-west1-b.

Crea el archivo de compilación del organizador

Crea un archivo llamado cloudbuild.yaml en el mismo directorio. Este archivo llama a los pasos del contenedor de Image Builder para compilar, validar y publicar la imagen de 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'

Reemplaza los siguientes valores de marcador de posición:

  • STAGING_BUCKET_NAME: Es un bucket de Cloud Storage existente en tu proyecto para usarlo como un espacio de trabajo de etapa de pruebas temporal. Si no tienes un bucket, puedes crear uno ejecutando gcloud storage buckets create gs://STAGING_BUCKET_NAME. Si implementas tu canalización con Terraform, este creará el bucket automáticamente.
  • PROJECT_ID: Es el ID del proyecto de Google Cloud .
  • REGION: Es la región de Google Cloud para tu repositorio de Artifact Registry, por ejemplo, us-east1 o europe-west1.
  • SERVICE_ACCOUNT_EMAIL: Es el correo electrónico de la cuenta de servicio que configuraste con los permisos de IAM necesarios.
  • REPOSITORY y PACKAGE: el nombre del repositorio y del paquete de destino creados en Artifact Registry. Para configurar el registro de Artifact Registry, consulta Configura Artifact Registry.

Crea y envía la canalización de compilación

Para ejecutar tu canalización de personalización de imágenes, envía una compilación con la gcloud CLI, implementa tu canalización con Terraform o envía una compilación con la biblioteca cliente de Go. Selecciona una de las siguientes pestañas:

gcloud

Para implementar y ejecutar la canalización de personalización de imágenes, desde el directorio de la terminal local que contiene ambos archivos de configuración, ejecuta el comando gcloud builds submit:

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

Reemplaza lo siguiente:

  • PROJECT_ID: Es el ID del proyecto de Google Cloud .
  • REGION: Es la región de Google Cloud en la que se ejecutará el trabajo de canalización de personalización de imágenes.
  • SERVICE_ACCOUNT_EMAIL: Es la dirección de correo electrónico de la cuenta de servicio configurada con los permisos de IAM necesarios.

Este comando sube tu espacio de trabajo de personalización, registra la ejecución de Cloud Build y lanza los contenedores de organización.

Terraform

Para aprovisionar la infraestructura necesaria para compilar y validar automáticamente las imágenes de SO personalizadas, puedes usar Terraform. Esta configuración de Terraform completa las siguientes tareas:

  • Habilita las APIs de Google Cloud necesarias.
  • Crea un bucket de Cloud Storage dedicado (workdir_bucket) para almacenar registros temporales y artefactos de compilación.
  • Configura un activador de compilación de Cloud Build (image_builder_trigger) vinculado a tu conexión del repositorio de GitHub de Developer Connect.

Crea tus archivos de configuración de Terraform

Para organizar e implementar la infraestructura de tu canalización con Terraform, completa los siguientes pasos:

  1. Crea un directorio dedicado en tu estación de trabajo local o entorno de CI/CD, independiente del repositorio de tu aplicación, y cámbiate a él:

    mkdir terraform-image-builder && cd terraform-image-builder
    
  2. En este directorio, crea los siguientes cinco archivos de configuración de Terraform:

    • terraform.tfvars: Establece valores para las variables específicas del proyecto.
    • main.tf: Aprovisiona recursos en tu proyecto de Google Cloud , lo que incluye habilitar las APIs necesarias, crear el bucket de etapa de pruebas de Cloud Storage (workdir_bucket) y, luego, implementar el activador de compilación de Cloud Build (image_builder_trigger).
    • outputs.tf: Define los valores de salida que se muestran en la terminal después de la implementación, como el ID del activador y el nombre del bucket de etapa de pruebas.
    • providers.tf: Especifica la versión de Terraform requerida (>= 1.3) y configura el proveedor de Google Cloud (hashicorp/google).
    • variables.tf: Define las variables de entrada, los valores predeterminados y las reglas de validación para la implementación.

    Selecciona una de las siguientes pestañas para ver y copiar la configuración de cada archivo en tu directorio local:

    terraform.tfvars

    En este archivo, se especifican los valores de los parámetros de tu entorno para las variables 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"
    

    Reemplaza los siguientes marcadores de posición por tus recursos preexistentes:

    • PROJECT_ID: Es el ID del proyecto deGoogle Cloud existente.
    • SERVICE_ACCOUNT_EMAIL: Es la dirección de correo electrónico de tu cuenta de servicio de compilación configurada en Configura la cuenta de servicio de Image Builder.
    • LOCATION, CONNECTION y REPO_NAME: la región de host de Developer Connect, el nombre de la conexión y el vínculo del repositorio configurados en Conecta un repositorio.
    • CLOUDBUILD_YAML_PATH: Es la ruta de acceso relativa a tu archivo cloudbuild.yaml en tu directorio local. No es necesario que verifiques cloudbuild.yaml en tu repositorio de Git.
    • RECIPE_PATH: Es la ruta de acceso relativa a tu archivo de receta de personalización de imagebuilder.yaml que se registró en tu repositorio de Git.
    • REPOSITORY_NAME: Es el repositorio genérico existente de Artifact Registry creado en Configura Artifact Registry.

    Reemplaza los siguientes marcadores de posición por los recursos que crea Terraform:

    • REGION: Es la región de destino de Google Cloud en la que Terraform aprovisiona el bucket de etapa de pruebas de Cloud Storage y el activador de compilación de Cloud Build, por ejemplo, us-central1.
    • TRIGGER_NAME: Es el nombre del nuevo activador del repositorio de Cloud Build creado por Terraform, por ejemplo, git-push-os-builder.
    • LIFECYCLE_DAYS: Es el período de retención en días antes de que se borren automáticamente los artefactos intermedios en el bucket de etapa de pruebas de Cloud Storage creado por Terraform, por ejemplo, 30.
    • PACKAGE_NAME: Es el nombre que deseas que Terraform use para el paquete creado dentro de tu repositorio de Artifact Registry. Este paquete almacena las versiones de imagen de SO publicadas, por ejemplo, ubuntu-custom.

    main.tf

    En este archivo, se declaran los recursos de infraestructura y las fuentes de datos principales para la implementación:

    # 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

    En este archivo, se definen los atributos de salida que se devuelven a tu terminal después de la implementación:

    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

    En este archivo, se configuran la versión de Terraform y los parámetros de configuración de la región necesarios:

    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

    En este archivo, se declaran todas las variables de entrada obligatorias y opcionales, y las reglas de validación:

    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 implementar los parámetros de configuración, ejecuta los siguientes comandos en el directorio que contiene tus archivos de Terraform:

    1. Inicializa el directorio:
      terraform init
    2. Valida la sintaxis:
      terraform validate
    3. Obtén una vista previa de la implementación:
      terraform plan
    4. Aplica la configuración:
      terraform apply

Go

Antes de ejecutar este ejemplo, sube tu archivo de configuración imagebuilder.yaml al bucket de etapa de pruebas de Cloud Storage, por ejemplo, gs://STAGING_BUCKET_NAME/imagebuilder.yaml.

Para enviar una compilación de Cloud Build que ejecute la canalización de Image Builder con la biblioteca cliente de Go para Cloud Build, usa el siguiente ejemplo:

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
}

Verifica y supervisa la compilación

Para hacer un seguimiento del progreso de tu canalización de compilación, completa los siguientes pasos:

  1. En la consola de Google Cloud , ve a la página de Cloud Build.

    Ir a Cloud Build

  2. En el menú de navegación, haz clic en Historial para ver los trabajos activos o completados.

  3. En la lista Compilaciones, haz clic en el ID de compilación de tu compilación para inspeccionar los registros de ejecución del contenedor. En los registros, se muestran los pasos que se realizan dentro de la VM de trabajador, como las actualizaciones de paquetes del sistema o los comandos de shell personalizados, seguidos de los resultados de las pruebas de validación de la VM de prueba y el registro de salida final.

¿Qué sigue?