创建自定义映像


您可以从源磁盘、映像、快照或 Cloud Storage 中存储的映像创建自定义映像,然后使用这些映像创建虚拟机 (VM) 实例。如果您创建并修改了一个永久性启动磁盘或特定映像,使其达到了特定的状态并需要保存该状态以用于创建虚拟机,则最适合使用这种方式。

此外,您也可以使用虚拟磁盘导入工具从现有系统将启动磁盘映像导入 Compute Engine,并将其添加到您的自定义映像列表中。

准备工作

  • 阅读映像文档。
  • 如果您尚未设置身份验证,请进行设置。身份验证是通过其进行身份验证以访问 Google Cloud 服务和 API 的过程。如需从本地开发环境运行代码或示例,您可以选择以下任一选项向 Compute Engine 进行身份验证:

    Select the tab for how you plan to use the samples on this page:

    Console

    When you use the Google Cloud console to access Google Cloud services and APIs, you don't need to set up authentication.

    gcloud

    1. Install the Google Cloud CLI, then initialize it by running the following command:

      gcloud init
    2. Set a default region and zone.
    3. Go

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

      1. Install the Google Cloud CLI.
      2. To initialize the gcloud CLI, run the following command:

        gcloud init
      3. If you're using a local shell, then create local authentication credentials for your user account:

        gcloud auth application-default login

        You don't need to do this if you're using Cloud Shell.

      如需了解详情,请参阅 Set up authentication for a local development environment

      Java

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

      1. Install the Google Cloud CLI.
      2. To initialize the gcloud CLI, run the following command:

        gcloud init
      3. If you're using a local shell, then create local authentication credentials for your user account:

        gcloud auth application-default login

        You don't need to do this if you're using Cloud Shell.

      如需了解详情,请参阅 Set up authentication for a local development environment

      Node.js

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

      1. Install the Google Cloud CLI.
      2. To initialize the gcloud CLI, run the following command:

        gcloud init
      3. If you're using a local shell, then create local authentication credentials for your user account:

        gcloud auth application-default login

        You don't need to do this if you're using Cloud Shell.

      如需了解详情,请参阅 Set up authentication for a local development environment

      Python

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

      1. Install the Google Cloud CLI.
      2. To initialize the gcloud CLI, run the following command:

        gcloud init
      3. If you're using a local shell, then create local authentication credentials for your user account:

        gcloud auth application-default login

        You don't need to do this if you're using Cloud Shell.

      如需了解详情,请参阅 Set up authentication for a local development environment

      REST

      如需在本地开发环境中使用本页面上的 REST API 示例,请使用您提供给 gcloud CLI 的凭据。

        Install the Google Cloud CLI, then initialize it by running the following command:

        gcloud init

      如需了解详情,请参阅 Google Cloud 身份验证文档中的使用 REST 时进行身份验证

创建自定义映像

本部分介绍如何在 Linux 虚拟机上创建自定义映像。 如需了解如何创建 Windows 映像,请参阅创建 Windows 映像

选择映像存储位置

创建自定义映像时,您可以为该映像指定除了双区域位置之外的 Cloud Storage 位置。指定映像存储位置后,您可以通过确保跨区域实现冗余来满足数据存储区域的法规和合规性要求以及高可用性需求。如需创建、修改和删除存储在 Cloud Storage 中的映像,您必须拥有 roles/compute.storageAdmin

存储位置功能是可选的。如果您未选择位置,则 Compute Engine 会将映像存储在最靠近映像来源的多区域位置。例如,在您通过位于 us-central1 的来源磁盘创建映像时,如果您没有为自定义映像指定位置,则 Compute Engine 会将映像存储在 us 多区域位置。

如果映像在您创建虚拟机的区域中未提供,则 Compute Engine 会在您首次创建虚拟机时在该区域中缓存映像。

如需查看存储映像的位置,请通过 gcloud compute 使用 images describe 命令:

gcloud compute images describe IMAGE_NAME \
    --project=PROJECT_ID

请替换以下内容:

  • IMAGE_NAME:您的映像的名称。

  • PROJECT_ID:映像所属的项目 ID。

此功能发布之前的所有现有映像都保留在原来的位置,唯一的变化是您可以查看所有映像的位置。如果要移动现有映像,您必须在新位置重新创建该映像。

为影响准备虚拟机

即使磁盘挂接到正在运行的虚拟机,您也可以从磁盘创建映像。但是,如果将虚拟机置于更易于捕获映像的状态,则映像会更加可靠。本部分介绍如何为映像准备启动磁盘。

