カスタム 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. VM インスタンスの初回起動時に実行するように instance_setup.ps1 を構成します。
  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 Console で、[新しいイメージの作成] ページに移動します。

    新しいイメージの作成

  2. イメージの名前を指定します。
  3. イメージを作成する元となる、Windows オペレーティング システムがインストールされているソースディスクを選択します。
  4. イメージを保存するロケーションを指定します。[ソースディスクのロケーションに基づく(デフォルト)] プルダウン メニューからイメージのロケーションを選択します。たとえば、イメージを 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

API

リクエスト本文に sourceDisk URL を追記して POST リクエストを images().insert メソッドに発行します。

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 はイメージのソースのロケーションに最も近いマルチリージョンにイメージを保存します。

イメージの追加に関する詳細については、イメージ参照をご覧ください。

Go

このサンプルを試す前に、Compute Engine クイックスタート: クライアント ライブラリの使用に記載されている Go の設定手順を実施します。 詳細については、Compute Engine Go API のリファレンス ドキュメントをご覧ください。

Compute Engine に対して認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証を設定するをご覧ください。

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

	compute "cloud.google.com/go/compute/apiv1"
	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
	"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 = instance_client.get(
            project=project_id, zone=zone, instance=disk_user
        )
        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)

Windows のエージェントとスクリプトを更新する

Compute Engine では、最新のエージェントおよびスクリプトを使用した新しい Windows イメージがリリースされることがあります。これらのアイテムは、Windows インスタンスの起動およびシャットダウン プロセス、アカウント管理、アドレス管理をアシストします。

Windows イメージ バージョン v20160112 以降、Windows エージェントはアップストリーム リリースで自動更新されます。このエージェントの自動更新は、disable-agent-updates インスタンス メタデータキーを true に設定することで無効にすることができます。古いイメージ リリースに基づいたインスタンスがある場合は、そのインスタンスの Windows エージェントの手動更新を実施できます。

次のステップ