建立自訂圖片


您可以從來源磁碟、映像檔、快照或儲存於 Cloud Storage 的映像檔建立自訂映像檔,並以這些映像檔建立虛擬機器 (VM) 執行個體。在您已建立永久開機磁碟或特定映像檔,將其修改為某種狀態,並且需要儲存該狀態以建立 VM 時,這是理想的做法。

或者也可以使用虛擬磁碟匯入工具,從現有系統將開機磁碟的映像檔匯入 Compute Engine,再將映像檔新增到您的自訂映像檔清單中。

事前準備

建立自訂映像檔

本節說明如何在 Linux VM 上建立自訂映像檔。如要瞭解如何建立 Windows 映像檔,請參閱「建立 Windows 映像檔」一文。

選取圖片儲存位置

建立自訂映像檔時,您也可以指定映像檔的 Cloud Storage 位置,但不包括雙地區位置。指定映像檔的儲存位置有助您確保跨地區的備援功能,藉以遵循資料本地性的法規並符合高可用性需求。如要建立、修改及刪除儲存在 Cloud Storage 中的圖片,您必須擁有 roles/compute.storageAdmin

您可以自行選擇是否要使用儲存位置功能。如果您沒有選取位置,Compute Engine 會將映像檔儲存在距離映像檔來源最近的多地區。舉例來說,如果您從位於 us-central1 的來源磁碟建立映像檔,但未指定自訂映像檔的位置,Compute Engine 就會將映像檔儲存在 us 多地區。

如果您建立 VM 的地區沒有提供映像檔,Compute Engine 會在您第一次建立 VM 時,將映像檔快取到該地區。

如要查看圖片的儲存位置,請使用 gcloud computeimages describe 指令:

gcloud compute images describe IMAGE_NAME \
    --project=PROJECT_ID

更改下列內容:

  • IMAGE_NAME:圖片名稱。

  • PROJECT_ID:映像檔所屬的專案 ID。

此功能推出前的所有現有圖片都會保留在原來的位置,唯一的不同是您可以查看所有圖片的所在位置。如要移動現有圖片,則必須在新位置重新建立圖片。

為映像檔準備 VM

您可以從磁碟建立映像檔,即使磁碟已連結至執行中的 VM 也沒問題。不過,如果您將 VM 置於更容易擷取映像檔的狀態,映像檔的穩定性就會提高。本節說明如何為映像檔準備開機磁碟。

盡量減少將資料寫入永久磁碟

請使用下列其中一種程序,減少磁碟寫入作業:

  • 停止 VM使它能關閉並停止將任何資料寫入永久磁碟。

  • 如果您無法在建立映像檔之前停止 VM,請盡量減少寫入磁碟的資料量,並且同步處理檔案系統。如要盡量減少寫入永久磁碟的情況,請按照下列步驟操作:

    1. 暫停會將資料寫入該永久磁碟的應用程式或作業系統程序。
    2. 視需要執行適當的指令來清除磁碟中的應用程式快取資料。例如,MySQL 擁有 FLUSH 陳述式。其他應用程式可能也有類似的程序。
    3. 讓應用程式停止將資料寫入永久磁碟。
    4. 執行 sudo sync

停用磁碟的自動刪除選項

根據預設,系統會在開機磁碟上啟用自動刪除選項。從磁碟建立映像檔前,請先停用自動刪除功能,確保在刪除 VM 時不會自動刪除磁碟。

請使用下列其中一種方法,停用磁碟的自動刪除功能。

主控台

  1. 前往 Google Cloud 控制台的「VM instances」(VM 執行個體) 頁面

    前往 VM 執行個體頁面

  2. 按一下要用來建立映像檔的來源 VM 名稱。

    系統會顯示「VM instance details」(VM 執行個體詳細資料)頁面。

  3. 按一下 [編輯]

  4. 在「Boot disk」(開機磁碟) 區段的「Deletion rule」(刪除規則) 中,確認已選取「Keep disk」(保留磁碟) 選項。

  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:VM 執行個體的名稱。
  • 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:來源 VM 所屬的專案 ID。
  • ZONE:來源 VM 所在的可用區。
  • VM_NAME:來源 VM 的名稱。
  • SOURCE_DISK:您要用來建立映像檔的磁碟裝置名稱。

準備好 VM 後,請建立映像檔