尽可能减少永久性磁盘的数据写入

使用以下过程之一减少磁盘写入:

  • 停止虚拟机,以便其关闭并停止将任何数据写入永久性磁盘。

  • 如果在创建映像之前无法停止虚拟机,请尽量减少磁盘的写入量并同步文件系统。为了尽可能减少对永久性磁盘的写入,请按照以下步骤操作:

    1. 暂停将数据写入该永久性磁盘的应用或操作系统进程。
    2. 如有必要,刷新写入磁盘的应用。例如,MySQL 设有 FLUSH 语句。其他应用可能有类似的进程。
    3. 阻止应用写入永久性磁盘。
    4. 运行 sudo sync

停用磁盘的自动删除选项

默认情况下,启动磁盘上会启用自动删除选项。在通过磁盘创建映像之前,请先停用自动删除,以确保在您删除虚拟机时不会自动删除磁盘。

请使用以下方法之一为磁盘停用自动删除功能。

控制台

  1. 在 Google Cloud 控制台中,转到虚拟机实例页面。

    转到“虚拟机实例”页面

  2. 点击您用作映像创建来源的虚拟机的名称。

    此时将显示虚拟机实例详情页面。

  3. 点击修改

  4. 启动磁盘部分,确保为删除规则选中保留磁盘选项。

  5. 点击保存

gcloud

在 Google Cloud CLI 中,使用 gcloud compute instances set-disk-auto-delete 命令停用磁盘的自动删除选项。

gcloud compute instances set-disk-auto-delete VM_NAME \
    --no-auto-delete \
    --disk=SOURCE_DISK

替换以下内容:

  • VM_NAME:虚拟机实例的名称。
  • SOURCE_DISK:要通过其创建映像的磁盘的名称。

Go

Go

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Go 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Go API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
)

// setDiskAutodelete sets the autodelete flag of a disk to given value.
func setDiskAutoDelete(
	w io.Writer,
	projectID, zone, instanceName, diskName string, autoDelete bool,
) error {
	// projectID := "your_project_id"
	// zone := "us-west3-b"
	// instanceName := "your_instance_name"
	// diskName := "your_disk_name"
	// autoDelete := true

	ctx := context.Background()
	instancesClient, err := compute.NewInstancesRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewInstancesRESTClient: %w", err)
	}
	defer instancesClient.Close()

	getInstanceReq := &computepb.GetInstanceRequest{
		Project:  projectID,
		Zone:     zone,
		Instance: instanceName,
	}

	instance, err := instancesClient.Get(ctx, getInstanceReq)
	if err != nil {
		return fmt.Errorf("unable to get instance: %w", err)
	}

	diskExists := false

	for _, disk := range instance.GetDisks() {
		if disk.GetDeviceName() == diskName {
			diskExists = true
			break
		}
	}

	if !diskExists {
		return fmt.Errorf(
			"instance %s doesn't have a disk named %s attached",
			instanceName,
			diskName,
		)
	}

	req := &computepb.SetDiskAutoDeleteInstanceRequest{
		Project:    projectID,
		Zone:       zone,
		Instance:   instanceName,
		DeviceName: diskName,
		AutoDelete: autoDelete,
	}

	op, err := instancesClient.SetDiskAutoDelete(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to set disk autodelete field: %w", err)
	}

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "disk autoDelete field updated.\n")

	return nil
}

Java

Java

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Java API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


