プリエンプティブル VM を作成して使用する


このガイドでは、プリエンプティブル仮想マシン(VM)インスタンスを作成して使用する方法について説明します。プリエンプティブル VM は、標準 VM の料金と比べて 60~91% 割引でご利用いただけます。ただし、他のタスクがリソースを再利用する必要がある場合、Compute Engine がこの VM を停止(プリエンプト)する可能性があります。プリエンプティブル VM は 24 時間後に必ず停止します。プリエンプティブル VM は、VM プリエンプションに耐えることができるフォールト トレラント アプリケーションでのみ推奨されています。プリエンプティブル VM を作成することを決める前に、ご使用のアプリケーションでプリエンプションの処理が可能であることを確認してください。プリエンプティブル VM のドキュメントでは、プリエンプティブル VM のリスクと価値について解説しています。

始める前に

プリエンプティブル VM を作成する

gcloud CLI または Compute Engine API を使用してプリエンプティブル VM を作成します。Google Cloud コンソールを使用するには、代わりに Spot VM を作成します。

gcloud

gcloud compute では、通常の VM を作成する場合に使用するものと同じ instances create コマンドを使用しますが、--preemptible フラグを追加します。

gcloud compute instances create [VM_NAME] --preemptible

ここで [VM_NAME] は VM の名前です。

API

API では、VM の作成リクエストを通常どおりに作成しますが、schedulingpreemptible プロパティを含め、その値を true に設定します。次に例を示します。

POST https://compute.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances

{
  'machineType': 'zones/[ZONE]/machineTypes/[MACHINE_TYPE]',
  'name': '[INSTANCE_NAME]',
  'scheduling':
  {
    'preemptible': true
  },
  ...
}

Go

