建立自訂 Windows Server 映像檔


您可以透過現有的 Compute Engine Windows Server 映像檔,建立自訂 Windows Server 映像檔。使用這些自訂映像檔,建立開機磁碟與現有執行個體相同的執行個體。

對於在現有執行個體上保存作業系統的配置設定,以及重新使用相同的設定來建立其他執行個體,這些自訂映像檔非常實用。

本節未涵蓋以下任務:

事前準備

準備建立 Windows Server 映像檔

在您建立映像檔之前,請在執行個體上執行 GCESysprep,以針對映像檔建立程序做好準備。

GCESysprep 會完成下列步驟,為執行個體做好成為 Compute Engine 映像檔的準備:

  1. 停用 GCEStartup 排程工作。
  2. 從暫存資料夾位置刪除所有檔案。
  3. 清除所有 Windows 事件記錄。
  4. 執行 sysprep.exe /generalize /oobe /quit /unattend
  5. 設定 instance_setup.ps1,以便在 VM 執行個體初次啟動時執行。
  6. 刪除 RDP 憑證。
  7. 移除儲存的永久磁碟清單。
  8. 啟用 RDP 和 WinRM 防火牆規則。
  9. 停用 Google OS Config 服務。
  10. 關閉執行個體。

GCESysprep 作業會記錄到 Windows 事件記錄檔和序列埠 1。Sysprep 會寫入多個記錄檔案

如何使用 GCESysprep 建立 Compute Engine 映像檔:

  1. 以管理員權限執行 GCESysprep

    GCESysprep
    
  2. 建立映像檔

指定圖片位置

建立自訂映像檔時,您也可以指定映像檔的 Cloud Storage 位置,但不包括雙地區位置。藉由指定映像檔的儲存位置,您可以確保跨地區的備援功能,藉以遵循資料位置的法規並符合高可用性需求。

您可以自行選擇是否要使用儲存位置功能。如果未選擇位置,Compute Engine 會將映像檔儲存在最靠近映像檔來源的多地區位置。您可以從來源磁碟、映像檔、快照或儲存在 Cloud Storage 中的映像檔建立自訂映像檔。您可以使用這些映像檔,建立新的 VM 執行個體。

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

建立 Windows 映像檔

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

  • 永久磁碟,即使該磁碟已連接執行個體。
  • 永久磁碟的快照
  • 專案中的另一個映像檔
  • 從另一個專案共用的映像檔
  • 儲存在 Cloud Storage 中的匯入映像檔

主控台

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

    「Create a new image」(建立新的映像檔) 頁面

  2. 指定映像檔「Name」(名稱)。
  3. 選擇「Source」(來源) 磁碟,這個磁碟應具有您要建立映像檔的 Windows 作業系統。
  4. 指定儲存映像檔的「Location」(位置)。從 [Based on source disk location (default)] (根據來源磁碟位置 (預設值)) 下拉式選單中選擇映像檔位置。例如,指定 us 會將映像檔儲存在 us 多地區;指定 us-central1 則會儲存在 us-central1 地區。如果未選擇位置,Compute Engine 會將映像檔儲存在最靠近映像檔來源的多地區。
  5. 指定映像檔的屬性。例如,可以為映像檔指定映像檔系列名稱,以將此映像檔整理為映像檔系列的成員。
  6. 按一下 [建立]。

gcloud

使用 gcloud computeimages create,並提供您要用來建立映像檔的來源永久磁碟。您可以選擇加入 --force 旗標,即使映像檔已連結至執行中的執行個體,仍可建立映像檔。

gcloud compute images create example-image --source-disk [DISK_NAME] \
    --source-disk-zone [ZONE] \
    --storage-location [LOCATION] \
    [--force]
  • [DISK_NAME] 是要建立映像檔的來源磁碟名稱。
  • [ZONE] 是磁碟的區域。
  • [LOCATION] 是選用旗標,讓您指定要儲存映像檔的地區或多地區。例如,指定 us 會將映像檔儲存在 us 多地區;指定 us-central1 則會儲存在 us-central1 地區。如果未選擇位置,Compute Engine 會將映像檔儲存在最靠近映像檔來源的多地區。
  • --force 是選用旗標,即使磁碟已連結至執行中的執行個體仍可建立映像檔。這個選項可能會危害映像檔的完整性。如果可以的話,請在建立映像檔之前,停止執行個體。

當您執行此指令時,gcloud compute 會根據您提供的永久磁碟建立一個新映像檔,並將該映像檔加到您的集合中。執行下列指令即可確認映像檔是否已成功建立:

gcloud compute images list

Go

在試用這個範例之前,請先按照 使用用戶端程式庫的 Compute Engine 快速入門中的操作說明設定 Go。詳情請參閱 Compute Engine Go API 參考資料說明文件

如要向 Compute Engine 進行驗證,請設定應用程式預設憑證。詳情請參閱「為本機開發環境設定驗證機制」。

import (
	"context"
	"fmt"
	"io"
	"strings"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"google.golang.org/protobuf/proto"
)