import com.google.cloud.compute.v1.Instance;
import com.google.cloud.compute.v1.InstancesClient;
import com.google.cloud.compute.v1.Operation;
import com.google.cloud.compute.v1.SetDiskAutoDeleteInstanceRequest;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class SetDiskAutodelete {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.

    // Project ID or project number of the Cloud project you want to use.
    String projectId = "YOUR_PROJECT_ID";

    // The zone of the disk that you want to modify.
    String zone = "europe-central2-b";

    // Name of the instance the disk is attached to.
    String instanceName = "YOUR_INSTANCE_NAME";

    // The name of the disk for which you want to modify the autodelete flag.
    String diskName = "YOUR_DISK_NAME";

    // The new value of the autodelete flag.
    boolean autoDelete = true;

    setDiskAutodelete(projectId, zone, instanceName, diskName, autoDelete);
  }

  // Sets the autodelete flag of a disk to given value.
  public static void setDiskAutodelete(String projectId, String zone, String instanceName,
      String diskName, boolean autoDelete)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {

    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `instancesClient.close()` method on the client to safely
    // clean up any remaining background resources.
    try (InstancesClient instancesClient = InstancesClient.create()) {

      // Retrieve the instance given by the instanceName.
      Instance instance = instancesClient.get(projectId, zone, instanceName);

      // Check if the instance contains a disk that matches the given diskName.
      boolean diskNameMatch = instance.getDisksList()
          .stream()
          .anyMatch(disk -> disk.getDeviceName().equals(diskName));

      if (!diskNameMatch) {
        throw new Error(
            String.format("Instance %s doesn't have a disk named %s attached", instanceName,
                diskName));
      }

      // Create the request object.
      SetDiskAutoDeleteInstanceRequest request = SetDiskAutoDeleteInstanceRequest.newBuilder()
          .setProject(projectId)
          .setZone(zone)
          .setInstance(instanceName)
          .setDeviceName(diskName)
          // Update the autodelete property.
          .setAutoDelete(autoDelete)
          .build();

      // Wait for the update instance operation to complete.
      Operation response = instancesClient.setDiskAutoDeleteAsync(request)
          .get(3, TimeUnit.MINUTES);

      if (response.hasError()) {
        System.out.println("Failed to update Disk autodelete field!" + response);
        return;
      }
      System.out.println(
          "Disk autodelete field updated. Operation Status: " + response.getStatus());
    }
  }
}

Node.js

Node.js

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Node.js 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Node.js API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

/**
 * TODO(developer): Uncomment and replace these variables before running the sample.
 */
// const projectId = 'YOUR_PROJECT_ID';
// const zone = 'europe-central2-b';
// const instanceName = 'YOUR_INSTANCE_NAME';
// const diskName = 'YOUR_DISK_NAME';
// const autoDelete = true;

const compute = require('@google-cloud/compute');

async function setDiskAutodelete() {
  const instancesClient = new compute.InstancesClient();

  const [instance] = await instancesClient.get({
    project: projectId,
    zone,
    instance: instanceName,
  });

  if (!instance.disks.some(disk => disk.deviceName === diskName)) {
    throw new Error(
      `Instance ${instanceName} doesn't have a disk named ${diskName} attached.`
    );
  }

  const [response] = await instancesClient.setDiskAutoDelete({
    project: projectId,
    zone,
    instance: instanceName,
    deviceName: diskName,
    autoDelete,
  });
  let operation = response.latestResponse;
  const operationsClient = new compute.ZoneOperationsClient();

  // Wait for the update instance operation to complete.
  while (operation.status !== 'DONE') {
    [operation] = await operationsClient.wait({
      operation: operation.name,
      project: projectId,
      zone: operation.zone.split('/').pop(),
    });
  }

  console.log('Disk autoDelete field updated.');
}

setDiskAutodelete();

Python

Python

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Python 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Python API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

from __future__ import annotations

import sys
from typing import Any

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def set_disk_autodelete(
    project_id: str, zone: str, instance_name: str, disk_name: str, autodelete: bool
) -> None:
    """
    Set the autodelete flag of a disk to given value.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone in which is the disk you want to modify.
        instance_name: name of the instance the disk is attached to.
        disk_name: the name of the disk which flag you want to modify.
        autodelete: the new value of the autodelete flag.
    """
    instance_client = compute_v1.InstancesClient()
    instance = instance_client.get(
        project=project_id, zone=zone, instance=instance_name
    )

    for disk in instance.disks:
        if disk.device_name == disk_name:
            break
    else:
        raise RuntimeError(
            f"Instance {instance_name} doesn't have a disk named {disk_name} attached."
        )

    disk.auto_delete = autodelete

    operation = instance_client.update(
        project=project_id,
        zone=zone,
        instance=instance_name,
        instance_resource=instance,
    )

    wait_for_extended_operation(operation, "disk update")

REST

如需设置磁盘的自动删除选项,请向 instances.setDiskAutoDelete 方法发出 POST 请求。

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/VM_NAME/setDiskAutoDelete?autoDelete=false&deviceName=SOURCE_DISK

替换以下内容:

  • PROJECT_ID:来源虚拟机所属的项目 ID。
  • ZONE:来源虚拟机所在的可用区。
  • VM_NAME:来源虚拟机的名称。
  • SOURCE_DISK:要通过其创建映像的磁盘的设备名称。

准备好虚拟机后,创建映像

创建映像

您可从以下来源创建磁盘映像:

  • 永久性磁盘,即使该磁盘已挂接到虚拟机。
  • 永久性磁盘的快照
  • 您项目中的另一个映像
  • 另一个项目分享的映像
  • Cloud Storage 中的压缩 RAW 映像

