Créer et utiliser des VM Spot


Cette page explique comment créer et gérer des VM Spot, et inclut les sections suivantes :

  • Créer, démarrer et identifier des VM Spot
  • Détecter, gérer et tester la préemption des VM Spot
  • Bonnes pratiques pour les VM Spot

Les VM Spot sont des instances de machine virtuelle (VM) dotées du modèle de provisionnement Spot. Les VM Spot sont proposées avec une remise allant jusqu'à 60 à 91% par rapport au prix des VM standards. Toutefois, Compute Engine peut récupérer les ressources en préemptant des VM Spot à tout moment. Les VM Spot ne sont recommandées que pour les applications tolérantes aux pannes et capables de résister à la préemption de VM. Assurez-vous que votre application peut gérer la préemption avant de décider de créer des VM Spot.

Avant de commencer

  • Consultez la documentation conceptuelle sur les VM Spot:
    • Consultez les limites et les tarifs des VM Spot.
    • Pour empêcher les VM Spot de consommer vos quotas pour les processeurs, GPU et disques des VM standards, envisagez de demander un quota préemptif pour les VM Spot.
  • Si ce n'est pas déjà fait, configurez l'authentification. L'authentification est le processus permettant de valider votre identité pour accéder aux services et aux API Google Cloud. Pour exécuter du code ou des exemples depuis un environnement de développement local, vous pouvez vous authentifier auprès de Compute Engine en sélectionnant l'une des options suivantes:

    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

      Pour utiliser les exemples Terraform de cette page dans un environnement de développement local, installez et initialisez gcloud CLI, puis configurez le service Identifiants par défaut de l'application à l'aide de vos identifiants utilisateur.

      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.

      Pour en savoir plus, consultez Set up authentication for a local development environment.

      REST

      Pour utiliser les exemples d'API REST de cette page dans un environnement de développement local, vous devez utiliser les identifiants que vous fournissez à gcloud CLI.

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

        gcloud init

      Pour en savoir plus, consultez la section S'authentifier pour utiliser REST dans la documentation sur l'authentification Google Cloud.

Créer une VM Spot

Créez une VM Spot à l'aide de la console Google Cloud, de gcloud CLI ou de l'API Compute Engine. Une VM Spot est une VM configurée pour utiliser le modèle de provisionnement Spot :

  • Modèle de provisionnement de VM défini sur Spot dans la console Google Cloud
  • --provisioning-model=SPOT dans gcloud CLI
  • "provisioningModel": "SPOT" dans l'API Compute Engine

Console

  1. Accédez à la page Créer une instance dans Google Cloud Console.

    Accéder à Créer une instance

  2. Ensuite, procédez comme suit :

    1. Dans la section Règles de disponibilité, sélectionnez Spot dans la liste Modèle de provisionnement de VM. Ce paramètre désactive les options de redémarrage automatique et de maintenance de l'hôte pour la VM, et active l'option d'action de terminaison.
    2. Facultatif : dans la liste À l'arrêt de la VM, sélectionnez ce qui se passe lorsque Compute Engine préempte la VM :
      • Pour arrêter la VM pendant la préemption, sélectionnez Arrêter (sélection par défaut).
      • Pour supprimer la VM pendant la préemption, sélectionnez Supprimer.
  3. Facultatif : spécifiez d'autres options de VM. Pour en savoir plus, consultez la page Créer et démarrer une instance de VM.

  4. Pour créer et démarrer la VM, cliquez sur Créer.

gcloud

Pour créer une VM à partir de la CLI gcloud, utilisez la commande gcloud compute instances create. Pour créer des VM Spot, vous devez inclure l'option --provisioning-model=SPOT. Vous pouvez également spécifier une action d'arrêt pour les VM Spot en incluant l'option --instance-termination-action.

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

Remplacez les éléments suivants :

  • VM_NAME : nom de la nouvelle VM.
  • TERMINATION_ACTION (facultatif) : spécifiez l'action à effectuer lorsque Compute Engine préempte la VM, soit STOP (comportement par défaut) soit DELETE.

Pour en savoir plus sur les options que vous pouvez spécifier lors de la création d'une VM, consultez la page Créer et démarrer une instance de VM. Par exemple, pour créer des VM Spot avec un type de machine et une image spécifiés, utilisez la commande suivante :

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

