Créer et utiliser des VM préemptives


Cette page explique comment créer et utiliser une instance de machine virtuelle préemptive. Les VM préemptives sont proposées avec une remise de 60 à 91 % par rapport au prix des VM standards. Cependant, Compute Engine peut arrêter (préempter) ces VM s'il a besoin de récupérer ces ressources pour d'autres tâches. Les VM préemptives s'arrêtent toujours au bout de 24 heures. Les VM préemptives 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 les préemptions avant de décider de créer une VM préemptive. Pour comprendre les risques et les atouts des VM préemptives, consultez la documentation sur les instances de VM préemptives.

Avant de commencer

  • Consultez la documentation sur les instances de VM préemptives.
  • 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 comme suit :

    Sélectionnez l'onglet correspondant à la façon dont vous prévoyez d'utiliser les exemples de cette page :

    Console

    Lorsque vous utilisez la console Google Cloud pour accéder aux services et aux API Google Cloud, vous n'avez pas besoin de configurer l'authentification.

    gcloud

    1. Installez Google Cloud CLI, puis initialisez-la en exécutant la commande suivante :

      gcloud init
    2. Définissez une région et une zone par défaut.

    Go

    Pour utiliser les exemples Go 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. Installez Google Cloud CLI.
    2. Pour initialiser gcloudCLI, exécutez la commande suivante :

      gcloud init
    3. Créez des identifiants d'authentification locaux pour votre compte Google :

      gcloud auth application-default login

    Pour en savoir plus, consultez les sections sur Configurer l'authentification pour un environnement de développement local.

    Java

    Pour utiliser les exemples Java 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. Installez Google Cloud CLI.
    2. Pour initialiser gcloudCLI, exécutez la commande suivante :

      gcloud init
    3. Créez des identifiants d'authentification locaux pour votre compte Google :

      gcloud auth application-default login

    Pour en savoir plus, consultez les sections sur Configurer l'authentification pour un environnement de développement local.

    Node.js

    Pour utiliser les exemples Node.js 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. Installez Google Cloud CLI.
    2. Pour initialiser gcloudCLI, exécutez la commande suivante :

      gcloud init
    3. Créez des identifiants d'authentification locaux pour votre compte Google :

      gcloud auth application-default login

    Pour en savoir plus, consultez les sections sur Configurer l'authentification pour un environnement de développement local.

    Python

    Pour utiliser les exemples Python 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. Installez Google Cloud CLI.
    2. Pour initialiser gcloudCLI, exécutez la commande suivante :

      gcloud init
    3. Créez des identifiants d'authentification locaux pour votre compte Google :

      gcloud auth application-default login

    Pour en savoir plus, consultez les sections sur Configurer l'authentification pour un environnement de développement local.

    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.

      Installez Google Cloud CLI, puis initialisez-la en exécutant la commande suivante :

      gcloud init

Créer une VM préemptive

Créez une VM préemptive à l'aide de gcloud CLI ou de l'API Compute Engine. Pour utiliser la console Google Cloud, créez plutôt une VM Spot.

gcloud

Avec gcloud compute, exécutez la commande instances create que vous utiliseriez pour créer une instance normale et ajoutez l'option --preemptible.

gcloud compute instances create [VM_NAME] --preemptible

[VM_NAME] est le nom de la VM.

Go

import (
	"context"
	"fmt"
	"io"

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

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

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

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

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

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

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

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

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

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

	return nil
}

Java


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

public class CreatePreemptibleInstance {

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

    createPremptibleInstance(projectId, zone, instanceName);
  }

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

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

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

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

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

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

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

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

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

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

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

Node.js

/**
 * TODO(developer): Uncomment and replace these variables before running the sample.
 */
// const projectId = 'YOUR_PROJECT_ID';
// const zone = 'europe-central2-b';
// const instanceName = 'YOUR_INSTANCE_NAME';

const compute = require('@google-cloud/compute');

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

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

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

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

createPreemptible();

Python

from __future__ import annotations

import re
import sys
from typing import Any
import warnings

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

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

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

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

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

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

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

def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result

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

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

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

    if internal_ip:
        network_interface.network_i_p = internal_ip

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

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

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

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

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

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

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

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

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

    operation = instance_client.insert(request=request)

    wait_for_extended_operation(operation, "instance creation")

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

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

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

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

REST

Dans l'API, créez une requête normale de création de VM et incluez la propriété preemptible sous scheduling et définissez-la sur true. Exemple :

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

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

Quotas de processeurs préemptifs

