使用 gcloud 或 Terraform 建立自訂 OS 映像檔管道

使用 Google Cloud CLI 或 Terraform,以程式輔助方式設定及提交 Image Builder 管道。以程式輔助方式設定管道,可在宣告式設定檔中定義基礎架構設定、基礎 OS 映像檔、自訂動作和驗證測試。

事前準備

  • 請按照「準備環境」一文中的步驟完成環境設定。
  • 如要使用 Terraform 部署管道,或從存放區自動建構,請使用 Cloud Build 存放區 (2nd gen) 或 Developer Connect 連線連結,連結 GitHub、GitLab 或 Bitbucket 存放區。
  • 如果您打算使用 Terraform,請安裝 Terraform CLI 1.3 以上版本。
  • 如果尚未設定驗證,請先完成設定。 驗證可確認您的身分,以便存取 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. 如果您使用本機殼層,請為使用者帳戶建立本機驗證憑證:

      gcloud auth application-default login

      如果您使用 Cloud Shell,則不需要執行這項操作。

      如果系統傳回驗證錯誤,且您使用外部識別資訊提供者 (IdP),請確認您已 使用聯合身分登入 gcloud CLI

    詳情請參閱 這篇文章,瞭解如何設定本機開發環境的驗證機制。

必要的角色

如要取得使用 Google Cloud CLI 或 Terraform 建立及提交映像檔自訂管道所需的權限,請要求管理員在專案中授予您下列 IAM 角色:

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

建立設定檔

如要使用 gcloud CLI 或 Terraform 設定管道,請建立兩個設定檔:

  • imagebuilder.yaml:定義映像檔的自訂方式,包括基礎 OS 映像檔、工作站 VM 基礎架構設定、目標映像檔輸出詳細資料,以及依序執行的自訂步驟,例如執行 Shell 指令碼、傳輸檔案或重新啟動。
  • cloudbuild.yaml: 協調 Cloud Build 中的建構程序步驟,包括剖析、驗證、建構、測試及發布自訂 OS 映像檔。

建立映像檔設定檔

如要指定映像檔設定,請在本地目錄中建立名為 imagebuilder.yaml 的檔案。如需所有支援的架構欄位和自訂動作完整清單,請參閱自訂食譜架構支援的自訂動作

下列範例 imagebuilder.yaml 檔案會設定管道,在您指定的區域和可用區中,使用 e2-standard-4 worker VM 建構自訂的 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 支援的區域
    • 工作站 VM ZONE 必須位於您指定的 REGION 內。
    • 為盡量縮短網路延遲時間並避免產生跨區域輸出費用,請確保工作站 VM 區域、Cloud Storage 暫存值區、Artifact Registry 存放區和目標映像檔儲存位置位於相同區域。
  • ZONE:位於指定 REGION 內的區域,例如 us-east1-beurope-west1-b

建立自動化調度管理工具建構檔案

在同一個目錄中建立名為 cloudbuild.yaml 的檔案。這個檔案會呼叫 Image Builder 容器步驟,以建構、驗證及發布自訂 OS 映像檔。

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 bucket,用做暫時的暫存工作區。如果沒有 bucket,可以執行 gcloud storage buckets create gs://STAGING_BUCKET_NAME 建立 bucket。如果您使用 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 提交建構作業,或使用 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

如要佈建基礎架構,以便自動建構及驗證自訂 OS 映像檔,可以使用 Terraform。這項 Terraform 設定會完成下列工作:

  • 啟用必要的 Google Cloud API。
  • 建立專屬的 Cloud Storage bucket (workdir_bucket),用於儲存臨時記錄和建構構件。
  • 設定連結至 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 服務帳戶」中設定的建構服務帳戶電子郵件地址。
    • LOCATIONCONNECTIONREPO_NAME:您的 Developer Connect 主機區域、連線名稱和存放區連結,這些都是在「連結存放區」中設定。
    • CLOUDBUILD_YAML_PATH:本機目錄中 cloudbuild.yaml 檔案的相對路徑。您不需要將 cloudbuild.yaml 簽入 Git 存放區。
    • RECIPE_PATHimagebuilder.yaml自訂食譜檔案的相對路徑,已簽入 Git 存放區。
    • REPOSITORY_NAME:在「設定 Artifact Registry」中建立的現有一般 Artifact Registry 存放區。

    將下列預留位置替換為 Terraform 建立的資源:

    • REGION:Terraform 佈建 Cloud Storage 暫存 bucket 和 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 存放區內建立的套件名稱。這個套件會儲存已發布的 OS 映像檔版本,例如 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

驗證及監控建構作業

如要追蹤建構管道的進度,請完成下列步驟:

  1. 前往 Google Cloud 控制台的「Cloud Build」頁面。

    前往 Cloud Build

  2. 在導覽選單中,按一下「記錄」即可查看進行中或已完成的工作。

  3. 在「建構」清單中,按一下建構項目的「建構 ID」,即可檢查容器執行記錄。記錄檔會顯示工作站 VM 中執行的步驟,例如系統套件更新或自訂殼層指令,接著是測試 VM 的驗證測試結果,以及最終輸出註冊。

後續步驟