Spot VM 만들기 및 사용


이 페이지에서는 다음을 포함하여 Spot VM을 만들고 관리하는 방법을 설명합니다.

  • Spot VM 생성, 시작, 식별 방법
  • Spot VM 선점 감지, 처리, 테스트 방법
  • Spot VM 권장사항

스팟 VM은 스팟 프로비저닝 모델을 사용하는 가상 머신(VM) 인스턴스입니다. 스팟 VM은 표준 VM 가격에 비해 최대 60~91% 할인된 가격으로 제공됩니다. 그러나 Compute Engine은 언제든지 스팟 VM을 선점하여 리소스를 회수할 수 있습니다. Spot VM은 VM 선점을 감당할 수 있는 내결함성 애플리케이션에만 권장됩니다. Spot VM을 만들기로 결정하기 전에 애플리케이션이 선점을 처리할 수 있는지 확인합니다.

시작하기 전에

  • 스팟 VM 개념 문서를 읽어봅니다.
  • 아직 인증을 설정하지 않았다면 설정합니다. 인증은 Google Cloud 서비스 및 API에 액세스하기 위해 ID를 확인하는 프로세스입니다. 로컬 개발 환경에서 코드 또는 샘플을 실행하려면 다음과 같이 Compute Engine에 인증하면 됩니다.

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

    Console

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

    gcloud

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

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

      로컬 개발 환경에서 이 페이지의 Terraform 샘플을 사용하려면 gcloud CLI를 설치 및 초기화한 다음 사용자 인증 정보로 애플리케이션 기본 사용자 인증 정보를 설정하세요.

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

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

        gcloud auth application-default login

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

      자세한 내용은 다음을 참조하세요: Set up authentication for a local development environment.

      REST

      로컬 개발 환경에서 이 페이지의 REST API 샘플을 사용하려면 gcloud CLI에 제공하는 사용자 인증 정보를 사용합니다.

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

        gcloud init

      자세한 내용은 Google Cloud 인증 문서의 REST 사용 인증을 참조하세요.

Spot VM 만들기

Google Cloud 콘솔, gcloud CLI, Compute Engine API를 사용하여 Spot VM을 만듭니다. Spot VM은 Spot 프로비저닝 모델을 사용하도록 구성된 모든 VM입니다.

  • Google Cloud 콘솔에서 VM 프로비저닝 모델Spot으로 설정됩니다.
  • gcloud CLI의 --provisioning-model=SPOT
  • Compute Engine API의 "provisioningModel": "SPOT"

콘솔

  1. Google Cloud 콘솔에서 인스턴스 만들기 페이지로 이동합니다.

    인스턴스 만들기로 이동

  2. 그런 후 다음 작업을 수행합니다.

    1. 가용성 정책 섹션의 VM 프로비저닝 모델 목록에서 Spot을 선택합니다. 이 설정은 VM에 대해 자동 재시작 및 호스트 유지보수 옵션을 사용 중지하고 종료 작업 옵션을 사용 설정합니다.
    2. 선택사항: VM 종료 시 목록에서 Compute Engine이 VM을 선점할 때 수행되는 작업을 선택합니다.
      • 선점 중에 VM을 중지하려면 중지(기본값)를 선택합니다.
      • 선점 중에 VM을 삭제하려면 삭제를 선택합니다.
  3. 선택사항: 다른 VM 옵션을 지정합니다. 자세한 내용은 VM 인스턴스 만들기 및 시작을 참조하세요.

  4. 만들기를 클릭하여 VM을 만들고 시작합니다.

gcloud

gcloud CLI에서 VM을 만들려면 gcloud compute instances create 명령어를 사용합니다. Spot VM을 만들려면 --provisioning-model=SPOT 플래그를 포함해야 합니다. 선택적으로 --instance-termination-action 플래그를 포함하여 Spot VM의 종료 작업을 지정할 수도 있습니다.

gcloud compute instances create VM_NAME \
    --provisioning-model=SPOT \
    --instance-termination-action=TERMINATION_ACTION

다음을 바꿉니다.

  • VM_NAME: 새 VM의 이름입니다.
  • TERMINATION_ACTION: (선택사항) Compute Engine이 VM을 선점할 때 수행할 작업을 지정합니다(STOP(기본 동작) 또는 DELETE).