Les VM préemptives nécessitent des quotas de processeurs disponibles, comme les VM standards. Pour éviter que les VM préemptives ne consomment les quotas de processeurs des VM standards, vous pouvez demander un quota spécial de "processeurs préemptifs". Lorsque Compute Engine vous accorde un quota de processeurs préemptifs dans la région, toutes les VM préemptives sont prises en compte dans ce quota. Toutes les VM standards continuent d'être comptabilisées dans le quota de processeurs standards.

Dans les régions où vous ne disposez pas de quota de processeurs préemptifs, vous pouvez utiliser un quota de processeurs standards pour lancer les instances préemptives. Comme à l'accoutumée, vous avez également besoin d'un quota suffisant d'adresses IP et d'espace disque. Le quota de processeurs préemptifs n'est pas visible sur les pages de quota de gcloud CLI ou de la console Google Cloud, sauf s'il a été accordé par Compute Engine.

Pour plus d'informations sur les quotas, consultez la page Quotas de ressources.

Démarrer une VM préemptée

Comme pour toute autre VM, si une VM préemptive est arrêtée ou préemptée, vous pouvez redémarrer la VM et la faire revenir à l'état RUNNING. Le démarrage d'une VM préemptive réinitialise le délai de 24 heures, mais comme il s'agit d'une VM préemptive, Compute Engine peut la préempter avant 24 heures. Il n'est pas possible de convertir une VM préemptive en VM standard pendant son exécution.

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

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

Lorsque la VM est préemptée, vous pouvez utiliser un script d'arrêt pour effectuer des actions de nettoyage avant son arrêt. Par exemple, vous pouvez arrêter normalement un processus en cours et copier un fichier de point de contrôle dans Cloud Storage.

Vous trouverez ci-après un script d'arrêt que vous pouvez ajouter à une VM préemptive en cours d'exécution ou que vous créez. 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 Google Cloud Storage.

#!/bin/bash

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

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

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

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

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

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

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

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

echo "Done uploading, shutting down."

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 sur votre poste de travail local.
  2. Ouvrez le fichier pour le modifier et changez les variables suivantes :
    • [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://.
  3. Enregistrez les modifications.
  4. Ajoutez le script d'arrêt à une nouvelle VM ou à une VM existante.

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. Consultez la documentation sur l'authentification pour savoir comment créer une VM avec les champs d'application appropriés.

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

Identifier les VM préemptives

Pour vérifier si une VM est une VM préemptive, suivez les étapes pour identifier le modèle de provisionnement et l'action d'arrêt d'une VM.

Déterminer si une VM a été préemptée

Déterminez si une VM a été préemptée à l'aide de la console Google Cloud, de gcloud CLI ou de l'API.

Console

Pour vérifier si une VM a été préemptée, consultez 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 le paramètre filtre pour obtenir la liste des événements de préemption dans votre projet.

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

Vous pouvez spécifier le paramètre de filtre pour affiner davantage les résultats. Par exemple, pour n'afficher que les événements de préemption associés aux VM 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_VM_NAME]"

gcloud renvoie une réponse semblable à ce qui suit :

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

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

gcloud compute operations describe \
    systemevent-xxxxxxxx

gcloud renvoie une réponse semblable à ce qui suit :

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

REST


Pour obtenir la liste des opérations système récentes, envoyez une requête GET à l'URI des opérations de la zone.

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

La réponse contient la liste des opérations récentes.

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

Pour n'afficher que les opérations de préemption, vous pouvez ajouter un filtre à votre requête API : operationType="compute.instances.preempted". Pour consulter 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]".

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 d'instance 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 préemptives. Consultez la section Tester vos règles de disponibilité pour savoir comment tester les événements de maintenance sur vos VM.

Vous pouvez également simuler la préemption d'une VM en arrêtant la 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 instances de machine virtuelle préemptives.

Utiliser l'API d'instances groupées

Plutôt que de créer des VM uniques, vous pouvez utiliser l'API d'instances groupées.

Choisir des configurations de machine plus petites

Les ressources des VM préemptives proviennent de la capacité excédentaire et de sauvegarde de Google Cloud. La capacité est souvent plus facile à obtenir pour des types de machines plus petits, c'est-à-dire qui disposent de moins de ressources, comme les vCPU et la mémoire. Vous pouvez augmenter la capacité des VM préemptives 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écuter des clusters de machines virtuelles préemptives 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 machines virtuelles préemptives.

Concevoir 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 préemptives 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. Vous pouvez tester le comportement de votre application face à un événement de préemption en arrêtant l'instance de machine virtuelle.

Réessayer de créer des VM préemptées

Si votre instance de VM a été préemptée, essayez de créer une ou deux fois les VM préemptives avant de revenir aux VM standards. Selon vos besoins, il peut être judicieux de combiner des VM standards et préemptives dans les clusters pour vous assurer que le travail se déroule à la cadence appropriée.

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

Étapes suivantes