您可以每 10 分钟创建磁盘映像一次。如果您想要发出大量创建磁盘映像的请求,可以在 60 分钟内发出最多 6 个请求。如需了解详情,请参阅快照频率限制

控制台

  1. 在 Google Cloud 控制台中,前往创建映像页面。

    转到“创建映像”

  2. 指定映像的名称

  3. 指定您要根据其创建映像的来源。可以是 Cloud Storage 中的永久性磁盘、快照、其他映像或 disk.raw 文件。

  4. 如果您从挂接到正在运行的虚拟机的磁盘来创建映像,请勾选保持实例运行,以确认您要在虚拟机保持运行的状态下创建映像。您可以在创建映像之前准备虚拟机

  5. 基于来源磁盘位置(默认)下拉列表中,指定映像的存储位置。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

  6. 可选:指定您的映像的属性。

    • 系列:这个新映像所属的映像系列
    • 说明:自定义映像的说明。
    • 标签:用于将资源组织在一起的标签
  7. 指定加密密钥。您可以选择 Google 拥有且 Google 管理的密钥、Cloud Key Management Service (Cloud KMS) 密钥或客户提供的加密密钥 (CSEK)。如果未指定加密密钥,则系统会使用 Google 拥有且 Google 管理的密钥对映像进行加密。

  8. 点击创建来创建映像。

gcloud

在 Google Cloud CLI 中,使用 gcloud compute images create 命令创建自定义映像。

根据来源磁盘创建映像

--force 标志是一个可选标志,可让您根据正在运行的实例创建映像。默认情况下,您无法根据正在运行的实例创建映像。只有当您确定要在实例正在运行的情况下创建映像时,才可指定此标志。

gcloud compute images create IMAGE_NAME \
    --source-disk=SOURCE_DISK \
    --source-disk-zone=ZONE \
    [--family=IMAGE_FAMILY] \
    [--storage-location=LOCATION] \
    [--force]

请替换以下内容:

  • IMAGE_NAME:新映像的名称
  • SOURCE_DISK:要从中创建映像的磁盘。
  • ZONE:磁盘所在的可用区。
  • IMAGE_FAMILY:可选:用于指定此映像所属的映像系列的标志。
  • LOCATION:可选:用于指定在其中存储此映像的单区域或多区域的标志。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最接近映像来源位置的多区域中。

根据来源映像创建映像

gcloud compute images create IMAGE_NAME \
  --source-image=SOURCE_IMAGE \
  [--source-image-project=IMAGE_PROJECT] \
  [--family=IMAGE_FAMILY] \
  [--storage-location=LOCATION]

请替换以下内容:

  • IMAGE_NAME:新映像的名称。
  • SOURCE_IMAGE:要从中创建新映像的映像。
  • IMAGE_PROJECT:可选:来源映像所在的项目。如果您想复制另一个项目中的映像,可使用此参数。
  • IMAGE_FAMILY:可选:此新映像所属的映像系列
  • LOCATION:可选:允许您指定在其中存储映像的单区域或多区域。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

根据快照创建映像

gcloud compute images create IMAGE_NAME \
    --source-snapshot=SOURCE_SNAPSHOT \
    [--storage-location=LOCATION]

请替换以下内容:

  • IMAGE_NAME:新映像的名称
  • SOURCE_SNAPSHOT:要根据其创建映像的快照
  • LOCATION:可选:用于指定在其中存储此映像的单区域或多区域的标志。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最接近映像来源位置的多区域中。

查看映像位置

使用 gcloud compute images describe 命令查看映像位置。

gcloud compute images describe IMAGE_NAME

IMAGE_NAME 替换为您要查看的映像的名称。

Go

Go

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Go 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Go API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
)

