Crear y usar VM Spot


En esta página, se explica cómo crear y administrar VM Spot, lo que incluye lo siguiente:

  • Cómo crear, iniciar e identificar VM Spot
  • Cómo detectar, manejar y probar la interrupción de las VM Spot
  • Prácticas recomendadas para las VM Spot

Las VM Spot son instancias de máquina virtual (VM) con el modelo de aprovisionamiento spot. Las VM Spot están disponibles con un descuento de hasta el 60% al 91% en comparación con el precio de las VMs estándar. Sin embargo, Compute Engine puede reclamar los recursos interrumpiendo las VMs Spot en cualquier momento. Las VM Spot se recomiendan solo para aplicaciones tolerantes a errores que pueden soportar la interrupción de la VM. Asegúrate de que tu aplicación pueda controlar la interrupción antes de decidir crear VM Spot.

Antes de comenzar

  • Lee la documentación conceptual de las VMs Spot:
    • Revisa las limitaciones y los precios de las VM Spot.
    • Si quieres evitar que las VM Spot consuman las cuotas de las CPU, GPU y discos estándar de las VM, considera solicitar una cuota interrumpible para las VM Spot.
  • Si aún no lo hiciste, configura la autenticación. La autenticación es el proceso mediante el cual se verifica tu identidad para acceder a los servicios y las API de Google Cloud. Para ejecutar código o muestras desde un entorno de desarrollo local, puedes autenticarte en Compute Engine seleccionando una de las siguientes opciones:

    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

      Para usar las muestras de Terraform de esta página en un entorno de desarrollo local, instala e inicializa gcloud CLI y, luego, configura las credenciales predeterminadas de la aplicación con tus credenciales de usuario.

      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.

      Para obtener más información, consulta Set up authentication for a local development environment.

      REST

      Para usar las muestras de la API de REST en esta página en un entorno de desarrollo local, debes usar las credenciales que proporcionas a la CLI de gcloud.

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

        gcloud init

      Si deseas obtener más información, consulta Autentica para usar REST en la documentación de autenticación de Google Cloud.

Crea una VM Spot

Crea una VM Spot con la consola de Google Cloud, la CLI de gcloud o la API de Compute Engine. Una VM Spot es cualquier VM que esté configurada para usar el modelo de aprovisionamiento spot:

  • El modelo de aprovisionamiento de VM configurado como Spot en la consola de Google Cloud
  • --provisioning-model=SPOT en la CLI de gcloud
  • "provisioningModel": "SPOT" en la API de Compute Engine

Console

  1. En la consola de Google Cloud, ve a la página Crea una instancia.

    Ir a Crear una instancia

  2. A continuación, sigue estos pasos:

    1. En la sección Políticas de disponibilidad, selecciona Spot de la lista Modelo de aprovisionamiento de VM. Esta configuración inhabilita las opciones de reinicio automático y mantenimiento del host de la VM y habilita la opción de acción de finalización.
    2. Opcional: En la lista Cuando se finaliza la VM, selecciona lo que sucede cuando Compute Engine interrumpe la VM:
      • Para detener la VM durante la interrupción, selecciona Detener (predeterminado).
      • Para borrar la VM durante la interrupción, selecciona Borrar.
  3. Opcional: Especifica otras opciones de VM. Para obtener más información, consulta Crea e inicia una instancia de VM.

  4. Para crear y, también, iniciar la VM, haz clic en Crear.

gcloud

Para crear una VM a partir de la CLI de gcloud, usa el comando gcloud compute instances create. Para crear las Spot VM, debes incluir la marca --provisioning-model=SPOT. De manera opcional, también puedes especificar una acción de finalización para las VM Spot si incluyes también la marca --instance-termination-action.

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

Reemplaza lo siguiente:

  • VM_NAME: Nombre de la VM nueva.
  • TERMINATION_ACTION: Opcional: Especifica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea STOP (comportamiento predeterminado) o DELETE.

Para obtener más información sobre las opciones que puedes especificar cuando creas una VM, consulta Crea y, luego, inicia una instancia de VM. Por ejemplo, para crear VM Spot con una imagen y un tipo de máquina especificados, usa el siguiente comando:

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