// createWindowsOSImage creates a new Windows image from the specified source disk.
func createWindowsOSImage(
	w io.Writer,
	projectID, zone, sourceDiskName, imageName, storageLocation string,
	forceCreate bool,
) error {
	// projectID := "your_project_id"
	// zone := "europe-central2-b"
	// sourceDiskName := "your_source_disk_name"
	// imageName := "your_image_name"
	// storageLocation := "eu"
	// forceCreate := false

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

	// Getting instances where source disk is attached
	diskRequest := &computepb.GetDiskRequest{
		Project: projectID,
		Zone:    zone,
		Disk:    sourceDiskName,
	}

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

	// Сhecking whether the instances is stopped
	for _, fullInstanceName := range sourceDisk.GetUsers() {
		parsedName := strings.Split(fullInstanceName, "/")
		l := len(parsedName)
		if l < 5 {
			return fmt.Errorf(
				"API returned instance name with unexpected format",
			)
		}
		instanceReq := &computepb.GetInstanceRequest{
			Project:  parsedName[l-5],
			Zone:     parsedName[l-3],
			Instance: parsedName[l-1],
		}
		instance, err := instancesClient.Get(ctx, instanceReq)
		if err != nil {
			return fmt.Errorf("unable to get instance: %w", err)
		}

		if instance.GetStatus() != "TERMINATED" && instance.GetStatus() != "STOPPED" {
			if !forceCreate {
				return fmt.Errorf("instance %s should be stopped. "+
					"Please stop the instance using "+
					"GCESysprep command or set forceCreate parameter to true "+
					"(not recommended). More information here: "+
					"https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api",
					parsedName[l-1],
				)
			}
		}
	}

	if forceCreate {
		fmt.Fprintf(w, "Warning: ForceCreate option compromise the integrity of your image. "+
			"Stop the instance before you create the image if possible.",
		)
	}

	req := &computepb.InsertImageRequest{
		Project:     projectID,
		ForceCreate: &forceCreate,
		ImageResource: &computepb.Image{
			Name:             proto.String(imageName),
			SourceDisk:       proto.String(fmt.Sprintf("zones/%s/disks/%s", zone, sourceDiskName)),
			StorageLocations: []string{storageLocation},
		},
	}

	op, err := imagesClient.Insert(ctx, req)
	if err != nil {
		return fmt.Errorf("unable to create image: %w", err)
	}

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

	fmt.Fprintf(w, "Image created\n")

	return nil
}

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]);
      }
    };
  }

}

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 sourceDiskName = 'YOUR_SOURCE_DISK_NAME';
// const imageName = 'YOUR_IMAGE_NAME';
// const storageLocation = 'eu';
// const forceCreate = false;

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

function parseInstanceName(name) {
  const parsedName = name.split('/');
  const l = parsedName.length;

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

  return [parsedName[l - 1], parsedName[l - 3], parsedName[l - 5]];
}

async function createWindowsOSImage() {
  const imagesClient = new compute.ImagesClient();
  const instancesClient = new compute.InstancesClient();
  const disksClient = new compute.DisksClient();

  // Getting instances where source disk is attached
  const [sourceDisk] = await disksClient.get({
    project: projectId,
    zone,
    disk: sourceDiskName,
  });

  // Сhecking whether the instances is stopped
  for (const fullInstanceName of sourceDisk.users) {
    const [instanceName, instanceZone, instanceProjectId] =
      parseInstanceName(fullInstanceName);
    const [instance] = await instancesClient.get({
      project: instanceProjectId,
      zone: instanceZone,
      instance: instanceName,
    });

    if (
      !['TERMINATED', 'STOPPED'].includes(instance.status) &&
      !forceCreate
    ) {
      throw new Error(
        `Instance ${instanceName} should be stopped. Please stop the instance using GCESysprep command or set forceCreate parameter to true (not recommended). More information here: https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api.`
      );
    }
  }

  if (forceCreate) {
    console.warn(
      'Warning: forceCreate option compromise the integrity of your image. Stop the instance before you create the image if possible.'
    );
  }

  const [response] = await imagesClient.insert({
    project: projectId,
    forceCreate,
    imageResource: {
      name: imageName,
      sourceDisk: `/zones/${zone}/disks/${sourceDiskName}`,
      storageLocations: storageLocation ? [storageLocation] : [],
    },
  });
  let operation = response.latestResponse;
  const operationsClient = new compute.GlobalOperationsClient();

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

  console.log('Image created.');
}

createWindowsOSImage();

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 方法提出主體包含 sourceDisk 網址的 POST 要求。

POST https://compute.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/images?[FORCE_OPTION]

{
  "name": "[IMAGE_NAME]",
  "sourceDisk": "zones/[ZONE]/disks/[DISK_NAME]",
  "storageLocations": "[LOCATION]",
}

其中:

  • [PROJECT_ID] 是這項要求的專案 ID。
  • [FORCE_OPTION] 是用來建立映像檔的選項,即使來源磁碟已連結至執行中的執行個體。請在 POST 行列末端指定 forceCreate=true,即可設定此選項。這個選項可能會危害映像檔的完整性。如果可以的話,請在建立映像檔之前,停止執行個體。
  • [IMAGE_NAME] 是您要為此映像檔指定的名稱。
  • [ZONE] 是要從中建立映像檔的來源磁碟區域。
  • [DISK_NAME] 是來源磁碟的名稱。
  • [LOCATION] 是選用參數,用來選取儲存映像檔的多地區或地區。例如,指定 us 會將映像檔儲存在 us 多地區;指定 us-central1 則會儲存在 us-central1 地區。如果未選擇位置,Compute Engine 會將映像檔儲存在最靠近映像檔來源的多地區。

如需更多新增映像檔的資訊,請參閱映像檔參考資料

更新 Windows 代理程式和指令碼

Compute Engine 偶爾會發佈包含最新代理程式和指令碼的新 Windows 映像檔。在處理 Windows 執行個體的啟動和關閉程序、帳戶管理以及位址管理等作業時,這些項目非常實用。

自 Windows 映像檔 v20160112 版起,Windows 代理程式會透過上游發布內容自行更新。您可將 disable-agent-updates 執行個體中繼資料鍵值設為 true 以停用代理程式更新。假如您有基於較舊映像檔版本的執行個體,則可以為這些執行個體手動更新 Windows 代理程式

後續步驟