// Creates a disk image from an existing disk
func createImageFromDisk(
	w io.Writer,
	projectID, zone, sourceDiskName, imageName string,
	storageLocations []string,
	forceCreate bool,
) error {
	// projectID := "your_project_id"
	// zone := "us-central1-a"
	// sourceDiskName := "your_disk_name"
	// imageName := "my_image"
	// // If storageLocations empty, automatically selects the closest one to the source
	// storageLocations = []string{}
	// // If forceCreate is set to `true`, proceeds even if the disk is attached to
	// // a running instance. This may compromise integrity of the image!
	// forceCreate = false

	ctx := context.Background()
	disksClient, err := compute.NewDisksRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewDisksRESTClient: %w", err)
	}
	defer disksClient.Close()
	imagesClient, err := compute.NewImagesRESTClient(ctx)
	if err != nil {
		return fmt.Errorf("NewImagesRESTClient: %w", err)
	}
	defer imagesClient.Close()

	// Get the source disk
	source_req := &computepb.GetDiskRequest{
		Disk:    sourceDiskName,
		Project: projectID,
		Zone:    zone,
	}

	disk, err := disksClient.Get(ctx, source_req)
	if err != nil {
		return fmt.Errorf("unable to get source disk: %w", err)
	}

	// Create the image
	req := computepb.InsertImageRequest{
		ForceCreate: &forceCreate,
		ImageResource: &computepb.Image{
			Name:             &imageName,
			SourceDisk:       disk.SelfLink,
			StorageLocations: storageLocations,
		},
		Project: projectID,
	}

	op, err := imagesClient.Insert(ctx, &req)

	if err = op.Wait(ctx); err != nil {
		return fmt.Errorf("unable to wait for the operation: %w", err)
	}

	fmt.Fprintf(w, "Disk image %s created\n", imageName)

	return nil
}

Java

Java

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Java API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


import com.google.cloud.compute.v1.Disk;
import com.google.cloud.compute.v1.DisksClient;
import com.google.cloud.compute.v1.Image;
import com.google.cloud.compute.v1.ImagesClient;
import com.google.cloud.compute.v1.InsertImageRequest;
import com.google.cloud.compute.v1.Instance;
import com.google.cloud.compute.v1.InstancesClient;
import com.google.cloud.compute.v1.Operation;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreateImage {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.

    // Project ID or project number of the Cloud project you use.
    String project = "your-project-id";
    // Zone of the disk you copy from.
    String zone = "europe-central2-b";
    // Name of the source disk you copy from.
    String sourceDiskName = "source-disk-name";
    // Name of the image you want to create.
    String imageName = "your-image-name";
    // Storage location for the image. If the value is undefined,
    // function will store the image in the multi-region closest to your image's source location.
    String storageLocation = "eu";
    // Create the image even if the source disk is attached to a running instance.
    boolean forceCreate = false;

    createImage(project, zone, sourceDiskName, imageName, storageLocation, forceCreate);
  }

  // Creates a new disk image from the specified source disk.
  public static void createImage(String project, String zone, String sourceDiskName,
      String imageName, String storageLocation, boolean forceCreate)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests. After completing all of your requests, call
    // the `client.close()` method on the client to safely
    // clean up any remaining background resources.
    try (ImagesClient imagesClient = ImagesClient.create();
        InstancesClient instancesClient = InstancesClient.create();
        DisksClient disksClient = DisksClient.create()) {

      Disk disk = disksClient.get(project, zone, sourceDiskName);

      // Getting instances where source disk is attached.
      for (String fullInstanceName : disk.getUsersList()) {
        Map<String, String> instanceInfo = parseInstanceName(fullInstanceName);
        Instance instance = instancesClient.get(instanceInfo.get("instanceProjectId"),
            instanceInfo.get("instanceZone"), instanceInfo.get("instanceName"));

        // Сheck whether the instances are stopped.
        if (!Arrays.asList("TERMINATED", "STOPPED").contains(instance.getStatus())
            && !forceCreate) {
          throw new IllegalStateException(
              String.format(
                  "Instance %s should be stopped. For Windows instances please stop the instance "
                      + "using GCESysprep command. For Linux instances just shut it down normally."
                      + " You can suppress this error and create an image of the disk by setting "
                      + "'forceCreate' parameter to true (not recommended). "
                      + "More information here: "
                      + "* https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api"
                      + "* https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#prepare_instance_for_image",
                  instanceInfo.get("instanceName")));
        }
      }

      if (forceCreate) {
        System.out.println(
            "Warning: forceCreate option compromise the integrity of your image. "
                + "Stop the instance before you create the image if possible.");
      }

      // Create Image.
      Image image = Image.newBuilder()
          .setName(imageName)
          .setSourceDisk(String.format("/zones/%s/disks/%s", zone, sourceDiskName))
          .addStorageLocations(storageLocation.isEmpty() ? "" : storageLocation)
          .build();

      InsertImageRequest insertImageRequest = InsertImageRequest.newBuilder()
          .setProject(project)
          .setForceCreate(forceCreate)
          .setImageResource(image)
          .build();

      Operation response = imagesClient.insertAsync(insertImageRequest).get(5, TimeUnit.MINUTES);

      if (response.hasError()) {
        System.out.println("Image creation failed ! ! " + response);
        return;
      }

      System.out.println("Image created.");
    }
  }


  public static Map<String, String> parseInstanceName(String name) {
    String[] parsedName = name.split("/");
    int splitLength = parsedName.length;

    if (splitLength < 5) {
      throw new IllegalArgumentException(
          "Provide correct instance name in the following format: "
              + "https://www.googleapis.com/compute/v1/projects/PROJECT/zones/ZONE/instances/INSTANCE_NAME");
    }

    return new HashMap<>() {
      {
        put("instanceName", parsedName[splitLength - 1]);
        put("instanceZone", parsedName[splitLength - 3]);
        put("instanceProjectId", parsedName[splitLength - 5]);
      }
    };
  }

}