import (
	"context"
	"fmt"
	"io"

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

// createPreemtibleInstance creates a new preemptible VM instance
// with Debian 10 operating system.
func createPreemtibleInstance(
	w io.Writer, projectID, zone, instanceName string,
) error {
	// projectID := "your_project_id"
	// zone := "europe-central2-b"
	// instanceName := "your_instance_name"
	// preemptible := true

	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()

	// List of public operating system (OS) images:
	// https://cloud.google.com/compute/docs/images/os-details.
	newestDebianReq := &computepb.GetFromFamilyImageRequest{
		Project: "debian-cloud",
		Family:  "debian-11",
	}
	newestDebian, err := imagesClient.GetFromFamily(ctx, newestDebianReq)
	if err != nil {
		return fmt.Errorf("unable to get image from family: %w", err)
	}

	inst := &computepb.Instance{
		Name: proto.String(instanceName),
		Disks: []*computepb.AttachedDisk{
			{
				InitializeParams: &computepb.AttachedDiskInitializeParams{
					DiskSizeGb:  proto.Int64(10),
					SourceImage: newestDebian.SelfLink,
					DiskType:    proto.String(fmt.Sprintf("zones/%s/diskTypes/pd-standard", zone)),
				},
				AutoDelete: proto.Bool(true),
				Boot:       proto.Bool(true),
			},
		},
		Scheduling: &computepb.Scheduling{
			// Set the preemptible setting
			Preemptible: proto.Bool(true),
		},
		MachineType: proto.String(fmt.Sprintf("zones/%s/machineTypes/n1-standard-1", zone)),
		NetworkInterfaces: []*computepb.NetworkInterface{
			{
				Name: proto.String("global/networks/default"),
			},
		},
	}

	req := &computepb.InsertInstanceRequest{
		Project:          projectID,
		Zone:             zone,
		InstanceResource: inst,
	}

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

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

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

	return nil
}

Java


import com.google.cloud.compute.v1.AttachedDisk;
import com.google.cloud.compute.v1.AttachedDiskInitializeParams;
import com.google.cloud.compute.v1.InsertInstanceRequest;
import com.google.cloud.compute.v1.Instance;
import com.google.cloud.compute.v1.InstancesClient;
import com.google.cloud.compute.v1.NetworkInterface;
import com.google.cloud.compute.v1.Operation;
import com.google.cloud.compute.v1.Scheduling;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreatePreemptibleInstance {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {
    // TODO(developer): Replace these variables before running the sample.
    // projectId: project ID or project number of the Cloud project you want to use.
    // zone: name of the zone you want to use. For example: “us-west3-b”
    // instanceName: name of the new virtual machine.
    String projectId = "your-project-id-or-number";
    String zone = "zone-name";
    String instanceName = "instance-name";

    createPremptibleInstance(projectId, zone, instanceName);
  }

  // Send an instance creation request with preemptible settings to the Compute Engine API
  // and wait for it to complete.
  public static void createPremptibleInstance(String projectId, String zone, String instanceName)
      throws IOException, ExecutionException, InterruptedException, TimeoutException {

    String machineType = String.format("zones/%s/machineTypes/e2-small", zone);
    String sourceImage = "projects/debian-cloud/global/images/family/debian-11";
    long diskSizeGb = 10L;
    String networkName = "default";

    try (InstancesClient instancesClient = InstancesClient.create()) {

      AttachedDisk disk =
          AttachedDisk.newBuilder()
              .setBoot(true)
              .setAutoDelete(true)
              .setType(AttachedDisk.Type.PERSISTENT.toString())
              .setInitializeParams(
                  // Describe the size and source image of the boot disk to attach to the instance.
                  AttachedDiskInitializeParams.newBuilder()
                      .setSourceImage(sourceImage)
                      .setDiskSizeGb(diskSizeGb)
                      .build())
              .build();

      // Use the default VPC network.
      NetworkInterface networkInterface = NetworkInterface.newBuilder()
          .setName(networkName)
          .build();

      // Collect information into the Instance object.
      Instance instanceResource =
          Instance.newBuilder()
              .setName(instanceName)
              .setMachineType(machineType)
              .addDisks(disk)
              .addNetworkInterfaces(networkInterface)
              // Set the preemptible setting.
              .setScheduling(Scheduling.newBuilder()
                  .setPreemptible(true)
                  .build())
              .build();

      System.out.printf("Creating instance: %s at %s %n", instanceName, zone);

      // Prepare the request to insert an instance.
      InsertInstanceRequest insertInstanceRequest = InsertInstanceRequest.newBuilder()
          .setProject(projectId)
          .setZone(zone)
          .setInstanceResource(instanceResource)
          .build();

      // Wait for the create operation to complete.
      Operation response = instancesClient.insertAsync(insertInstanceRequest)
          .get(3, TimeUnit.MINUTES);
      ;

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

      System.out.printf("Instance created : %s\n", instanceName);
      System.out.println("Operation Status: " + response.getStatus());
    }
  }
}

Node.js

/**
 * 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 compute = require('@google-cloud/compute');

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

  const [response] = await instancesClient.insert({
    instanceResource: {
      name: instanceName,
      disks: [
        {
          initializeParams: {
            diskSizeGb: '64',
            sourceImage:
              'projects/debian-cloud/global/images/family/debian-11/',
          },
          autoDelete: true,
          boot: true,
        },
      ],
      scheduling: {
        // Set the preemptible setting
        preemptible: true,
      },
      machineType: `zones/${zone}/machineTypes/e2-small`,
      networkInterfaces: [
        {
          name: 'global/networks/default',
        },
      ],
    },
    project: projectId,
    zone,
  });
  let operation = response.latestResponse;
  const operationsClient = new compute.ZoneOperationsClient();

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

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

createPreemptible();

Python

from __future__ import annotations

import re
import sys
from typing import Any
import warnings

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

def get_image_from_family(project: str, family: str) -> compute_v1.Image:
    """
    Retrieve the newest image that is part of a given family in a project.

    Args:
        project: project ID or project number of the Cloud project you want to get image from.
        family: name of the image family you want to get image from.

    Returns:
        An Image object.
    """
    image_client = compute_v1.ImagesClient()
    # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details
    newest_image = image_client.get_from_family(project=project, family=family)
    return newest_image

def disk_from_image(
    disk_type: str,
    disk_size_gb: int,
    boot: bool,
    source_image: str,
    auto_delete: bool = True,
) -> compute_v1.AttachedDisk:
    """
    Create an AttachedDisk object to be used in VM instance creation. Uses an image as the
    source for the new disk.

    Args:
         disk_type: the type of disk you want to create. This value uses the following format:
            "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "zones/us-west3-b/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        boot: boolean flag indicating whether this disk should be used as a boot disk of an instance
        source_image: source image to use when creating this disk. You must have read access to this disk. This can be one
            of the publicly available images or an image from one of your projects.
            This value uses the following format: "projects/{project_name}/global/images/{image_name}"
        auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it

    Returns:
        AttachedDisk object configured to be created using the specified image.
    """
    boot_disk = compute_v1.AttachedDisk()
    initialize_params = compute_v1.AttachedDiskInitializeParams()
    initialize_params.source_image = source_image
    initialize_params.disk_size_gb = disk_size_gb
    initialize_params.disk_type = disk_type
    boot_disk.initialize_params = initialize_params
    # Remember to set auto_delete to True if you want the disk to be deleted when you delete
    # your VM instance.
    boot_disk.auto_delete = auto_delete
    boot_disk.boot = boot
    return boot_disk

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 create_instance(
    project_id: str,
    zone: str,
    instance_name: str,
    disks: list[compute_v1.AttachedDisk],
    machine_type: str = "n1-standard-1",
    network_link: str = "global/networks/default",
    subnetwork_link: str = None,
    internal_ip: str = None,
    external_access: bool = False,
    external_ipv4: str = None,
    accelerators: list[compute_v1.AcceleratorConfig] = None,
    preemptible: bool = False,
    spot: bool = False,
    instance_termination_action: str = "STOP",
    custom_hostname: str = None,
    delete_protection: bool = False,
) -> compute_v1.Instance:
    """
    Send an instance creation request to the Compute Engine API and wait for it to complete.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone to create the instance in. For example: "us-west3-b"
        instance_name: name of the new virtual machine (VM) instance.
        disks: a list of compute_v1.AttachedDisk objects describing the disks
            you want to attach to your new instance.
        machine_type: machine type of the VM being created. This value uses the
            following format: "zones/{zone}/machineTypes/{type_name}".
            For example: "zones/europe-west3-c/machineTypes/f1-micro"
        network_link: name of the network you want the new instance to use.
            For example: "global/networks/default" represents the network
            named "default", which is created automatically for each project.
        subnetwork_link: name of the subnetwork you want the new instance to use.
            This value uses the following format:
            "regions/{region}/subnetworks/{subnetwork_name}"
        internal_ip: internal IP address you want to assign to the new instance.
            By default, a free address from the pool of available internal IP addresses of
            used subnet will be used.
        external_access: boolean flag indicating if the instance should have an external IPv4
            address assigned.
        external_ipv4: external IPv4 address to be assigned to this instance. If you specify
            an external IP address, it must live in the same region as the zone of the instance.
            This setting requires `external_access` to be set to True to work.
        accelerators: a list of AcceleratorConfig objects describing the accelerators that will
            be attached to the new instance.
        preemptible: boolean value indicating if the new instance should be preemptible
            or not. Preemptible VMs have been deprecated and you should now use Spot VMs.
        spot: boolean value indicating if the new instance should be a Spot VM or not.
        instance_termination_action: What action should be taken once a Spot VM is terminated.
            Possible values: "STOP", "DELETE"
        custom_hostname: Custom hostname of the new VM instance.
            Custom hostnames must conform to RFC 1035 requirements for valid hostnames.
        delete_protection: boolean value indicating if the new virtual machine should be
            protected against deletion or not.
    Returns:
        Instance object.
    """
    instance_client = compute_v1.InstancesClient()

    # Use the network interface provided in the network_link argument.
    network_interface = compute_v1.NetworkInterface()
    network_interface.network = network_link
    if subnetwork_link:
        network_interface.subnetwork = subnetwork_link

    if internal_ip:
        network_interface.network_i_p = internal_ip

    if external_access:
        access = compute_v1.AccessConfig()
        access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name
        access.name = "External NAT"
        access.network_tier = access.NetworkTier.PREMIUM.name
        if external_ipv4:
            access.nat_i_p = external_ipv4
        network_interface.access_configs = [access]

    # Collect information into the Instance object.
    instance = compute_v1.Instance()
    instance.network_interfaces = [network_interface]
    instance.name = instance_name
    instance.disks = disks
    if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type):
        instance.machine_type = machine_type
    else:
        instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}"

    instance.scheduling = compute_v1.Scheduling()
    if accelerators:
        instance.guest_accelerators = accelerators
        instance.scheduling.on_host_maintenance = (
            compute_v1.Scheduling.OnHostMaintenance.TERMINATE.name
        )

    if preemptible:
        # Set the preemptible setting
        warnings.warn(
            "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning
        )
        instance.scheduling = compute_v1.Scheduling()
        instance.scheduling.preemptible = True

    if spot:
        # Set the Spot VM setting
        instance.scheduling.provisioning_model = (
            compute_v1.Scheduling.ProvisioningModel.SPOT.name
        )
        instance.scheduling.instance_termination_action = instance_termination_action

    if custom_hostname is not None:
        # Set the custom hostname for the instance
        instance.hostname = custom_hostname

    if delete_protection:
        # Set the delete protection bit
        instance.deletion_protection = True

    # Prepare the request to insert an instance.
    request = compute_v1.InsertInstanceRequest()
    request.zone = zone
    request.project = project_id
    request.instance_resource = instance

    # Wait for the create operation to complete.
    print(f"Creating the {instance_name} instance in {zone}...")

    operation = instance_client.insert(request=request)

    wait_for_extended_operation(operation, "instance creation")

    print(f"Instance {instance_name} created.")
    return instance_client.get(project=project_id, zone=zone, instance=instance_name)

def create_preemptible_instance(
    project_id: str, zone: str, instance_name: str
) -> compute_v1.Instance:
    """
    Create a new preemptible VM instance with Debian 10 operating system.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone to create the instance in. For example: "us-west3-b"
        instance_name: name of the new virtual machine (VM) instance.

    Returns:
        Instance object.
    """
    newest_debian = get_image_from_family(project="debian-cloud", family="debian-11")
    disk_type = f"zones/{zone}/diskTypes/pd-standard"
    disks = [disk_from_image(disk_type, 10, True, newest_debian.self_link)]
    instance = create_instance(project_id, zone, instance_name, disks, preemptible=True)
    return instance

プリエンプティブル CPU の割り当て

プリエンプティブル VM には、標準 VM などの使用可能な CPU の割り当てが必要です。プリエンプティブル VM が標準 VM の CPU の割り当てを消費しないように、特別な「プリエンプティブル CPU」の割り当てをリクエストできます。Compute Engine が対象のリージョンでプリエンプティブル CPU の割り当てを付与した後は、すべてのプリエンプティブル VM がその割り当てに対してカウントされ、すべての標準 VM は引き続き標準 CPU の割り当てに対してカウントされます。

プリエンプティブル CPU の割り当てがないリージョンでは、標準の CPU の割り当てを使用してプリエンプティブル VM を起動できます。通常どおり、十分な IP とディスクを割り当てる必要があります。プリエンプティブル CPU の割り当ては、Compute Engine が割り当てを付与しない限り、gcloud CLI または Google Cloud コンソールの割り当てページに表示されません。

割り当ての詳細については、リソースの割り当てのページをご覧ください。

プリエンプトされた VM を開始する

他の VM と同様に、プリエンプティブル VM が停止またはプリエンプトされた場合、VM を再起動して、RUNNING 状態に戻すことができます。プリエンプティブル VM を起動すると、24 時間カウンタはリセットされますが、これはまだプリエンプティブル VM であるため、Compute Engine によって 24 時間前にプリエンプトされる場合があります。実行中のプリエンプティブル VM を標準 VM に変換することはできません。

Compute Engine が、自動スケーリングされるマネージド インスタンス グループ(MIG)または Google Kubernetes Engine(GKE)クラスタでプリエンプティブル VM を停止した場合、リソースが再び使用可能になると、グループが VM を再起動します。

シャットダウン スクリプトを使用してプリエンプションを処理する

シャットダウン スクリプトを使用することにより、VM がプリエンプトされる際、VM が停止する前に、クリーンアップ アクションを実行させることができます。たとえば、実行中のプロセスを適切に停止し、チェックポイント ファイルを Cloud Storage にコピーできます。

シャットダウン スクリプトを以下に示します。これは、実行中のプリエンプティブル VM に追加するか、新規作成時にプリエンプティブル VM に追加できます。このスクリプトは、オペレーティング システムの通常の kill コマンドが残りすべてのプロセスを停止する前に、VM がシャットダウンを開始するときに実行されます。目的のプログラムを正常に停止した後、スクリプトは Cloud Storage バケットにチェックポイント ファイルの並列アップロードを実行します。

#!/bin/bash

MY_PROGRAM="[PROGRAM_NAME]" # For example, "apache2" or "nginx"
MY_USER="[LOCAL_USERNAME]"
CHECKPOINT="/home/$MY_USER/checkpoint.out"
GSUTIL_OPTS="-m -o GSUtil:parallel_composite_upload_threshold=32M"
BUCKET_NAME="[BUCKET_NAME]" # For example, "my-checkpoint-files" (without gs://)

echo "Shutting down!  Seeing if ${MY_PROGRAM} is running."

# Find the newest copy of $MY_PROGRAM
PID="$(pgrep -n "$MY_PROGRAM")"

if [[ "$?" -ne 0 ]]; then
  echo "${MY_PROGRAM} not running, shutting down immediately."
  exit 0
fi

echo "Sending SIGINT to $PID"
kill -2 "$PID"

# Portable waitpid equivalent
while kill -0 "$PID"; do
   sleep 1
done

echo "$PID is done, copying ${CHECKPOINT} to gs://${BUCKET_NAME} as ${MY_USER}"

su "${MY_USER}" -c "gsutil $GSUTIL_OPTS cp $CHECKPOINT gs://${BUCKET_NAME}/"

echo "Done uploading, shutting down."

このスクリプトを VM に追加するには、VM 上のアプリケーションと連携するようにスクリプトを構成し、そのスクリプトを VM のメタデータに追加します。

  1. シャットダウン スクリプトをローカル ワークステーションにコピーまたはダウンロードします。
  2. ファイルを開いて編集し、以下の変数を変更します。
    • [PROGRAM_NAME] は、シャットダウンするプロセスまたはプログラムの名前です。たとえば、apache2nginx です。
    • [LOCAL_USER] は、仮想マシンへのログインに使用しているユーザー名です。
    • [BUCKET_NAME] は、プログラムのチェックポイント ファイルを保存する Cloud Storage バケットの名前です。ここではバケット名が gs:// で始まっていないことにご注意ください。
  3. 変更を保存します。
  4. シャットダウン スクリプトを新しい VM または既存の VM に追加します。

このスクリプトの前提条件は次のとおりです。

  • 少なくとも Cloud Storage に対する読み取り / 書き込みアクセス権を付与して VM が作成されていること。適切なスコープを指定して VM を作成する方法については、認証のドキュメントをご覧ください。

  • 既存の Cloud Storage バケットがあり、そのバケットに対する書き込み権限が付与されていること。

プリエンプティブル VM を特定する

VM がプリエンプティブル VM かどうかを確認するには、VM のプロビジョニング モデルと終了アクションを特定する手順を行います。

VM がプリエンプトされたかどうかを確かめる

VM が Google Cloud コンソールgcloud CLI、または API でプリエンプトされたかどうかを確認します。

コンソール

システム アクティビティ ログを確認することで、VM がプリエンプトされたかどうかを確認できます。

  1. Google Cloud コンソールのログページに移動します。

    ログに移動

  2. プロジェクトを選択し、[続行] をクリックします。

  3. [ラベルまたはテキスト検索でフィルタ] フィールドに compute.instances.preempted を追加します。

  4. 特定の VM のプリエンプション オペレーションを確認するには、VM 名を入力することもできます。

  5. Enter キーを押して、指定されたフィルタを適用します。Google Cloud コンソールによってログのリストが更新され、VM がプリエンプトされたオペレーションのみが表示されます。

  6. リストでオペレーションを選択し、プリエンプトされた VM に関する詳細を表示します。

gcloud


プロジェクト内のプリエンプション イベントのリストを取得するには、filter パラメータを指定して gcloud compute operations list コマンドを使用します。

gcloud compute operations list \
    --filter="operationType=compute.instances.preempted"

filter パラメータでは、結果をさらに絞り込めます。たとえば、マネージド インスタンス グループ内の VM のプリエンプション イベントのみを表示するには、次のようにします。

gcloud compute operations list \
    --filter="operationType=compute.instances.preempted AND targetLink:instances/[BASE_VM_NAME]"

gcloud から次のようなレスポンスが返されます。

NAME                  TYPE                         TARGET                                   HTTP_STATUS STATUS TIMESTAMP
systemevent-xxxxxxxx  compute.instances.preempted  us-central1-f/instances/example-vm-xxx  200         DONE   2015-04-02T12:12:10.881-07:00

compute.instances.preempted というオペレーション タイプは、VM がプリエンプトされたことを示します。operations describe コマンドを使用すると、特定のプリエンプション オペレーションに関する詳細情報を取得できます。

gcloud compute operations describe \
    systemevent-xxxxxxxx

gcloud から次のようなレスポンスが返されます。

...
operationType: compute.instances.preempted
progress: 100
selfLink: https://compute.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/us-central1-f/operations/systemevent-xxxxxxxx
startTime: '2015-04-02T12:12:10.881-07:00'
status: DONE
statusMessage: Instance was preempted.
...

API


最近のシステム オペレーションのリストを取得するには、ゾーン オペレーションの URI に GET リクエストを送信します。

GET https://compute.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/operations

レスポンスには、最近のオペレーションのリストが記載されます。

{
  "kind": "compute#operation",
  "id": "15041793718812375371",
  "name": "systemevent-xxxxxxxx",
  "zone": "https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/us-central1-f",
  "operationType": "compute.instances.preempted",
  "targetLink": "https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/us-central1-f/instances/example-vm",
  "targetId": "12820389800990687210",
  "status": "DONE",
  "statusMessage": "Instance was preempted.",
  ...
}

プリエンプション オペレーションのみを表示するようにレスポンスの内容を絞り込むには、API リクエストに operationType="compute.instances.preempted" というフィルタを追加します。特定の VM のプリエンプション オペレーションを表示するには、operationType="compute.instances.preempted" AND targetLink="https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[VM_NAME]" のように targetLink パラメータをフィルタに追加します。

また、VM 自体の内部から VM がプリエンプトされたかどうかを確認することもできます。これは、Compute Engine のプリエンプションのためにシャットダウン スクリプトの通常のシャットダウンとは異なるシャットダウンを処理する場合に便利です。これを行うには、メタデータ サーバーで VM のデフォルトのメタデータに含まれる preempted の値を確認します。

たとえば、VM 内から curl を使用して preempted の値を取得します。

curl "http://metadata.google.internal/computeMetadata/v1/instance/preempted" -H "Metadata-Flavor: Google"
TRUE

この値が TRUE の場合、VM は Compute Engine によってプリエンプトされており、プリエンプトされていない場合は FALSE です。

これをシャットダウン スクリプトの外部で使用する場合は、URL に ?wait_for_change=true を追加します。これにより、HTTP GET リクエストが待機中になり、メタデータが変更されて VM がプリエンプトされたときにのみ結果が返されます。

curl "http://metadata.google.internal/computeMetadata/v1/instance/preempted?wait_for_change=true" -H "Metadata-Flavor: Google"
TRUE

プリエンプション設定をテストする

VM でシミュレート メンテナンス イベントを実行して、強制的にインスタンスをプリエンプトできます。この機能を使用して、アプリによるプリエンプティブル VM の処理方法をテストします。VM でメンテナンス イベントをテストする方法については、可用性ポリシーのテストをご覧ください。

VM を停止して、VM のプリエンプションをシミュレートすることもできます。メンテナンス イベントをシミュレートする代わりに、この操作を行うことで、割り当ての上限に達するのを回避できます。

ベスト プラクティス

以下に、プリエンプティブル VM インスタンスを最大限に活用するために役立ついくつかのおすすめの方法を紹介します。

バルク インスタンス API の使用

単一の VM を作成する代わりに、バルク インスタンス API を使用できます。

より小さいマシンの形を選ぶ

プリエンプティブル VM のリソースは、Google Cloud の予備キャパシティの余剰分から取得されます。多くの場合、より小さなマシンタイプ、つまり vCPU やメモリなどのリソースが少ないマシンタイプの方が容量を取得しやすくなります。より小さいカスタム マシンタイプを選択するとプリエンプティブル VM のキャパシティは高くなる可能性がありますが、事前定義されたマシンタイプの中からより小さいマシンタイプを選んだ方が、キャパシティはより一層高くなる可能性があります。たとえば、事前定義されたマシンタイプ n2-standard-32 の容量と比較して、カスタム マシンタイプ n2-custom-24-96 の容量は増加する可能性が高いですが、事前定義されたマシンタイプ n2-standard-16 の容量はさらに増加する可能性があります。

大きなプリエンプティブル VM クラスタはオフピーク時に実行する

Google Cloud データセンターの負荷は、ロケーションや時間帯によって異なりますが、一般的には夜間と週末に最も低くなります。そのため、大きなプリエンプティブル VM クラスタを実行する時間としては夜間や週末が最適です。

耐障害性と対プリエンプション性を備えたアプリケーションを設計する

時間が異なればプリエンプションのパターンが変化するという事実に備えることが重要です。たとえば、あるゾーンの一部が停止した場合、復旧作業の一環として、標準 VM を移動する余地を作るために多数のプリエンプティブル VM がプリエンプトされる可能性があります。この短い時間のプリエンプション レートは他の日とはまったく異なったものとなります。プリエンプションが常に小さなグループで実行されることを前提としている場合は、そのようなイベントへの対処ができない場合があります。VM インスタンスを停止することによってプリエンプション イベント発生中のアプリケーションの動作をテストできます。

プリエンプトされた VM の作成を再試行する

VM インスタンスがプリエンプトされた場合、標準 VM にフォールバックする前に、新しいプリエンプティブル VM の作成を 1 回または 2 回試行します。実際の要件によっては、クラスタ内で標準 VM とプリエンプティブル VM を組み合わせて適切なペースで作業が続行されるようにすると有効な場合があります。

シャットダウン スクリプトを使用する

ジョブの進行状況を保存できるシャットダウン スクリプトを使用してシャットダウンとプリエンプションの通知を管理すると、最初からやり直す代わりに停止した時点を選択できます。

次のステップ