Remplacez les éléments suivants :

  • VM_NAME : nom de la nouvelle VM.
  • IMAGE : spécifiez l'une des options suivantes :
    • IMAGE: version spécifique d'une image publique ou de la famille d'images. Par exemple, une image spécifique est --image=debian-10-buster-v20200309.
    • Une famille d'images. Cette action crée la VM à partir de l'image d'OS la plus récente et non obsolète. Par exemple, si vous spécifiez --image-family=debian-10, Compute Engine crée une VM à partir de la dernière version de l'image de l'OS dans la famille d'images Debian 10.
  • IMAGE_PROJECT : projet contenant l'image. Par exemple, si vous spécifiez debian-10 comme famille d'images, spécifiez debian-cloud comme projet d'image.
  • MACHINE_TYPE : type de machine prédéfini ou personnalisé pour la nouvelle VM.
  • TERMINATION_ACTION (facultatif) : spécifiez l'action à effectuer lorsque Compute Engine préempte la VM, soit STOP (comportement par défaut) soit DELETE.

    Pour obtenir la liste des types de machines disponibles dans une zone, utilisez la commande gcloud compute machine-types list avec l'option --zones.

Terraform

Vous pouvez utiliser une ressource Terraform pour créer une instance Spot à l'aide d'un bloc de planification.


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

Pour créer une VM à partir de l'API Compute Engine, utilisez la méthode instances.insert. Vous devez spécifier un type de machine et un nom pour la VM. Vous pouvez également spécifier une image pour le disque de démarrage.

Pour créer des VM Spot, vous devez inclure le champ "provisioningModel": spot. Vous pouvez également spécifier une action d'arrêt pour les VM Spot en incluant le champ "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"
 },
 ...
}

Remplacez les éléments suivants :

  • PROJECT_ID : ID du projet dans lequel créer la VM.
  • ZONE : zone dans laquelle créer la VM. La zone doit également accepter le type de machine à utiliser pour la nouvelle VM.
  • MACHINE_TYPE : type de machine prédéfini ou personnalisé pour la nouvelle VM.
  • VM_NAME : nom de la nouvelle VM.
  • IMAGE_PROJECT : projet contenant l'image. Par exemple, si vous spécifiez family/debian-10 comme famille d'images, spécifiez debian-cloud comme projet d'image.
  • IMAGE : spécifiez l'une des options suivantes :
    • Version spécifique d'une image publique. Par exemple, une image spécifique est "sourceImage": "projects/debian-cloud/global/images/debian-10-buster-v20200309"debian-cloud correspond à IMAGE_PROJECT.
    • Une famille d'images. Cette action crée la VM à partir de l'image d'OS la plus récente et non obsolète. Par exemple, si vous spécifiez "sourceImage": "projects/debian-cloud/global/images/family/debian-10"debian-cloud a la valeur IMAGE_PROJECT, Compute Engine crée une VM à partir de la dernière version de l'image de l'OS dans la famille d'images Debian 10.
  • TERMINATION_ACTION (facultatif) : spécifiez l'action à effectuer lorsque Compute Engine préempte la VM, soit STOP (comportement par défaut) soit DELETE.

Pour en savoir plus sur les options que vous pouvez spécifier lors de la création d'une VM, consultez la page Créer et démarrer une instance 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

Pour créer plusieurs VM Spot avec les mêmes propriétés, vous pouvez créer un modèle d'instance et utiliser celui-ci pour créer un groupe d'instances géré (MIG). Pour en savoir plus, consultez la section Bonnes pratiques.

Démarrer les VM Spot

Comme les autres VM, les VM Spot démarrent lors de la création. De même, si les VM Spot sont arrêtées, vous pouvez redémarrer les VM pour qu'elles reprennent l'état RUNNING. Vous pouvez arrêter et redémarrer les VM Spot préemptées autant de fois que vous le souhaitez tant qu'elles disposent d'une capacité suffisante. Pour en savoir plus, consultez la page Cycle de vie des instances de VM.

Si Compute Engine arrête une ou plusieurs VM Spot dans un groupe d'instances géré soumis à l'autoscaling ou dans un cluster Google Kubernetes Engine (GKE), le groupe redémarre les VM lorsque les ressources sont à nouveau disponibles.

Identifier le modèle de provisionnement et l'action d'arrêt d'une VM

Identifiez le modèle de provisionnement d'une VM pour vérifier s'il s'agit d'une VM standard, d'une VM spot ou d'une VM préemptive. Pour une VM Spot, vous pouvez également identifier l'action d'arrêt. Vous pouvez identifier le modèle de provisionnement et l'action d'arrêt d'une VM en utilisant la console Google Cloud, gcloud CLI ou l'API Compute Engine.