Python

Python

试用此示例之前,请按照《Compute Engine 快速入门:使用客户端库》中的 Python 设置说明进行操作。 如需了解详情,请参阅 Compute Engine Python API 参考文档

如需向 Compute Engine 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证

from __future__ import annotations

import sys
from typing import Any
import warnings

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


STOPPED_MACHINE_STATUS = (
    compute_v1.Instance.Status.TERMINATED.name,
    compute_v1.Instance.Status.STOPPED.name,
)


def create_image_from_disk(
    project_id: str,
    zone: str,
    source_disk_name: str,
    image_name: str,
    storage_location: str | None = None,
    force_create: bool = False,
) -> compute_v1.Image:
    """
    Creates a new disk image.

    Args:
        project_id: project ID or project number of the Cloud project you use.
        zone: zone of the disk you copy from.
        source_disk_name: name of the source disk you copy from.
        image_name: name of the image you want to create.
        storage_location: storage location for the image. If the value is undefined,
            function will store the image in the multi-region closest to your image's
            source location.
        force_create: create the image even if the source disk is attached to a
            running instance.

    Returns:
        An Image object.
    """
    image_client = compute_v1.ImagesClient()
    disk_client = compute_v1.DisksClient()
    instance_client = compute_v1.InstancesClient()

    # Get source disk
    disk = disk_client.get(project=project_id, zone=zone, disk=source_disk_name)

    for disk_user in disk.users:
        instance_name = disk_user.split("/")[-1]
        instance = instance_client.get(
            project=project_id, zone=zone, instance=instance_name
        )
        if instance.status in STOPPED_MACHINE_STATUS:
            continue
        if not force_create:
            raise RuntimeError(
                f"Instance {disk_user} should be stopped. For Windows instances please "
                f"stop the instance using `GCESysprep` command. For Linux instances just "
                f"shut it down normally. You can supress this error and create an image of"
                f"the disk by setting `force_create` parameter to true (not recommended). \n"
                f"More information here: \n"
                f" * https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api \n"
                f" * https://cloud.google.com/compute/docs/images/create-delete-deprecate-private-images#prepare_instance_for_image"
            )
        else:
            warnings.warn(
                f"Warning: The `force_create` option may compromise the integrity of your image. "
                f"Stop the {disk_user} instance before you create the image if possible."
            )

    # Create image
    image = compute_v1.Image()
    image.source_disk = disk.self_link
    image.name = image_name
    if storage_location:
        image.storage_locations = [storage_location]

    operation = image_client.insert(project=project_id, image_resource=image)

    wait_for_extended_operation(operation, "image creation from disk")

    return image_client.get(project=project_id, image=image_name)

REST

images().insert 方法发出 POST 请求,请求正文中的网址指向您要根据其创建映像的源对象。使用您自己的项目 ID 和资源名称来指定指向您资源的网址。

根据永久性磁盘创建映像

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images

{
  "name": "IMAGE_NAME",
  "sourceDisk": "/zones/ZONE/disks/SOURCE_DISK",
  ("storageLocations": "LOCATION",)
  ("forceCreate": "TRUE")
}

请替换以下内容:

  • PROJECT_ID:映像所属的项目 ID。
  • IMAGE_NAME:您要创建的新映像的名称。
  • ZONE:来源磁盘所在的可用区。
  • SOURCE_DISK:要从中创建映像的磁盘。
  • LOCATION:可选:映像的存储位置。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

可选的 forceCreate 参数让您可以从正在运行的虚拟机创建映像。只有当您确定要从正在运行的虚拟机创建映像时,才可指定 TRUEforceCreate 默认设置为 FALSE

根据其他映像创建映像:

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images

{
  "name": "IMAGE_NAME",
  "sourceImage": "/global/images/SOURCE_IMAGE",
  ("storageLocations": "LOCATION")
}