VM을 만들 때 지정할 수 있는 옵션에 대한 자세한 내용은 VM 인스턴스 만들기 및 시작을 참조하세요. 예를 들어 지정된 머신 유형 및 이미지로 Spot VM을 만들려면 다음 명령어를 사용합니다.

gcloud compute instances create VM_NAME \
    --provisioning-model=SPOT \
    [--image=IMAGE | --image-family=IMAGE_FAMILY] \
    --image-project=IMAGE_PROJECT \
    --machine-type=MACHINE_TYPE \
    --instance-termination-action=TERMINATION_ACTION

다음을 바꿉니다.

  • VM_NAME: 새 VM의 이름입니다.
  • IMAGE: 다음 중 하나를 지정합니다.
    • IMAGE: 공개 이미지나 이미지 계열의 특정 버전입니다. 예를 들어 특정 이미지는 --image=debian-10-buster-v20200309입니다.
    • 이미지 계열입니다. 이렇게 하면 지원 중단되지 않은 최신 OS 이미지를 사용하여 VM이 생성됩니다. 예를 들어 --image-family=debian-10을 지정하면 Compute Engine이 Debian 10 이미지 계열에서 최신 버전의 OS 이미지를 사용하여 VM을 만듭니다.
  • IMAGE_PROJECT: 이미지가 포함된 프로젝트입니다. 예를 들어 debian-10을 이미지 계열로 지정하면 debian-cloud를 이미지 프로젝트로 지정합니다.
  • MACHINE_TYPE: 새 VM의 머신 유형입니다(사전 정의 또는 커스텀).
  • TERMINATION_ACTION: (선택사항) Compute Engine이 VM을 선점할 때 수행할 작업을 지정합니다(STOP(기본 동작) 또는 DELETE).

    영역에서 사용 가능한 머신 유형 목록을 보려면 --zones 플래그와 함께 gcloud compute machine-types list 명령어를 사용합니다.

Terraform

Terraform 리소스를 사용해서 예약 블록을 사용하여 Spot 인스턴스를 만듭니다.


resource "google_compute_instance" "spot_vm_instance" {
  name         = "spot-instance-name"
  machine_type = "f1-micro"
  zone         = "us-central1-c"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  scheduling {
    preemptible                 = true
    automatic_restart           = false
    provisioning_model          = "SPOT"
    instance_termination_action = "STOP"
  }

  network_interface {
    # A default network is created for all GCP projects
    network = "default"
    access_config {
    }
  }
}

REST

Compute Engine API에서 VM을 만들려면 instances.insert 메서드를 사용합니다. VM의 머신 유형과 이름을 지정해야 합니다. 필요할 경우 부팅 디스크의 이미지를 지정할 수도 있습니다.

Spot VM을 만들려면 "provisioningModel": spot 필드를 포함해야 합니다. 선택적으로 "instanceTerminationAction" 필드를 포함하여 Spot VM의 종료 작업을 지정할 수도 있습니다.

POST https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances
{
 "machineType": "zones/ZONE/machineTypes/MACHINE_TYPE",
 "name": "VM_NAME",
 "disks": [
   {
     "initializeParams": {
       "sourceImage": "projects/IMAGE_PROJECT/global/images/IMAGE"
     },
     "boot": true
   }
 ]
 "scheduling":
 {
     "provisioningModel": "SPOT",
     "instanceTerminationAction": "TERMINATION_ACTION"
 },
 ...
}

다음을 바꿉니다.

  • PROJECT_ID: VM을 만들 프로젝트 ID입니다.
  • ZONE: VM을 만들 영역입니다. 또한 영역은 새 VM에 사용할 머신 유형을 지원해야 합니다.
  • MACHINE_TYPE: 새 VM의 머신 유형입니다(사전 정의 또는 커스텀).
  • VM_NAME: 새 VM의 이름입니다.
  • IMAGE_PROJECT: 이미지가 포함된 프로젝트입니다. 예를 들어 family/debian-10을 이미지 계열로 지정하면 debian-cloud를 이미지 프로젝트로 지정합니다.
  • IMAGE: 다음 중 하나를 지정합니다.
    • 공개 이미지의 특정 버전입니다. 예를 들어 특정 이미지는 "sourceImage": "projects/debian-cloud/global/images/debian-10-buster-v20200309"이며 여기서 debian-cloudIMAGE_PROJECT입니다.
    • 이미지 계열입니다. 이렇게 하면 지원 중단되지 않은 최신 OS 이미지를 사용하여 VM이 생성됩니다. 예를 들어 "sourceImage": "projects/debian-cloud/global/images/family/debian-10"을 지정하고 debian-cloudIMAGE_PROJECT이면 Compute Engine은 Debian 10 이미지 계열에서 최신 버전의 OS 이미지를 사용하여 VM을 만듭니다.
  • TERMINATION_ACTION: (선택사항) Compute Engine이 VM을 선점할 때 수행할 작업을 지정합니다(STOP(기본 동작) 또는 DELETE).