Console

  1. Accédez à la page Instances de VM.

    Accéder à la page "Instances de VM"

  2. Cliquez sur le nom de la VM que vous souhaitez modifier. La page Détails de l'instance de VM s'affiche.

  3. Accédez à la section Gestion en bas de la page. Dans la sous-section Règles de disponibilité, vérifiez les options suivantes :

    • Si le modèle de provisionnement de VM est défini sur Spot, alors la VM est une VM Spot.
      • Le champ À l'arrêt de la VM indique l'action à effectuer lorsque Compute Engine préempte la VM, soit arrêter la VM, soit la supprimer.
    • Sinon, si le modèle de provisionnement de VM est défini sur Standard ou  :
      • Si l'option Préemption est définie sur Activé, alors la VM est une VM préemptive.
      • Sinon, il s'agit d'une VM standard.

gcloud

Pour décrire une VM à partir de la CLI gcloud, utilisez la commande gcloud compute instances describe :

gcloud compute instances describe VM_NAME

VM_NAME est le nom de la VM à vérifier.

Dans la sortie, vérifiez le champ scheduling afin d'identifier la VM :

  • Si la sortie inclut le champ provisioningModel défini sur SPOT, comme ci-après, alors la VM est une VM Spot.

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

    TERMINATION_ACTION indique l'action à effectuer lorsque Compute Engine préempte la VM, soit l'arrêter (STOP), soit la supprimer (DELETE). Si le champ instanceTerminationAction n'est pas renseigné, la valeur par défaut est STOP.

  • Dans le cas contraire, si la sortie inclut le champ provisioningModel défini sur standard ou si le champ provisioningModel y est omis :

    • Si la sortie inclut le champ preemptible défini sur true, alors la VM est une VM préemptive.
    • Sinon, il s'agit d'une VM standard.

REST

Pour décrire une VM à partir de l'API Compute Engine, utilisez la méthode instances.get :

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

Remplacez les éléments suivants :

  • PROJECT_ID : ID du projet dans lequel se trouve la VM.
  • ZONE : zone où se trouve la VM.
  • VM_NAME : nom de la VM que vous souhaitez vérifier.

Dans la sortie, vérifiez le champ scheduling afin d'identifier la VM :

  • Si la sortie inclut le champ provisioningModel défini sur SPOT, comme ci-après, alors la VM est une VM Spot.

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

    TERMINATION_ACTION indique l'action à effectuer lorsque Compute Engine préempte la VM, soit l'arrêter (STOP), soit la supprimer (DELETE). Si le champ instanceTerminationAction n'est pas renseigné, la valeur par défaut est STOP.

  • Dans le cas contraire, si la sortie inclut le champ provisioningModel défini sur standard ou si le champ provisioningModel y est omis :

    • Si la sortie inclut le champ preemptible défini sur true, alors la VM est une VM préemptive.
    • Sinon, il s'agit d'une VM standard.

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
    )

Gérer la préemption avec un script d'arrêt

Lorsque Compute Engine préempte une VM Spot, vous pouvez utiliser un script d'arrêt pour tenter d'effectuer des actions de nettoyage avant que la VM ne soit préemptée. Par exemple, vous pouvez arrêter normalement un processus en cours et copier un fichier de point de contrôle dans Cloud Storage. En particulier, la durée maximale de l'arrêt est plus courte pour un préavis de préemption que pour un arrêt initié par l'utilisateur. Pour en savoir plus sur la période d'arrêt d'une notification de préemption, consultez la section Procédure de préemption dans la documentation conceptuelle sur les VM Spot.

Vous trouverez ci-dessous un exemple de script d'arrêt que vous pouvez ajouter à une VM Spot en cours d'exécution ou lors de la création d'une VM Spot. Ce script s'exécute lorsque la VM commence à s'arrêter, avant que la commande kill normale du système d'exploitation ne mette fin à tous les processus restants. Une fois qu'il a arrêté de manière optimale le programme souhaité, le script effectue un téléchargement parallèle d'un fichier de point de contrôle dans un bucket 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."

Ce script suppose que les conditions suivantes sont remplies :

  • La VM a été créée avec au moins un accès en lecture/écriture à Cloud Storage. Pour savoir comment créer une VM avec les champs d'application appropriés, consultez la documentation sur l'authentification.

  • Vous disposez d'un bucket Cloud Storage et de l'autorisation d'y écrire.

