使用 gcloud 或 Terraform 创建自定义操作系统映像流水线

使用 Google Cloud CLI 或 Terraform 以编程方式配置和提交 Image Builder 流水线。以程序化方式配置流水线,可让您在声明性配置文件中定义基础设施设置、基本操作系统映像、自定义操作和验证测试。

准备工作

  • 完成准备环境中的环境设置步骤。
  • 如果您打算使用 Terraform 部署流水线或自动执行代码库中的构建,请使用 Cloud Build 代码库 (2nd gen) 或 Developer Connect 连接链接连接您的 GitHub、GitLab 或 Bitbucket 代码库。
  • 如果您计划使用 Terraform,请安装 1.3 版或更高版本的 Terraform CLI
  • 如果您尚未设置身份验证,请进行设置。身份验证用于验证您的身份,以便访问 Google Cloud 服务和 API。如需从本地开发环境运行代码或示例,您可以通过选择以下选项之一向 Compute Engine 进行身份验证:

    选择标签页以了解您打算如何使用本页面上的示例:

    gcloud

    1. 安装 Google Cloud CLI。 安装完成后,运行以下命令来初始化 Google Cloud CLI:

      gcloud init

      如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

  • 设置默认区域和可用区
  • Terraform

    如需在本地开发环境中使用本页面上的 Terraform 示例,请安装并初始化 gcloud CLI,然后使用您的用户凭证设置应用默认凭据。

    1. 安装 Google Cloud CLI。

    2. 如果您使用的是外部身份提供方 (IdP),则必须先使用联合身份登录 gcloud CLI

    3. 如果您使用的是本地 shell,请为您的用户账号创建本地身份验证凭证:

      gcloud auth application-default login

      如果您使用的是 Cloud Shell,则无需执行此操作。

      如果系统返回身份验证错误,并且您使用的是外部身份提供方 (IdP),请确认您已 使用联合身份登录 gcloud CLI

    如需了解详情,请参阅 为本地开发环境设置身份验证

所需的角色

如需获得使用 Google Cloud CLI 或 Terraform 创建和提交映像自定义流水线所需的权限,请让管理员向您授予项目的以下 IAM 角色:

如需详细了解如何授予角色,请参阅管理对项目、文件夹和组织的访问权限

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

创建配置文件

如需使用 gcloud CLI 或 Terraform 配置流水线,请创建两个配置文件:

  • imagebuilder.yaml:定义映像的自定义配方,包括基础操作系统映像、工作器虚拟机基础设施设置、目标映像输出详细信息,以及顺序自定义步骤,例如运行 shell 脚本、传输文件或执行重新启动。
  • cloudbuild.yaml:编排 Cloud Build 中的构建流程步骤,包括解析、验证、构建、测试和发布自定义操作系统映像。

创建映像配置文件

如需指定映像配置,请在本地目录中创建一个名为 imagebuilder.yaml 的文件。如需查看所有受支持的架构字段和自定义操作的完整列表,请参阅自定义配方架构支持的自定义操作

以下示例 imagebuilder.yaml 文件配置了一个流水线,该流水线使用指定区域和可用区中的 e2-standard-4 工作器虚拟机机构建自定义 Ubuntu 22.04 LTS 映像,并执行系统软件包更新。

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"

替换以下占位值:

  • PROJECT_ID:您的项目 ID。
  • REGION:目标映像存储位置,例如 us-east1europe-west1。确保您满足以下地区和时区要求:
    • Image Builder 仅在 Cloud Build 可用的区域受支持。
    • 工作器虚拟机 ZONE 必须位于您指定的 REGION 内。
    • 为最大限度地减少网络延迟并避免产生跨区域出站流量费用,请确保您的工作器虚拟机可用区、Cloud Storage 暂存存储桶、Artifact Registry 代码库和目标映像存储位置位于同一区域。
  • ZONE:位于您指定的 REGION 内的可用区,例如 us-east1-beurope-west1-b

创建编排器 build 文件

在同一目录中创建一个名为 cloudbuild.yaml 的文件。此文件调用 Image Builder 容器步骤来构建、验证和发布自定义操作系统映像。

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'

替换以下占位值:

  • STAGING_BUCKET_NAME:您项目中的现有 Cloud Storage 存储桶,用作临时过渡工作区。如果您没有存储桶,可以运行 gcloud storage buckets create gs://STAGING_BUCKET_NAME 来创建一个。如果您使用 Terraform 部署流水线,Terraform 会自动创建此存储桶。
  • PROJECT_ID:您的 Google Cloud 项目 ID。
  • REGION:您的 Artifact Registry 代码库的 Google Cloud 区域,例如 us-east1europe-west1
  • SERVICE_ACCOUNT_EMAIL:您配置了所需 IAM 权限的服务账号的电子邮件地址。
  • REPOSITORYPACKAGE:在 Artifact Registry 中创建的目标代码库和软件包名称。如需设置 Artifact Registry 注册表,请参阅配置 Artifact Registry