Reemplaza lo siguiente:

  • VM_NAME: Nombre de la VM nueva.
  • IMAGE: Especifica una de las siguientes opciones:
    • IMAGE: Es una versión específica de una imagen pública o la familia de imágenes. Por ejemplo, una imagen específica es --image=debian-10-buster-v20200309.
    • Es una familia de imágenes. Esto crea la VM a partir de la imagen de SO no obsoleta más reciente. Por ejemplo, si especificas --image-family=debian-10, Compute Engine crea una VM a partir de la última versión de la imagen de SO en la familia de imágenes Debian 10.
  • IMAGE_PROJECT: es el proyecto que contiene la imagen. Por ejemplo, si especificas debian-10 como familia de imágenes, especifica debian-cloud como proyecto de imagen.
  • MACHINE_TYPE: Es el tipo de máquina predefinido o personalizado de la VM nueva.
  • TERMINATION_ACTION: Opcional: Especifica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea STOP (comportamiento predeterminado) o DELETE.

    Para obtener una lista de los tipos de máquinas disponibles en una zona, usa el comando gcloud compute machine-types list con la marca --zones.

Terraform

Puedes usar un recurso de Terraform para crear una instancia spot con un bloque de programación.


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

Para crear una VM desde la API de Compute Engine, usa el método instances.insert: Debes especificar un tipo de máquina y un nombre para la VM. De manera opcional, también puedes especificar una imagen para el disco de arranque.

Para crear VM Spot, debes incluir el campo "provisioningModel": spot. De manera opcional, también puedes especificar una acción de finalización para las VM Spot si incluyes también el campo "instanceTerminationAction".

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"
 },
 ...
}

Reemplaza lo siguiente:

  • PROJECT_ID: El ID del proyecto en el que se creará la VM.
  • ZONE: La zona en la que se creará la VM. La zona también debe admitir el tipo de máquina que se usará para la VM nueva.
  • MACHINE_TYPE: Es el tipo de máquina predefinido o personalizado de la VM nueva.
  • VM_NAME: es el nombre de la VM nueva.
  • IMAGE_PROJECT: El proyecto que contiene la imagen. Por ejemplo, si especificas family/debian-10 como familia de imágenes, especifica debian-cloud como proyecto de imagen.
  • IMAGE: Especifica una de las siguientes opciones:
    • Una versión específica de una imagen pública Por ejemplo, una imagen específica es "sourceImage": "projects/debian-cloud/global/images/debian-10-buster-v20200309", en la que debian-cloud es IMAGE_PROJECT.
    • Es una familia de imágenes. Esto crea la VM a partir de la imagen de SO no obsoleta más reciente. Por ejemplo, si especificas "sourceImage": "projects/debian-cloud/global/images/family/debian-10" en el que debian-cloud es IMAGE_PROJECT, Compute Engine crea una VM a partir de la versión más reciente de la familia de imágenes de Debian 10.
  • TERMINATION_ACTION: Opcional: Especifica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea STOP (comportamiento predeterminado) o DELETE.

Para obtener más información sobre las opciones que puedes especificar cuando creas una VM, consulta Crea y, luego, inicia una instancia de 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
}

Java


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

Para crear varias VM Spot con las mismas propiedades, puedes crear una plantilla de instancias y usa la plantilla para crear un grupo de instancias administrado (MIG). Para obtener más información, consulta las prácticas recomendadas.

Inicia VM Spot

Al igual que otras VM, las VM Spot se inician cuando se crean. Del mismo modo, si se detienen las VM Spot, puedes reiniciar las VM para reanudar el estado RUNNING. Puedes detener y reiniciar las VM Spot interrumpidas tantas veces como desees, siempre que haya capacidad. Para obtener más información, consulta Ciclo de vida de las instancias de VM.

Si Compute Engine detiene una o más VM Spot en un grupo de instancias administrado (MIG) con ajuste de escala automático o un clúster de Google Kubernetes Engine (GKE), el grupo reinicia las VM cuando los recursos vuelven a estar disponibles.

Identifica el modelo de aprovisionamiento y la acción de finalización de una VM

Identifica el modelo de aprovisionamiento de una VM para ver si es una VM estándar, una VM Stock o una VM interrumpible. Para una VM Spot, también puedes identificar la acción de finalización. Puedes identificar el modelo de aprovisionamiento y la acción de finalización de una VM mediante la consola de Google Cloud, la CLI de gcloud o la API de Compute Engine.