VM을 만들 때 지정할 수 있는 옵션에 대한 자세한 내용은 VM 인스턴스 만들기 및 시작을 참조하세요.

Go


import (
	"context"
	"fmt"
	"io"

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

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

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

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

	req := &computepb.GetFromFamilyImageRequest{
		Project: "debian-cloud",
		Family:  "debian-11",
	}

	image, err := imagesClient.GetFromFamily(ctx, req)
	if err != nil {
		return fmt.Errorf("getImageFromFamily: %w", err)
	}

	diskType := fmt.Sprintf("zones/%s/diskTypes/pd-standard", zone)
	disks := []*computepb.AttachedDisk{
		{
			AutoDelete: proto.Bool(true),
			Boot:       proto.Bool(true),
			InitializeParams: &computepb.AttachedDiskInitializeParams{
				DiskSizeGb:  proto.Int64(10),
				DiskType:    proto.String(diskType),
				SourceImage: proto.String(image.GetSelfLink()),
			},
			Type: proto.String(computepb.AttachedDisk_PERSISTENT.String()),
		},
	}

	req2 := &computepb.InsertInstanceRequest{
		Project: projectID,
		Zone:    zone,
		InstanceResource: &computepb.Instance{
			Name:        proto.String(instanceName),
			Disks:       disks,
			MachineType: proto.String(fmt.Sprintf("zones/%s/machineTypes/%s", zone, "n1-standard-1")),
			NetworkInterfaces: []*computepb.NetworkInterface{
				{
					Name: proto.String("global/networks/default"),
				},
			},
			Scheduling: &computepb.Scheduling{
				ProvisioningModel: proto.String(computepb.Scheduling_SPOT.String()),
			},
		},
	}
	op, err := instancesClient.Insert(ctx, req2)
	if err != nil {
		return fmt.Errorf("insert: %w", err)
	}

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

	instance, err := instancesClient.Get(ctx, &computepb.GetInstanceRequest{
		Project:  projectID,
		Zone:     zone,
		Instance: instanceName,
	})

	if err != nil {
		return fmt.Errorf("createInstance: %w", err)
	}

	fmt.Fprintf(w, "Instance created: %v\n", instance)
	return nil
}

자바