建立映像檔

您可從下列來源建立磁碟映像檔:

  • 永久磁碟,即使該磁碟已連接 VM
  • 永久磁碟的快照
  • 專案中的另一個映像檔
  • 從另一個專案共用的映像檔
  • Cloud Storage 中的已壓縮 RAW 映像檔

您可以每 10 分鐘建立一次磁碟映像檔。如果您想發出連續要求來建立磁碟映像檔,請在 60 分鐘內發出最多 6 個要求。詳情請參閱「快照頻率限制」。

主控台

  1. 在 Google Cloud 控制台中,前往「Create an image」(建立映像檔) 頁面。

    前往「Create an image」(建立映像檔)

  2. 指定映像檔的「Name」(名稱)

  3. 指定您要建立映像檔的「Source」(來源)。這可以是 Cloud Storage 中的永久磁碟、快照、其他映像檔或 disk.raw 檔案。

  4. 如要從連結到執行中 VM 的磁碟建立映像檔,請勾選「Keep instance running」,確認您要在 VM 執行時建立映像檔。您可以在建立映像檔前準備 VM

  5. 在「Based on source disk location (default)」(根據來源磁碟位置 (預設值)) 下拉式選單中,指定儲存圖片的位置。例如,指定 us 會將圖片儲存在 us 多地區;指定 us-central1 則會儲存在 us-central1 地區。如果未選擇位置,Compute Engine 會將映像檔儲存在最靠近映像檔來源位置的多地區。

  6. 選用:指定圖片的屬性。

    • Family:這個新映像檔所屬的映像檔系列
    • 說明:自訂圖片的說明。
    • 標籤:用來將資源分組的標籤
  7. 指定加密金鑰。您可以選擇 Google-owned and Google-managed encryption key、Cloud Key Management Service (Cloud KMS) 金鑰或客戶提供的加密金鑰 (CSEK)。如果未指定加密金鑰,系統會使用 Google-owned and Google-managed encryption key為圖片加密。

  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 與資源名稱,將 URL 指定到資源。