Console

  1. Ve a la página Instancias de VM.

    Ir a la página Instancias de VM

  2. Haz clic en el nombre de la VM que deseas identificar. Se abrirá la página Detalles de instancia de VM.

  3. Ve a la sección Administración en la parte inferior de la página. En la subsección Políticas de disponibilidad, verifica las siguientes opciones:

    • Si el modelo de aprovisionamiento de VM está configurado como Spot, la VM es Spot.
      • Cuando se finaliza la VM indica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea Detener o Borrar la VM.
    • De lo contrario, si el modelo de aprovisionamiento de VM se configura como Estándar o :
      • Si la opción Interrumpible está configurada como Activado, la VM es interrumpible.
      • De lo contrario, la VM es estándar.

gcloud

Para describir una VM a partir de la CLI de gcloud, usa el comando gcloud compute instances describe:

gcloud compute instances describe VM_NAME

En el comando anterior, VM_NAME es el nombre de la VM que deseas verificar.

En el resultado, verifica el campo scheduling para identificar la VM:

  • Si el resultado incluye el campo provisioningModel configurado como SPOT, que es similar a lo siguiente, la VM es Spot.

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

    En el ejemplo anterior, TERMINATION_ACTION indica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea detenerla (STOP) o borrarla (DELETE). Si falta el campo instanceTerminationAction, el valor predeterminado es STOP.

  • De lo contrario, si el resultado incluye el campo provisioningModel configurado como standard o si el resultado omite el campo provisioningModel:

    • Si el resultado incluye el campo preemptible configurado como true, la VM es interrumpible.
    • De lo contrario, la VM es estándar.

REST

Para describir una VM desde la API de Compute Engine, usa el método instances.get:

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

Reemplaza lo siguiente:

  • PROJECT_ID: el ID del proyecto en el que se encuentra la VM.
  • ZONE: la zona en la que se encuentra la VM
  • VM_NAME: el nombre de la VM que deseas verificar.

En el resultado, verifica el campo scheduling para identificar la VM:

  • Si el resultado incluye el campo provisioningModel configurado como SPOT, que es similar a lo siguiente, la VM es Spot.

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

    En el ejemplo anterior, TERMINATION_ACTION indica qué acción se debe realizar cuando Compute Engine interrumpe la VM, ya sea detenerla (STOP) o borrarla (DELETE). Si falta el campo instanceTerminationAction, el valor predeterminado es STOP.

  • De lo contrario, si el resultado incluye el campo provisioningModel configurado como standard o si el resultado omite el campo provisioningModel:

    • Si el resultado incluye el campo preemptible configurado como true, la VM es interrumpible.
    • De lo contrario, la VM es estándar.

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
}

Java


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
    )

Controla la interrupción con una secuencia de comandos de apagado

Cuando Compute Engine interrumpe una VM Spot, puedes usar una secuencia de comandos de apagado para intentar realizar acciones de limpieza antes de que se interrumpa la VM. Por ejemplo, puedes detener con un proceso en ejecución con facilidad y copiar un archivo de punto de control en Cloud Storage. En particular, la duración máxima del período de baja es más corta para un aviso de interrupción que para un apagado iniciado por el usuario. Para obtener más información sobre el período de baja de un aviso de interrupción, consulta Proceso de interrupción en la documentación conceptual de las VMs Spot.

El siguiente es un ejemplo de una secuencia de comandos de apagado que puedes agregar a una VM Spot en ejecución o agregar mientras creas una VM Spot nueva. Esta secuencia de comandos se ejecuta cuando la VM comienza a cerrarse, antes de que el comando kill normal del sistema operativo detenga todos los procesos restantes. Después de detener de forma correcta el programa deseado, la secuencia de comandos realiza una carga paralela de un archivo de punto de control a un bucket de 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."

Esta secuencia de comandos supone lo siguiente:

  • La VM se creó, como mínimo, con acceso de lectura/escritura a Cloud Storage como mínimo. Para obtener instrucciones sobre cómo crear una VM con los permisos adecuados, consulta la documentación sobre autenticación.

  • Tienes un bucket de Cloud Storage existente y permiso para escribir en él.