import com.google.cloud.compute.v1.AccessConfig;
import com.google.cloud.compute.v1.AccessConfig.Type;
import com.google.cloud.compute.v1.Address.NetworkTier;
import com.google.cloud.compute.v1.AttachedDisk;
import com.google.cloud.compute.v1.AttachedDiskInitializeParams;
import com.google.cloud.compute.v1.ImagesClient;
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.Scheduling;
import com.google.cloud.compute.v1.Scheduling.ProvisioningModel;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreateSpotVm {
  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 Google Cloud project you want to use.
    String projectId = "your-project-id";
    // Name of the virtual machine to check.
    String instanceName = "your-instance-name";
    // Name of the zone you want to use. For example: "us-west3-b"
    String zone = "your-zone";

    createSpotInstance(projectId, instanceName, zone);
  }

  // Create a new Spot VM instance with Debian 11 operating system.
  public static Instance createSpotInstance(String projectId, String instanceName, String zone)
          throws IOException, ExecutionException, InterruptedException, TimeoutException {
    String image;
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests.
    try (ImagesClient imagesClient = ImagesClient.create()) {
      image = imagesClient.getFromFamily("debian-cloud", "debian-11").getSelfLink();
    }
    AttachedDisk attachedDisk = buildAttachedDisk(image, zone);
    String machineTypes = String.format("zones/%s/machineTypes/%s", zone, "n1-standard-1");

    // Send an instance creation request to the Compute Engine API and wait for it to complete.
    Instance instance =
            createInstance(projectId, zone, instanceName, attachedDisk, true, machineTypes, false);

    System.out.printf("Spot instance '%s' has been created successfully", instance.getName());

    return 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"
  // external_access: boolean flag indicating if the instance should have an external IPv4
  //     address assigned.
  // spot: boolean value indicating if the new instance should be a Spot VM or not.
  private static Instance createInstance(String projectId, String zone, String instanceName,
                                         AttachedDisk disk, boolean isSpot, String machineType,
                                         boolean externalAccess)
          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.
    try (InstancesClient client = InstancesClient.create()) {
      Instance instanceResource =
              buildInstanceResource(instanceName, disk, machineType, externalAccess, isSpot);

      InsertInstanceRequest build = InsertInstanceRequest.newBuilder()
              .setProject(projectId)
              .setRequestId(UUID.randomUUID().toString())
              .setZone(zone)
              .setInstanceResource(instanceResource)
              .build();
      client.insertCallable().futureCall(build).get(60, TimeUnit.SECONDS);

      return client.get(projectId, zone, instanceName);
    }
  }

  private static Instance buildInstanceResource(String instanceName, AttachedDisk disk,
                                                String machineType, boolean externalAccess,
                                                boolean isSpot) {
    NetworkInterface networkInterface =
            networkInterface(externalAccess);
    Instance.Builder builder = Instance.newBuilder()
            .setName(instanceName)
            .addDisks(disk)
            .setMachineType(machineType)
            .addNetworkInterfaces(networkInterface);

    if (isSpot) {
      // Set the Spot VM setting
      Scheduling.Builder scheduling = builder.getScheduling()
              .toBuilder()
              .setProvisioningModel(ProvisioningModel.SPOT.name())
              .setInstanceTerminationAction("STOP");
      builder.setScheduling(scheduling);
    }

    return builder.build();
  }

  private static NetworkInterface networkInterface(boolean externalAccess) {
    NetworkInterface.Builder build = NetworkInterface.newBuilder()
            .setNetwork("global/networks/default");

    if (externalAccess) {
      AccessConfig.Builder accessConfig = AccessConfig.newBuilder()
              .setType(Type.ONE_TO_ONE_NAT.name())
              .setName("External NAT")
              .setNetworkTier(NetworkTier.PREMIUM.name());
      build.addAccessConfigs(accessConfig.build());
    }

    return build.build();
  }

  private static AttachedDisk buildAttachedDisk(String sourceImage, String zone) {
    AttachedDiskInitializeParams initializeParams = AttachedDiskInitializeParams.newBuilder()
            .setSourceImage(sourceImage)
            .setDiskSizeGb(10)
            .setDiskType(String.format("zones/%s/diskTypes/pd-standard", zone))
            .build();
    return AttachedDisk.newBuilder()
            .setInitializeParams(initializeParams)
            // Remember to set auto_delete to True if you want the disk to be deleted
            // when you delete your VM instance.
            .setAutoDelete(true)
            .setBoot(true)
            .build();
  }
}

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_spot_instance(
    project_id: str, zone: str, instance_name: str
) -> compute_v1.Instance:
    """
    Create a new Spot 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, spot=True)
    return instance

속성이 동일한 여러 Spot VM을 만들려면 인스턴스 템플릿을 만들고 이 템플릿을 사용하여 관리형 인스턴스 그룹(MIG)을 만들 수 있습니다. 자세한 내용은 권장사항을 참조하세요.

Spot VM 시작

다른 VM과 마찬가지로 Spot VM은 생성 시 시작됩니다. 마찬가지로 Spot VM이 중지되면 VM을 다시 시작하여 RUNNING 상태를 재개할 수 있습니다. 용량이 있는 한 선점된 Spot VM을 원하는 만큼 중지했다 다시 시작할 수 있습니다. 자세한 내용은 VM 인스턴스 수명 주기를 참조하세요.

자동 확장 관리형 인스턴스 그룹(MIG) 또는 Google Kubernetes Engine(GKE) 클러스터에서 Compute Engine이 하나 이상의 Spot VM을 중지하면 리소스를 다시 사용할 수 있게 되었을 때 그룹이 VM을 다시 시작합니다.

VM의 프로비저닝 모델 및 종료 작업 식별

VM의 프로비저닝 모델을 식별하여 표준 VM, 스팟 VM, 선점형 VM인지 확인합니다. 스팟 VM의 경우 종료 작업을 식별할 수도 있습니다. Google Cloud 콘솔, gcloud CLI 또는 Compute Engine API를 사용하여 VM의 프로비저닝 모델과 종료 작업을 식별할 수 있습니다.

콘솔

  1. VM 인스턴스 페이지로 이동합니다.

    VM 인스턴스 페이지로 이동

  2. 식별하려는 VM의 이름을 클릭합니다. VM 인스턴스 세부정보 페이지가 열립니다.

  3. 페이지 하단의 관리 섹션으로 이동합니다. 가용성 정책 하위 섹션에서 다음 옵션을 선택합니다.

    • VM 프로비저닝 모델Spot으로 설정되었으면 VM이 Spot VM입니다.
      • VM 종료 시는 VM 중지 또는 삭제와 같이 Compute Engine이 VM을 선점할 때 수행할 작업을 나타냅니다.
    • 그렇지 않으면 VM 프로비저닝 모델표준 또는 로 설정됩니다.
      • 선점 옵션이 켜기로 설정되었으면 VM이 선점형 VM입니다.
      • 그렇지 않으면 VM이 표준 VM입니다.

gcloud

gcloud CLI에서 VM을 설명하려면 gcloud compute instances describe 명령어를 사용합니다.

gcloud compute instances describe VM_NAME

여기서 VM_NAME은 확인할 VM의 이름입니다.

출력에서 scheduling 필드를 확인하여 VM을 식별합니다.

  • 다음과 비슷하게 출력에 SPOT으로 설정된 provisioningModel 필드가 포함된 경우에는 VM이 Spot VM입니다.

    ...
    scheduling:
    ...
    provisioningModel: SPOT
    instanceTerminationAction: TERMINATION_ACTION
    ...
    

    여기에서 TERMINATION_ACTION은 VM 중지(STOP) 또는 삭제(DELETE)와 같이Compute Engine이 VM을 선점할 때 수행할 작업을 나타냅니다. instanceTerminationAction 필드가 누락된 경우에는 기본값이 STOP입니다.

  • 그렇지 않고, 출력에 standard로 설정된 provisioningModel 필드가 포함되었거나 출력에서 provisioningModel 필드가 생략된 경우에는 다음이 적용됩니다.

    • 출력에 true로 설정된 preemptible 필드가 포함되었으면 VM이 선점형 VM입니다.
    • 그렇지 않으면 VM이 표준 VM입니다.

REST

Compute Engine API에서 VM을 설명하려면 instances.get 메서드를 사용합니다.

GET https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/VM_NAME

다음을 바꿉니다.

  • PROJECT_ID: VM이 속한 프로젝트의 프로젝트 ID입니다.
  • ZONE: VM이 있는 영역입니다.
  • VM_NAME: 확인할 VM의 이름입니다.

출력에서 scheduling 필드를 확인하여 VM을 식별합니다.

  • 다음과 비슷하게 출력에 SPOT으로 설정된 provisioningModel 필드가 포함된 경우에는 VM이 Spot VM입니다.

    {
      ...
      "scheduling":
      {
         ...
         "provisioningModel": "SPOT",
         "instanceTerminationAction": "TERMINATION_ACTION"
         ...
      },
      ...
    }
    

    여기에서 TERMINATION_ACTION은 VM 중지(STOP) 또는 삭제(DELETE)와 같이Compute Engine이 VM을 선점할 때 수행할 작업을 나타냅니다. instanceTerminationAction 필드가 누락된 경우에는 기본값이 STOP입니다.

  • 그렇지 않고, 출력에 standard로 설정된 provisioningModel 필드가 포함되었거나 출력에서 provisioningModel 필드가 생략된 경우에는 다음이 적용됩니다.

    • 출력에 true로 설정된 preemptible 필드가 포함되었으면 VM이 선점형 VM입니다.
    • 그렇지 않으면 VM이 표준 VM입니다.

Go


import (
	"context"
	"fmt"
	"io"

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

// isSpotVM checks if a given instance is a Spot VM or not.
func isSpotVM(w io.Writer, projectID, zone, instanceName string) (bool, error) {
	// projectID := "your_project_id"
	// zone := "europe-central2-b"
	// instanceName := "your_instance_name"
	ctx := context.Background()
	client, err := compute.NewInstancesRESTClient(ctx)
	if err != nil {
		return false, fmt.Errorf("NewInstancesRESTClient: %w", err)
	}
	defer client.Close()

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

	instance, err := client.Get(ctx, req)
	if err != nil {
		return false, fmt.Errorf("GetInstance: %w", err)
	}

	isSpot := instance.GetScheduling().GetProvisioningModel() == computepb.Scheduling_SPOT.String()

	var isSpotMessage string
	if !isSpot {
		isSpotMessage = " not"
	}
	fmt.Fprintf(w, "Instance %s is%s spot\n", instanceName, isSpotMessage)

	return instance.GetScheduling().GetProvisioningModel() == computepb.Scheduling_SPOT.String(), nil
}

자바


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

public class CheckIsSpotVm {
  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 Google Cloud project you want to use.
    String projectId = "your-project-id";
    // Name of the virtual machine to check.
    String instanceName = "your-route-name";
    // Name of the zone you want to use. For example: "us-west3-b"
    String zone = "your-zone";

    boolean isSpotVm = isSpotVm(projectId, instanceName, zone);
    System.out.printf("Is %s spot VM instance - %s", instanceName, isSpotVm);
  }

  // Check if a given instance is Spot VM or not.
  public static boolean isSpotVm(String projectId, String instanceName, String zone)
          throws IOException {
    // Initialize client that will be used to send requests. This client only needs to be created
    // once, and can be reused for multiple requests.
    try (InstancesClient client = InstancesClient.create()) {
      Instance instance = client.get(projectId, zone, instanceName);

      return instance.getScheduling().getProvisioningModel()
              .equals(Scheduling.ProvisioningModel.SPOT.name());
    }
  }
}

Python

from google.cloud import compute_v1


def is_spot_vm(project_id: str, zone: str, instance_name: str) -> bool:
    """
    Check if a given instance is Spot VM or not.
    Args:
        project_id: 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"
        instance_name: name of the virtual machine to check.
    Returns:
        The Spot VM status of the instance.
    """
    instance_client = compute_v1.InstancesClient()
    instance = instance_client.get(
        project=project_id, zone=zone, instance=instance_name
    )
    return (
        instance.scheduling.provisioning_model
        == compute_v1.Scheduling.ProvisioningModel.SPOT.name
    )

종료 스크립트로 선점 처리

Compute Engine이 스팟 VM을 선점하면 종료 스크립트를 사용하여 VM이 선점되기 전에 정리 작업을 수행할 수 있습니다. 예를 들어 실행 중인 프로세스를 정상적으로 중지하고 체크포인트 파일을 Cloud Storage에 복사할 수 있습니다. 특히 종료 기간의 최대 시간은 사용자가 시작한 종료보다 선점 알림에서 더 짧습니다. 선점 알림의 종료 기간에 관한 자세한 내용은 스팟 VM 개념 문서의 선점 프로세스를 참조하세요.

다음은 실행 중인 스팟 VM에 추가하거나 새 스팟 VM을 만드는 동안 추가할 수 있는 종료 스크립트의 예시입니다. 이 스크립트는 운영체제의 일반 kill 명령어가 나머지 프로세스를 모두 중지하기 전 VM이 종료되기 시작할 때 실행됩니다. 원하는 프로그램을 정상적으로 중지한 후 스크립트는 Cloud Storage 버킷에 체크포인트 파일을 동시에 업로드합니다.

#!/bin/bash

MY_PROGRAM="PROGRAM_NAME" # For example, "apache2" or "nginx"
MY_USER="LOCAL_USER"
CHECKPOINT="/home/$MY_USER/checkpoint.out"
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 "gcloud storage cp $CHECKPOINT gs://${BUCKET_NAME}/"

echo "Done uploading, shutting down."

이 스크립트는 다음을 가정합니다.

  • VM이 Cloud Storage에 대한 최소한의 읽기/쓰기 권한으로 생성되었습니다. 적절한 범위로 VM을 만드는 방법에 대한 안내는 인증 문서를 참조하세요.

  • 기존 Cloud Storage 버킷과 이에 대한 쓰기 권한이 있습니다.

이 스크립트를 VM에 추가하려면 VM의 애플리케이션에서 작동하도록 스크립트를 구성한 후 VM 메타데이터에 추가합니다.

  1. 종료 스크립트를 복사하거나 다운로드합니다.

    • 다음을 바꾼 후 선행 종료 스크립트를 복사합니다.

      • PROGRAM_NAME은 종료할 프로세스나 프로그램의 이름입니다. 예를 들면 apache2 또는 nginx입니다.
      • LOCAL_USER는 가상 머신에 로그인할 때 사용한 사용자 이름입니다.
      • BUCKET_NAME은 프로그램의 체크포인트 파일을 저장할 Cloud Storage 버킷의 이름입니다. 이 경우 버킷 이름은 gs://로 시작되지 않습니다.
    • 로컬 워크스테이션에 종료 스크립트를 다운로드한 후 파일에서 다음 변수를 바꿉니다.

      • [PROGRAM_NAME]은 종료할 프로세스나 프로그램의 이름입니다. 예를 들면 apache2 또는 nginx입니다.
      • [LOCAL_USER]는 가상 머신에 로그인할 때 사용한 사용자 이름입니다.
      • [BUCKET_NAME]은 프로그램의 체크포인트 파일을 저장할 Cloud Storage 버킷의 이름입니다. 이 경우 버킷 이름은 gs://로 시작되지 않습니다.
  2. 새 VM이나 기존 VM에 종료 스크립트를 추가합니다.

Spot VM 선점 감지

Google Cloud 콘솔, gcloud CLI 또는 Compute Engine API를 사용하여 Compute Engine에 의해 Spot VM이 선점되었는지 확인하세요.

콘솔

시스템 활동 로그를 점검하여 VM이 선점되었는지 확인할 수 있습니다.

  1. Google Cloud 콘솔에서 로그 페이지로 이동합니다.

    로그로 이동

  2. 프로젝트를 선택하고 계속을 클릭합니다.

  3. 라벨별 필터링 또는 텍스트 검색 필드에 compute.instances.preempted를 추가합니다.

  4. 선택적으로 특정 VM의 선점 작업을 보려면 VM 이름을 입력해도 됩니다.

  5. Enter 키를 눌러 지정한 필터를 적용합니다. VM이 선점된 작업만 표시하도록 Google Cloud 콘솔의 로그 목록이 업데이트됩니다.

  6. 선점된 VM에 대한 세부정보를 보려면 목록에서 작업을 선택합니다.

gcloud

gcloud compute operations list 명령어filter 매개변수와 함께 사용하여 프로젝트의 선점 이벤트 목록을 가져올 수 있습니다.

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

필요한 경우 추가 필터 매개변수를 사용하여 결과의 범위를 지정할 수 있습니다. 예를 들어 관리형 인스턴스 그룹 내의 인스턴스에 대한 선점 이벤트만 확인하려면 다음 명령어를 사용합니다.

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

여기서 BASE_INSTANCE_NAME은 이 관리형 인스턴스 그룹에 있는 모든 VM 이름의 프리픽스로 지정된 기본 이름입니다.

출력은 다음과 비슷합니다.

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

작업 유형이 compute.instances.preempted이면 VM 인스턴스가 선점된 것입니다. gcloud compute operations describe 명령어를 사용하여 특정 선점 작업에 대한 자세한 정보를 볼 수 있습니다.

gcloud compute operations describe SYSTEM_EVENT \
    --zone=ZONE

다음을 바꿉니다.

  • SYSTEM_EVENT: gcloud compute operations list 명령어 출력의 시스템 이벤트입니다(예: systemevent-xxxxxxxx).
  • ZONE: 시스템 이벤트의 영역입니다(예: us-central1-f).

출력은 다음과 비슷합니다.

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

REST

특정 프로젝트 및 영역의 최근 시스템 작업 목록을 가져오려면 zoneOperations.get 메서드를 사용합니다.

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

다음을 바꿉니다.

선택 사항으로, 선점 작업만 표시하도록 응답 범위를 지정하려면 다음과 같이 API 요청에 필터를 추가합니다.

operationType="compute.instances.preempted"

또는 특정 VM의 선점 작업을 확인하려면 필터에 targetLink 매개변수를 추가합니다.

operationType="compute.instances.preempted" AND
targetLink="https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/VM_NAME

다음을 바꿉니다. + PROJECT_ID: 프로젝트 ID + ZONE: 영역 + VM_NAME: 이 영역 및 프로젝트에 있는 특정 VM의 이름

응답에는 최근 작업 목록이 포함됩니다. 예를 들어 선점은 다음과 유사합니다.

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

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를 추가하면 됩니다. 그러면 메타데이터가 변경되고 VM이 선점된 경우에만 반환되는 중지된 HTTP GET 요청이 수행됩니다.

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

선점 설정 테스트하는 방법

VM에서 시뮬레이션된 유지보수 이벤트를 실행해 선점을 강제로 적용해 볼 수 있습니다. 이 기능을 사용하여 앱이 Spot VM을 처리하는 방법을 테스트합니다. 인스턴스에서 유지보수 이벤트를 테스트하는 방법은 호스트 유지보수 이벤트 시뮬레이션을 참조하세요.

또한 VM 인스턴스를 중지하여 VM 선점을 시뮬레이션할 수도 있습니다. 이 방법은 유지보수 이벤트를 시뮬레이션하는 대신 사용할 수 있으며 할당량 제한을 피할 수 있습니다.

권장사항

다음은 Spot VM을 최대한 활용하는 데 도움이 되는 몇 가지 권장사항입니다.

  • 인스턴스 템플릿 사용. Spot VM을 한 번에 하나씩 만드는 대신 인스턴스 템플릿을 사용하여 동일한 속성으로 여러 Spot VM을 만들 수 있습니다. 인스턴스 템플릿은 MIG를 사용하기 위해 필요합니다. 또는 일괄 인스턴스 API를 사용하여 여러 Spot VM을 만들 수 있습니다.

  • MIG를 사용하여 Spot VM 리전별 배포 및 자동으로 다시 만들기. MIG를 사용하여 Spot VM의 워크로드에 대한 유연성 및 복원력을 높일 수 있습니다. 예를 들어 리전 MIG를 사용해서 여러 영역 간에 VM을 배포하여 리소스 가용성 오류를 해결할 수 있습니다. 또한 자동 복구를 사용해서 선점 후 Spot VM을 자동으로 다시 만들 수 있습니다.

  • 더 작은 머신 유형 선택. Spot VM용 리소스는 초과 및 백업 Google Cloud 용량에서 나옵니다. Spot VM 용량은 머신 유형이 작을수록 즉, vCPU 및 메모리와 같은 리소스가 작을수록 더 쉽게 가져올 수 있습니다. 더 작은 커스텀 머신 유형을 선택하여 Spot VM 용량을 더 많이 찾을 수 있지만 사전 정의된 머신 유형이 더 작을수록 용량이 더 클 수 있습니다. 예를 들어 n2-standard-32 사전 정의된 머신 유형의 용량과 비교할 때 n2-custom-24-96 커스텀 머신 유형의 용량이 더 클 수 있지만 n2-standard-16 사전 정의된 머신 유형은 용량이 이보다 더 클 수 있습니다.

  • 사용량이 적을 때 Spot VM의 대규모 클러스터 실행 Google Cloud 데이터 센터의 부하는 위치와 시간에 따라 다르지만 일반적으로 야간과 주말에 가장 낮습니다. 따라서 Spot VM의 대규모 클러스터를 실행하기에 가장 좋은 시간은 야간과 주말입니다.

  • 결함 및 선점을 허용하도록 애플리케이션 설계 선점 패턴이 다른 시점에서 변경될 것이라는 사실에 대비하는 것이 중요합니다. 예를 들어, 영역이 부분적으로 중단되는 경우 복구 과정에서 이동해야 하는 표준 VM을 위한 공간을 확보하기 위해 많은 수의 Spot VM을 선점할 수 있습니다. 이 짧은 기간 동안의 선점률은 여느 날과 매우 달라 보입니다. 애플리케이션에서 선점이 항상 작은 그룹으로 이루어진다고 가정할 경우 이러한 이벤트에 대한 준비가 되어 있지 않을 수 있습니다.

  • 선점된 Spot VM 만들기 재시도 Spot VM이 선점되었으면 표준 VM으로 돌아가기 전에 새 Spot VM 만들기를 1~2회 시도해 봅니다. 요구사항에 따라 클러스터에서 표준 VM과 Spot VM을 결합하여 작업이 적절한 속도로 진행되도록 하는 것이 좋습니다.

  • 종료 스크립트 사용 처음부터 다시 시작하지 않고 중단한 부분부터 작업을 다시 진행할 수 있도록 작업 진행 상황을 저장할 수 있는 종료 스크립트로 종료 및 선점 알림을 관리합니다.

다음 단계