Pour ajouter ce script à une VM, configurez-le pour qu'il fonctionne avec une application dans la VM et ajoutez-le aux métadonnées de la VM.

  1. Copiez ou téléchargez le script d'arrêt :

    • Copiez le script d'arrêt précédent après avoir remplacé les éléments suivants :

      • PROGRAM_NAME est le nom du processus ou du programme que vous souhaitez arrêter. Par exemple, apache2 ou nginx.
      • LOCAL_USER est le nom d'utilisateur sous lequel vous êtes connecté à la machine virtuelle.
      • BUCKET_NAME correspond au nom du bucket Cloud Storage dans lequel vous souhaitez enregistrer le fichier de point de contrôle du programme. Notez que dans ce cas, le nom du bucket ne commence pas par gs://.
    • Téléchargez le script d'arrêt sur votre poste de travail local, puis remplacez les variables suivantes dans le fichier:

      • [PROGRAM_NAME] est le nom du processus ou du programme que vous souhaitez arrêter. Par exemple, apache2 ou nginx.
      • [LOCAL_USER] est le nom d'utilisateur sous lequel vous êtes connecté à la machine virtuelle.
      • [BUCKET_NAME] correspond au nom du bucket Cloud Storage dans lequel vous souhaitez enregistrer le fichier de point de contrôle du programme. Notez que dans ce cas, le nom du bucket ne commence pas par gs://.
  2. Ajoutez le script d'arrêt à une nouvelle VM ou à une VM existante.

Détecter la préemption des VM Spot

Déterminez si les VM Spot ont été préemptées par Compute Engine à l'aide de la console Google Cloud, de la CLI gcloud ou de l'API Compute Engine.

Console

Vous pouvez vérifier si une VM a été préemptée en consultant les journaux d'activité du système.

  1. Dans Google Cloud Console, accédez à la page Journaux.

    Accéder aux journaux

  2. Sélectionnez le projet et cliquez sur Continuer.

  3. Ajoutez compute.instances.preempted au champ filtre par libellé ou recherche textuelle.

  4. Vous pouvez également saisir un nom de VM pour consulter les opérations de préemption d'une VM spécifique.

  5. Appuyez sur Entrée pour appliquer les filtres spécifiés. La console Google Cloud met à jour la liste des journaux pour n'afficher que les opérations au cours desquelles une VM a été préemptée.

  6. Sélectionnez une opération dans la liste pour voir les détails de la VM qui a été préemptée.

gcloud

Utilisez la commande gcloud compute operations list avec un paramètre de filtre pour obtenir la liste des événements de préemption de votre projet.

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

Vous pouvez éventuellement utiliser des paramètres de filtre supplémentaires pour affiner davantage les résultats. Par exemple, pour n'afficher que les événements de préemption associés aux instances appartenant à un groupe d'instances géré, exécutez la commande suivante :

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

BASE_INSTANCE_NAME est le nom de base spécifié en tant que préfixe pour les noms de toutes les VM de ce groupe d'instances géré.

Le résultat ressemble à ce qui suit :

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

Le type d'opération compute.instances.preempted indique que l'instance de VM a été préemptée. Vous pouvez utiliser la commande gcloud compute operations describe pour obtenir plus d'informations sur une opération de préemption spécifique.

gcloud compute operations describe SYSTEM_EVENT \
    --zone=ZONE

Remplacez les éléments suivants :

  • SYSTEM_EVENT : événement système issu de la sortie de la commande gcloud compute operations list (par exemple, systemevent-xxxxxxxx).
  • ZONE : zone de l'événement système, par exemple us-central1-f

Le résultat ressemble à ce qui suit :

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

Pour obtenir la liste des opérations système récentes pour un projet et une zone spécifiques, utilisez la méthode zoneOperations.get.

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

Remplacez les éléments suivants :

Pour n'afficher que les opérations de préemption, vous pouvez ajouter un filtre à votre requête API :

operationType="compute.instances.preempted"

Sinon, pour afficher les opérations de préemption pour une VM spécifique, ajoutez un paramètre targetLink au filtre :

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

Remplacez les éléments suivants : + PROJECT_ID : ID du projet. + ZONE : zone. + VM_NAME : nom d'une VM spécifique dans cette zone et ce projet.

La réponse contient la liste des opérations récentes. Par exemple, une préemption se présente comme suit :

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

Vous pouvez également déterminer si une VM a été préemptée depuis la VM elle-même. Cette fonction est utile si vous souhaitez gérer un arrêt dû à une préemption Compute Engine différemment d'un arrêt normal dans un script d'arrêt. Pour ce faire, il vous suffit de consulter, sur le serveur de métadonnées, la valeur de preempted dans les métadonnées par défaut de votre VM.