Si quieres agregar esta secuencia de comandos a una VM, configúrala para que funcione con una aplicación en tu VM y agrégala a los metadatos de la VM.

  1. Copia o descarga la secuencia de comandos de apagado:

    • Copia la secuencia de comandos de apagado anterior después de reemplazar lo siguiente:

      • PROGRAM_NAME es el nombre del proceso o programa que quieres cerrar. Por ejemplo, apache2 o nginx.
      • LOCAL_USER es el nombre de usuario con el que accediste a la máquina virtual.
      • BUCKET_NAME es el nombre del bucket de Cloud Storage en el que quieres guardar el archivo de punto de control del programa. Ten en cuenta que el nombre del bucket no comienza con gs:// en este caso.
    • Descarga la secuencia de comandos de apagado en tu estación de trabajo local y, luego, reemplaza las siguientes variables en el archivo:

      • [PROGRAM_NAME] es el nombre del proceso o programa que quieres cerrar. Por ejemplo, apache2 o nginx.
      • [LOCAL_USER] es el nombre de usuario con el que accediste a la máquina virtual.
      • [BUCKET_NAME] es el nombre del bucket de Cloud Storage donde quieres guardar el archivo de punto de control del programa. Ten en cuenta que el nombre del depósito no comienza con gs:// en este caso.
  2. Agrega la secuencia de comandos de apagado a una VM nueva o a una VM existente.

Detecta la interrupción de las VM Spot

Determina si Compute Engine interrumpió las VMs Spot con la consola de Google Cloud, la CLI de gcloud o la API de Compute Engine

Console

Para verificar si se interrumpió una VM, consulta los registros de actividad del sistema.

  1. En la consola de Google Cloud, ve a la página Registros.

    Ir a Registros

  2. Selecciona el proyecto y haz clic en Continuar.

  3. Agrega compute.instances.preempted al filtro por etiqueta o campo de búsqueda de texto.

  4. De forma alternativa, también puedes ingresar un nombre de VM si quieres ver las operaciones de interrupción de una VM específica.

  5. Presiona Intro para aplicar los filtros especificados. La consola de Google Cloud actualiza la lista de registros para mostrar solo las operaciones en las que se interrumpió una VM.

  6. Selecciona una operación en la lista para obtener detalles acerca de la instancia que se interrumpió.

gcloud

Usa el comando gcloud compute operations list con un parámetro de filtro para obtener una lista de eventos de interrupción de tu proyecto.

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

De forma opcional, puedes usar parámetros de filtro adicionales para ampliar aún más los resultados. Por ejemplo, si quieres ver eventos de interrupción solo para instancias dentro de un grupo de instancias administrado, usa el siguiente comando:

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

En el ejemplo anterior, BASE_INSTANCE_NAME es el nombre base especificado como un prefijo para los nombres de todas las VM en este grupo de instancias administrado.

El resultado es similar a este:

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

Un tipo de operación compute.instances.preempted indica que la instancia de VM se interrumpió. Puedes usar el comando gcloud compute operations describe para obtener más información sobre una operación de interrupción específica.

gcloud compute operations describe SYSTEM_EVENT \
    --zone=ZONE

Reemplaza lo siguiente:

  • SYSTEM_EVENT: el evento del sistema del resultado del comando gcloud compute operations list, por ejemplo, systemevent-xxxxxxxx.
  • ZONE: la zona del evento del sistema, por ejemplo, us-central1-f.

El resultado es similar a este:

...
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

A fin de obtener una lista de las operaciones del sistema recientes para un proyecto y una zona específicos, usa el método zoneOperations.get.

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

Reemplaza lo siguiente:

Opcional, para determinar la respuesta a fin de mostrar solo las operaciones de prioridad, puedes agregar un filtro a tu solicitud a la API:

operationType="compute.instances.preempted"

De manera alternativa, si quieres ver las operaciones de interrupción de una VM específica, agrega un parámetro targetLink al filtro:

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

Reemplaza lo siguiente: + PROJECT_ID: el ID del proyecto. + ZONE: la zona. + VM_NAME: el nombre de una VM específica en esta zona y este proyecto.

La respuesta contiene una lista de operaciones recientes. Por ejemplo, una interrupción es similar a la siguiente:

{
  "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.",
  ...
}

Como alternativa, puedes determinar si una VM se interrumpió desde la VM en sí. Esto es útil si quieres controlar el apagado debido a una interrupción de Compute Engine diferente de un apagado normal en una secuencia de comandos de apagado. Si quieres hacerlo, solo debes verificar el servidor de metadatos del valor preempted en los metadatos predeterminados de tu VM.