创建并提交构建流水线

如需运行映像自定义流水线,请使用 gcloud CLI 提交 build,或使用 Terraform 部署流水线。选择以下某个标签页:

gcloud

如需部署并运行映像自定义流水线,请从包含这两个配置文件的本地终端目录中运行 gcloud builds submit 命令

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

替换以下内容:

  • PROJECT_ID:您的 Google Cloud 项目 ID。
  • REGION:运行映像自定义流水线作业的 Google Cloud 区域。
  • SERVICE_ACCOUNT_EMAIL:已配置所需 IAM 权限的服务账号的电子邮件地址。

此命令会上传自定义工作区、注册 Cloud Build 执行并启动编排容器。

Terraform

如需预配自动构建和验证自定义操作系统映像所需的基础设施,您可以使用 Terraform。此 Terraform 配置可完成以下任务:

  • 启用必需的 Google Cloud API。
  • 创建专用 Cloud Storage 存储桶 (workdir_bucket) 以存储临时日志和 build 工件。
  • 配置与 Developer Connect GitHub 代码库连接关联的 Cloud Build 触发器 (image_builder_trigger)。

创建 Terraform 配置文件

如需使用 Terraform 整理和部署流水线基础架构,请完成以下步骤:

  1. 在本地工作站或 CI/CD 环境中创建一个专用目录(与应用代码库分开),然后切换到该目录:

    mkdir terraform-image-builder && cd terraform-image-builder
    
  2. 在此目录中,创建以下五个 Terraform 配置文件:

    • terraform.tfvars:为特定于项目的变量设置值。
    • main.tf:在您的 Google Cloud 项目中预配资源,包括启用必需的 API、创建 Cloud Storage 暂存存储桶 (workdir_bucket) 和部署 Cloud Build 构建触发器 (image_builder_trigger)。
    • outputs.tf:定义部署后在终端中显示的输出值,例如触发器 ID 和临时存储桶名称。
    • providers.tf:指定所需的 Terraform 版本 (>= 1.3) 并配置 Google Cloud 提供程序 (hashicorp/google)。
    • variables.tf:定义部署的输入变量、默认值和验证规则。

    选择以下标签页之一,查看每个文件的配置并将其复制到本地目录:

    terraform.tfvars

    此文件为声明的变量指定了环境的参数值:

    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"
    

    替换现有资源的以下占位符:

    • PROJECT_ID:您现有Google Cloud 项目的 ID。
    • SERVICE_ACCOUNT_EMAIL:在配置 Image Builder 服务账号中配置的 build 服务账号的电子邮件地址。
    • LOCATIONCONNECTIONREPO_NAME:在关联代码库中配置的 Developer Connect 主机区域、连接名称和代码库链接。
    • CLOUDBUILD_YAML_PATH:本地目录中 cloudbuild.yaml 文件的相对路径。您无需将 cloudbuild.yaml 签入 Git 代码库。
    • RECIPE_PATH:签入到 Git 代码库中的 imagebuilder.yaml 自定义配方文件的相对路径。
    • REPOSITORY_NAME:在配置 Artifact Registry 中创建的现有通用 Artifact Registry 代码库。

    替换以下占位符,以用于 Terraform 创建的资源:

    • REGION:Terraform 用于预配 Cloud Storage 临时存储桶和 Cloud Build 构建触发器的目标 Google Cloud区域,例如 us-central1
    • TRIGGER_NAME:由 Terraform 创建的新 Cloud Build 代码库触发器的名称,例如 git-push-os-builder
    • LIFECYCLE_DAYS:在 Terraform 创建的 Cloud Storage 暂存存储桶中的中间制品被自动删除之前,保留期限(以天为单位),例如 30
    • PACKAGE_NAME:您希望 Terraform 用于在 Artifact Registry 代码库中创建的软件包的名称。此软件包存储已发布的操作系统映像版本,例如 ubuntu-custom

    main.tf

    此文件声明了部署的核心基础架构资源和数据源:

    # 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

    此文件定义了部署后返回到终端的输出属性:

    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

    此文件配置了所需的 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

    此文件声明了所有必需和可选的输入变量以及验证规则:

    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. 如需部署配置,请在包含 Terraform 文件的目录中运行以下命令:

    1. 初始化目录:
      terraform init
    2. 验证语法:
      terraform validate
    3. 预览部署:
      terraform plan
    4. 应用配置:
      terraform apply

验证和监控 build

如需跟踪 build 流水线的进度,请完成以下步骤:

  1. 在 Google Cloud 控制台中,前往 Cloud Build 页面。

    转至 Cloud Build

  2. 在导航菜单中,点击历史记录以查看有效或已完成的作业。

  3. 构建列表中,点击相应构建的构建 ID 以检查容器运行日志。日志会显示工作器虚拟机中执行的步骤(例如系统软件包更新或自定义 shell 命令)、测试虚拟机的验证测试结果以及最终输出注册。

后续步骤