從永久磁碟建立映像檔:

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 參數,從執行中的 VM 建立映像檔。只有在您確定要從執行中的 VM 建立映像檔時,才指定 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) 功能,在自訂映像檔上設定下列網路、安全性、儲存空間和 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:訪客 OS 標記,可為您從映像檔建立的 VM 啟用功能

    如要新增多個值,請使用半形逗號分隔各個值。設為下列一或多個值:

    • VIRTIO_SCSI_MULTIQUEUE 在本機 SSD 裝置上使用,做為 NVMe 的替代方案。如要進一步瞭解支援 SCSI 的映像檔,請參閱「選擇介面」一文。

      對於 Linux 映像檔,您可以在核心版本 3.17 以上的映像檔上,在本機 SSD 裝置上啟用多佇列 SCSI。對於 Windows 映像檔,您可以在含有 Compute Engine Windows 驅動程式 1.2 版的映像檔上,為本機 SSD 裝置啟用多佇列 SCSI。

    • WINDOWS將 Windows Server 自訂開機映像檔標記為 Windows 映像檔。
    • MULTI_IP_SUBNET設定具有 /32 之外網路遮罩的介面。如要進一步瞭解多個網路介面及其運作方式,請參閱「 多個網路介面的總覽與範例」。
    • UEFI_COMPATIBLE使用 UEFI 韌體和下列 受防護的 VM 功能啟動:
    • GVNIC支援更高的網路頻寬,最高可達 50 Gbps 到 100 Gbps 的速度。詳情請參閱「 使用 Google 虛擬網路介面卡」一文。
    • IDPF支援 Intel Infrastructure Data Path Function (IDPF) 網路介面。
    • SEV_CAPABLESEV_SNP_CAPABLE如果您想在支援 AMD 安全加密虛擬化 (SEV) 或 AMD 安全加密虛擬化-安全巢狀分頁 (SEV-SNP) 的 機密 VM 執行個體上使用映像檔,請使用這些標記。如要確認您的核心是否支援 AMD SEV 或 AMD SEV-SNP,請參閱 Linux 核心詳細資料
    • SEV_LIVE_MIGRATABLE_V2如果您想在支援 AMD SEV 的 即時遷移 機密 VM 例項上使用映像檔,請使用這個標記。如要確認核心是否支援即時遷移,請參閱 Linux 核心詳細資料
    • TDX_CAPABLE如果您想在支援 Intel Trust Domain Extensions (TDX) 的 Confidential VM 例項上使用映像檔,請使用這個標記。如要確認核心是否支援 Intel TDX,請參閱 Linux 核心詳細資料
    • SUSPEND_RESUME_COMPATIBLE支援在 VM 上暫停及重新啟用。詳情請參閱「作業系統相容性」。
  • 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:訪客 OS 標記,可為您從映像檔建立的 VM 啟用功能

    如要新增多個值,請使用半形逗號分隔各個值。設為下列一或多個值:

    • VIRTIO_SCSI_MULTIQUEUE 在本機 SSD 裝置上使用,做為 NVMe 的替代方案。如要進一步瞭解支援 SCSI 的映像檔,請參閱「選擇介面」一文。

      對於 Linux 映像檔,您可以在核心版本 3.17 以上的映像檔上,在本機 SSD 裝置上啟用多佇列 SCSI。對於 Windows 映像檔,您可以在含有 Compute Engine Windows 驅動程式 1.2 版的映像檔上,為本機 SSD 裝置啟用多佇列 SCSI。

    • WINDOWS將 Windows Server 自訂開機映像檔標記為 Windows 映像檔。
    • MULTI_IP_SUBNET設定具有 /32 之外網路遮罩的介面。如要進一步瞭解多個網路介面及其運作方式,請參閱「 多個網路介面的總覽與範例」。
    • UEFI_COMPATIBLE使用 UEFI 韌體和下列 受防護的 VM 功能啟動:
    • GVNIC支援更高的網路頻寬,最高可達 50 Gbps 到 100 Gbps 的速度。詳情請參閱「 使用 Google 虛擬網路介面卡」一文。
    • IDPF支援 Intel Infrastructure Data Path Function (IDPF) 網路介面。
    • SEV_CAPABLESEV_SNP_CAPABLE如果您想在支援 AMD 安全加密虛擬化 (SEV) 或 AMD 安全加密虛擬化-安全巢狀分頁 (SEV-SNP) 的 機密 VM 執行個體上使用映像檔,請使用這些標記。如要確認您的核心是否支援 AMD SEV 或 AMD SEV-SNP,請參閱 Linux 核心詳細資料
    • SEV_LIVE_MIGRATABLE_V2如果您想在支援 AMD SEV 的 即時遷移 機密 VM 例項上使用映像檔,請使用這個標記。如要確認核心是否支援即時遷移,請參閱 Linux 核心詳細資料
    • TDX_CAPABLE如果您想在支援 Intel Trust Domain Extensions (TDX) 的 Confidential VM 例項上使用映像檔,請使用這個標記。如要確認核心是否支援 Intel TDX,請參閱 Linux 核心詳細資料
    • SUSPEND_RESUME_COMPATIBLE支援在 VM 上暫停及重新啟用。詳情請參閱「作業系統相容性」。

避免在 UEFI 變數中使用機密資訊

統一可延伸韌體介面 (UEFI) 變數是鍵/值組合變數,UEFI 韌體會在啟動期間使用這些變數來啟動 VM 的作業系統。與實體機器不同,變數儲存在硬體晶片上,Compute Engine 會將這些變數的儲存空間虛擬化。因此,在許多作業系統中,所有應用程式和使用者都可以存取這些變數和資訊。

基於這個原因,Google 強烈建議您不要將密碼或私密金鑰等敏感或個人識別資訊寫入或儲存在 UEFI 變數中。

Arm 映像檔的注意事項

Google 提供 C4A 和 Tau T2A 機器系列,可在 Arm CPU 平台上執行。您可以使用其中一個機器系列啟動 VM,然後使用該來源 VM 建立 Arm 映像檔。建立自訂 Arm 映像檔的程序與建立 x86 映像檔的程序相同。

為協助使用者區分 Arm 和 x86 映像檔,Arm 映像檔的 architecture 欄位會設為 ARM64。這個欄位的可能值如下:

  • ARCHITECTURE_UNSPECIFIED
  • X86_64
  • ARM64

圖像使用者可以依據這個欄位進行篩選,找出 x86 或 Arm 架構的圖像。

後續步驟