Por ejemplo, usa curl desde tu VM para obtener el valor de preempted:

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

Si este valor es TRUE, Compute Engine interrumpió la VM; de lo contrario, es FALSE.

Si quieres usarlo por fuera de una secuencia de comandos de apagado, puedes agregar ?wait_for_change=true a la URL. Esto realiza una solicitud HTTP GET pendiente que solo se mostrará cuando cambien los metadatos y se interrumpa la VM.

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

Cómo probar la configuración de interrupción

Puedes ejecutar eventos de mantenimiento simulados en tus VM para forzarlas a interrumpirse. Usa esta función para probar cómo tus apps manejan las VM Spot. Consulta la página Simula un evento de mantenimiento de host para obtener información sobre cómo probar los eventos de mantenimiento en tus instancias.

También puedes simular una interrupción de VM si detienes la instancia de VM, que se puede usar en lugar de simular un evento de mantenimiento y evita los límites de cuota.

Prácticas recomendadas

Estas son algunas de las prácticas recomendadas para ayudarte a aprovechar al máximo las VM Spot.

  • Usa plantillas de instancias. En lugar de crear VM Spot una a la vez, puedes usar plantillas de instancias para crear varias VM Spot con las mismas propiedades. Las plantillas de instancias son necesarias para usar los MIG. De manera alternativa, también puedes crear varias VM Spot con la API de instancia masiva.

  • Usa MIG para distribuir y volver a crear automáticamente las VM Spot. Usa MIG para hacer que las cargas de trabajo en las VM Spot sean más flexibles y resilientes. Por ejemplo, usa MIG regionales para distribuir VM en varias zonas, lo que ayuda a mitigar los errores de disponibilidad de recursos. Además, usa la reparación automática para volver a crear de forma automática las VM Spot después de que se interrumpen.

  • Elige tipos de máquina más pequeños. Los recursos de VM Spot se obtienen del exceso y la capacidad de copia de seguridad de Google Cloud. La capacidad de las VM Spot suele ser más fácil de obtener en tipos de máquinas más pequeños, es decir, tipos de máquinas con menos recursos, como la memoria y las CPU virtuales. Puedes encontrar más capacidad para las VM Spot si seleccionas un tipo personalizado de máquina más pequeño, pero la capacidad es aún más probable para tipos predefinidos de máquinas más pequeños. Por ejemplo, en comparación con la capacidad del tipo predefinido de máquina n2-standard-32, la capacidad para el tipo personalizado de máquina n2-custom-24-96 es más probable, pero la capacidad para el tipo predefinido de máquina n2-standard-16 es aún más probable.

  • Ejecuta clústeres grandes de VM Spot durante las horas de menor actividad. La carga en los centros de datos de Google Cloud varía según la ubicación y la hora del día, pero generalmente es más baja durante las noches y los fines de semana. En este sentido, a la noche y en los fines de semana son los mejores momentos para ejecutar clústeres grandes de VM Spot.

  • Diseña tus aplicaciones para que sean tolerantes a fallas y a la interrupción. Es importante estar preparado para el hecho de que hay cambios en los patrones de interrupción en diferentes momentos. Por ejemplo, si una zona sufre una interrupción parcial, se puede interrumpir una gran cantidad de VM Spot a fin de dejar lugar para las VM estándar que deben moverse como parte de la recuperación. Durante ese período, la tasa de interrupción se verá muy diferente a la de cualquier otro día. Si tu aplicación da por hecho que las interrupciones siempre se hacen en grupos pequeños, es posible que no estés preparado para tal evento.

  • Intenta crear las VM Spot que se interrumpiron. Si se interrumpieron tus VM Spot, intenta crear VM nuevas una o dos veces antes de volver a las VM estándar. Según tus requisitos, puede ser una buena idea combinar VM estándar y VM Spot en tus clústeres para garantizar que el trabajo avance a un ritmo adecuado.

  • Usa secuencia de comandos de apagado Administra las notificaciones de cierre y de interrupción con una secuencia de comandos de apagado que puede guardar el progreso de un trabajo para que pueda continuar donde lo dejó, en lugar de comenzar desde cero.

Próximos pasos