Par exemple, utilisez curl depuis votre VM pour obtenir la valeur de preempted :

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

Si cette valeur est TRUE, la VM a été préemptée par Compute Engine. Sinon, elle est FALSE.

Pour utiliser cette fonctionnalité en dehors d'un script d'arrêt, vous pouvez ajouter ?wait_for_change=true à l'URL. Cette opération génère une requête HTTP GET suspendue qui ne renvoie les résultats que si les métadonnées ont été modifiées et que la VM a été préemptée.

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

Tester les paramètres de préemption

Vous pouvez simuler des événements de maintenance sur vos VM pour forcer la préemption. Utilisez cette fonctionnalité pour tester la manière dont vos applications gèrent les VM Spot. Consultez la section Simuler un événement de maintenance de l'hôte pour savoir comment tester les événements de maintenance sur vos instances.

Vous pouvez également simuler une préemption de VM en arrêtant l'instance de VM plutôt qu'en simulant un événement de maintenance. Cela évite d'atteindre les limites du quota.

Bonnes pratiques

Voici quelques bonnes pratiques pour vous aider à tirer le meilleur parti des VM Spot.

  • Utilisez des modèles d'instance. Plutôt que de créer des VM Spot une par une, vous pouvez utiliser des modèles d'instance pour créer plusieurs VM Spot avec les mêmes propriétés. Les modèles d'instance sont requis pour utiliser les MIG. Vous pouvez également créer plusieurs VM Spot à l'aide de l'API d'instances groupées.

  • Utilisez des MIG pour assurer la distribution régionale et la recréation automatique des VM Spot. Utilisez les MIG pour rendre les charges de travail sur les VM Spot plus flexibles et résilientes. Par exemple, utilisez des MIG régionaux pour répartir les VM sur plusieurs zones, ce qui permet de limiter les erreurs de disponibilité des ressources. De plus, utilisez l'autoréparation pour recréer automatiquement les VM Spot après leur préemption.

  • Choisissez des types de machines plus petits. Les ressources des VM Spot proviennent de la capacité excédentaire et de sauvegarde de Google Cloud. La capacité des VM Spot est souvent plus facile à obtenir pour des types de machines plus petits, c'est-à-dire qui disposent de moins de ressources, comme les vCPU (processeurs virtuels) et la mémoire. Vous pouvez augmenter la capacité des VM Spot en sélectionnant un type de machine personnalisé plus petit, mais la probabilité d'obtention de capacité est encore plus élevée en optant pour des types de machines prédéfinis plus petits. Par exemple, par rapport à la capacité du type de machine prédéfini n2-standard-32, la capacité du type de machine personnalisé n2-custom-24-96 présente certes une bonne probabilité, mais celle associée à la capacité du type de machine prédéfini n2-standard-16 est encore plus élevée.

  • Exécutez les grands clusters de VM Spot pendant les heures creuses. La charge sur les centres de données Google Cloud varie en fonction de l'emplacement et de l'heure de la journée, mais est généralement plus faible la nuit et le week-end. Par conséquent, la nuit et le week-end sont les meilleurs moments pour exécuter de grands clusters de VM Spot.

  • Concevez des applications tolérantes aux erreurs et à la préemption. Il est important de prévoir que des changements se produisent dans les schémas de préemption à différents moments. Par exemple, en cas de panne partielle d'une zone, un grand nombre de VM Spot peuvent être préemptées pour libérer de l'espace pour les VM standards à déplacer dans le cadre de la reprise. Dans ce petit intervalle de temps, le taux de préemption sera très différent de celui d'un autre jour. Si votre application suppose que les préemptions sont toujours effectuées par petits groupes, vous ne serez peut-être pas préparé à un tel événement.

  • Réessayez de créer des VM Spot qui ont été préemptées. Si vos VM Spot ont été préemptées, il est conseillé de réessayer de créer une ou deux fois des VM Spot avant de revenir aux VM standards. Selon vos besoins, il peut être judicieux de combiner des VM standards et des VM Spot dans vos clusters pour vous assurer que le travail se déroule à la cadence appropriée.

  • Utilisez des scripts d'arrêt. Gérez les notifications d'arrêt et de préemption à l'aide d'un script d'arrêt qui peut enregistrer la progression d'une tâche et permettre de la reprendre là où elle s'était arrêtée, plutôt que de tout recommencer à zéro.

Étape suivante