请替换以下内容:

  • PROJECT_ID:映像所属的项目。
  • IMAGE_NAME:您要创建的新映像的名称。
  • SOURCE_IMAGE:要从中创建映像的映像。
  • LOCATION:可选:映像的存储位置。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

根据快照创建映像

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images
{
  "name": "IMAGE_NAME",
  "sourceSnapshot": "(/SOURCE_PROJECT_ID)/global/snapshots/SOURCE_SNAPSHOT",
  ("storageLocations": "LOCATION")
}

请替换以下内容:

  • PROJECT_ID:映像所属的项目。
  • IMAGE_NAME:您要创建的新映像的名称。
  • SOURCE_PROJECT_ID:可选:快照所在的项目。您必须有权访问该项目中的快照资源。
  • SOURCE_SNAPSHOT:要从中创建映像的快照。
  • LOCATION:可选:映像的存储位置。例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将其存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

有关添加映像的更多信息,请参阅映像参考

共享映像

创建自定义映像后,您可以在多个项目之间共享该映像。如果您允许其他项目中的用户使用您的自定义映像,那么他们可以通过在其请求中指定该映像的项目来访问这些映像。

启用客机操作系统功能

使用客机操作系统 (OS) 功能,在自定义映像上配置以下网络、安全、存储和操作系统选项。具有这些已配置功能的自定义映像用作启动磁盘。

gcloud

结合使用 gcloud compute images create 命令和 --guest-os-features 标志,以根据现有自定义映像创建新的自定义映像。

gcloud compute images create IMAGE_NAME \
    --source-image=SOURCE_IMAGE \
    [--source-image-project=IMAGE_PROJECT] \
    --guest-os-features="FEATURES,..." \
    [--storage-location=LOCATION]

替换以下内容:

  • IMAGE_NAME:新映像的名称
  • SOURCE_IMAGE:新映像所要依据的来源映像
  • IMAGE_PROJECT:可选:包含来源映像的项目

    使用此参数从其他项目中复制映像。

  • FEATURES:用于为根据映像创建的虚拟机启用功能的客机操作系统标记

    如需添加多个值,请使用逗号来分隔值。设置为以下一个或多个值:

    • VIRTIO_SCSI_MULTIQUEUE。在本地 SSD 设备上使用以替代 NVMe。如需详细了解支持 SCSI 的映像,请参阅选择接口

      对于 Linux 映像,您可以在内核版本为 3.17 或更高版本的映像的本地 SSD 设备上启用多队列 SCSI。对于 Windows 映像,您可以在具有 1.2 版 Compute Engine Windows 驱动程序的映像的本地 SSD 设备上启用多队列 SCSI。

    • WINDOWS。将 Windows Server 自定义启动映像标记为 Windows 映像。
    • MULTI_IP_SUBNET。使用 /32 以外的网络掩码配置接口。如需详细了解多个网络接口及其具体工作方式,请参阅 多个网络接口概览和示例
    • UEFI_COMPATIBLE。使用 UEFI 固件和以下 安全强化型虚拟机功能启动:
    • GVNIC。支持更高的网络带宽,即 50 Gbps 到 100 Gbps 的速度。如需了解详情,请参阅 使用 Google 虚拟 NIC
    • IDPF。支持 Intel Infrastructure Data Path Function (IDPF) 网络接口。
    • SEV_CAPABLESEV_SNP_CAPABLE如果您希望在支持 AMD 安全加密虚拟化 (SEV) 或 AMD 安全加密虚拟化-安全嵌套分页 (SEV-SNP) 的 机密虚拟机实例上使用您的映像,请使用这些标记。如需检查您的内核是否支持 AMD SEV 或 AMD SEV-SNP,请参阅 Linux 内核详情
    • SEV_LIVE_MIGRATABLE_V2。如果您想在支持 AMD SEV 上的 实时迁移 机密虚拟机实例上使用映像,请使用此标记。如需检查您的内核是否支持实时迁移,请参阅 Linux 内核详情
    • TDX_CAPABLE。如果您想在支持 Intel Trust Domain Extensions (TDX) 的 机密虚拟机实例上使用您的映像,请使用此标记。如需检查您的内核是否支持 Intel TDX,请参阅 Linux 内核详情
    • SUSPEND_RESUME_COMPATIBLE。支持在虚拟机上暂停和恢复。如需了解详情,请参阅操作系统兼容性
  • LOCATION:可选:用于存储映像的单区域或多区域

    例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将映像存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

REST

结合使用 images().insert 命令guestOsFeatures 标志,以根据现有自定义映像创建新的自定义映像。


POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images

{
 "name": "IMAGE_NAME",
 "sourceImage": "(projects/IMAGE_PROJECT)/global/images/SOURCE_IMAGE",
 ("storageLocations": "LOCATION",)
 "guestOsFeatures": [
  {
   "type": "FEATURES"
  }
 ]
}

替换以下内容:

  • PROJECT_ID:要在其中创建新映像的项目的 ID
  • IMAGE_NAME:新映像的名称
  • IMAGE_PROJECT:可选:包含来源映像的项目

    使用此参数从其他项目中复制映像。

  • SOURCE_IMAGE:新映像所要依据的来源映像

  • LOCATION:可选:用于存储映像的单区域或多区域

    例如,指定 us 可将映像存储在 us 多区域中,或指定 us-central1 可将映像存储在 us-central1 区域中。如果您未选择位置,Compute Engine 会将映像存储在最靠近映像源位置的多区域中。

  • FEATURES:用于为根据映像创建的虚拟机启用功能的客机操作系统标记

    如需添加多个值,请使用逗号来分隔值。设置为以下一个或多个值:

    • VIRTIO_SCSI_MULTIQUEUE。在本地 SSD 设备上使用以替代 NVMe。如需详细了解支持 SCSI 的映像,请参阅选择接口

      对于 Linux 映像,您可以在内核版本为 3.17 或更高版本的映像的本地 SSD 设备上启用多队列 SCSI。对于 Windows 映像,您可以在具有 1.2 版 Compute Engine Windows 驱动程序的映像的本地 SSD 设备上启用多队列 SCSI。

    • WINDOWS。将 Windows Server 自定义启动映像标记为 Windows 映像。
    • MULTI_IP_SUBNET。使用 /32 以外的网络掩码配置接口。如需详细了解多个网络接口及其具体工作方式,请参阅 多个网络接口概览和示例
    • UEFI_COMPATIBLE。使用 UEFI 固件和以下 安全强化型虚拟机功能启动:
    • GVNIC。支持更高的网络带宽,即 50 Gbps 到 100 Gbps 的速度。如需了解详情,请参阅 使用 Google 虚拟 NIC
    • IDPF。支持 Intel Infrastructure Data Path Function (IDPF) 网络接口。
    • SEV_CAPABLESEV_SNP_CAPABLE如果您希望在支持 AMD 安全加密虚拟化 (SEV) 或 AMD 安全加密虚拟化-安全嵌套分页 (SEV-SNP) 的 机密虚拟机实例上使用您的映像,请使用这些标记。如需检查您的内核是否支持 AMD SEV 或 AMD SEV-SNP,请参阅 Linux 内核详情
    • SEV_LIVE_MIGRATABLE_V2。如果您想在支持 AMD SEV 上的 机密虚拟机实例上使用映像,请使用此标记。如需检查您的内核是否支持实时迁移,请参阅 Linux 内核详情
    • TDX_CAPABLE。如果您想在支持 Intel Trust Domain Extensions (TDX) 的 机密虚拟机实例上使用您的映像,请使用此标记。如需检查您的内核是否支持 Intel TDX,请参阅 Linux 内核详情
    • SUSPEND_RESUME_COMPATIBLE。支持在虚拟机上暂停和恢复。如需了解详情,请参阅操作系统兼容性

避免在 UEFI 变量中包含敏感信息

统一可扩展固件接口 (UEFI) 变量是 UEFI 固件在启动时用于启动虚拟机操作系统的键值对变量。与将变量存储在硬件芯片上的物理机器不同,Compute Engine 会虚拟化这些变量的存储。因此,在许多操作系统中,所有应用和用户都可以访问这些变量并获取此类信息。

因此,Google 强烈建议您不要将敏感信息或个人身份信息(例如密码或私钥)写入或存储到 UEFI 变量中。

Arm 映像的注意事项

Google 提供在 Arm CPU 平台上运行的 C4A 和 Tau T2A 机器系列。您可以使用其中一个机器系列启动虚拟机,然后使用该源虚拟机创建 Arm 映像。创建自定义 Arm 映像的过程与创建 x86 映像的过程相同。

为了帮助您的用户区分 Arm 和 x86 映像,ARM 映像的 architecture 字段将设置为 ARM64。此字段的可能值包括:

  • ARCHITECTURE_UNSPECIFIED
  • X86_64
  • ARM64

然后,映像用户可以对此字段进行过滤,以查找基于 x86 或 Arm 的映像。

后续步骤