יצירה של מכונות וירטואליות שניתן להפסיק את הפעולה שלהן ושימוש בהן

בדף הזה מוסבר איך ליצור ולהשתמש במכונה וירטואלית זמנית (VM). מכונות Preemptible VM זמינות בהנחה של עד 91% מהמחיר שמוגדר כברירת מחדל למכונות VM רגילות. עם זאת, יכול להיות ש-Compute Engine יפסיק (יבצע קדימה) את מכונות ה-VM האלה אם הוא יצטרך להחזיר את המשאבים האלה לשימוש במשימות אחרות. מכונות Preemptible VM תמיד מפסיקות לפעול אחרי 24 שעות. מומלץ להשתמש במכונות וירטואליות זמניות רק באפליקציות עמידות בכשלים שיכולות לעמוד בהפסקה זמנית של מכונה וירטואלית. לפני שיוצרים מכונה וירטואלית (VM) זמנית, צריך לוודא שהאפליקציה יכולה להתמודד עם הפסקות זמניות. כדי להבין את הסיכונים והערך של מכונות וירטואליות זמניות, כדאי לקרוא את התיעוד בנושא מכונות וירטואליות זמניות.

לפני שמתחילים

יצירת VM זמני

יוצרים VM זמני באמצעות ה-CLI של gcloud או Compute Engine API. כדי להשתמש במסוףGoogle Cloud , צריך ליצור VM במודל Spot במקום זאת.

gcloud

כדי להשתמש ב-gcloud compute, מריצים את אותה פקודה instances create שבה משתמשים כדי ליצור מכונה וירטואלית רגילה, אבל מוסיפים את הדגל --preemptible.

gcloud compute instances create VM_NAME --preemptible

כאשר VM_NAME הוא השם של המכונה הווירטואלית.

המשך

import (
	"context"
	"fmt"
	"io"

	compute "cloud.google.com/go/compute/apiv1"
	computepb "cloud.google.com/go/compute/apiv1/computepb"
	"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 (InstInstancesClienttancesClient = InstInstancesClientate()) {

      AttaAttachedDiskk =
          AttaAttachedDiskBuilder()
              .setBoot(true)
              .setAutoDelete(true)
              .setType(AttaAttachedDiske.PERSISTENT.toString())
              .setIsetInitializeParams                // Describe the size and source image of the boot disk to attach to the instance.
                  AttaAttachedDiskInitializeParamsBuilder()
                      .setSourceImage(sourceImage)
                      .setDiskSizeGb(diskSizeGb)
                      .build())
              .build();

      // Use the default VPC network.
      NetwNetworkInterfaceworkInterface = NetwNetworkInterfaceBuilder()
          .setName(networkName)
          .build();

      // Collect information into the Instance object.
      InstInstancetanceResource =
          InstInstanceBuilder()
              .setName(instanceName)
              .setMachineType(machineType)
              .addDisks(disk)
              .addNetworkInterfaces(networkInterface)
              // Set the preemptible setting.
              .setScheduling(ScheSchedulingBuilder()
                  .setPsetPreemptiblee)
                  .build())
              .build();

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

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

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

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

      System.out.printf("Instance created : %s\n", instanceName);
      System.out.println("Operation Status: " + respresponse.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

ב-API, יוצרים בקשה רגילה ליצירת מכונה וירטואלית, אבל כוללים את המאפיין preemptible בקטע scheduling ומגדירים אותו לערך true. לדוגמה:

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

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

מכסות של מעבדים (CPU) שניתנים להפסקה

כדי להשתמש במכונות וירטואליות זמניות, צריך מכסות CPU זמינות, כמו במכונות וירטואליות רגילות. כדי למנוע ממכונות וירטואליות שניתן לקטוע את הפעולה שלהן לצרוך את מכסות ה-CPU של המכונות הווירטואליות הרגילות, אפשר לבקש מכסה מיוחדת של 'CPU שניתן לקטוע את הפעולה שלו'. אחרי ש-Compute Engine יאשר את מכסת המעבדים הווירטואליים שניתנים להפסקת פעולה באותו אזור, כל המכונות הווירטואליות שניתנות להפסקת פעולה ייכללו במכסה הזו, וכל המכונות הווירטואליות הרגילות ימשיכו להיכלל במכסת המעבדים הווירטואליים הרגילה.

באזורים שבהם אין לכם מכסת CPU של מכונות preemptible VM, אתם יכולים להשתמש במכסת CPU רגילה כדי להפעיל מכונות preemptible VM. כמו כן, תצטרכו מכסת כתובות IP ומכסת נפח אחסון מספיקות, כרגיל. מכסת CPU שניתן להפסיק לא מוצגת ב-CLI של gcloud או בדפי המכסות במסוףGoogle Cloud אלא אם Compute Engine העניק את המכסה.

מידע נוסף על מכסות זמין בדף מכסות משאבים.

הפעלה של מכונה וירטואלית (VM) שהופסקה לפני הזמן

בדומה לכל מכונה וירטואלית אחרת, אם מכונה וירטואלית עם אפשרות קדימה נפסקה או נדחקה, אפשר להפעיל אותה מחדש ולהחזיר אותה למצב RUNNING. הפעלה של VM זמני מאפסת את מונה 24 השעות, אבל מכיוון שהוא עדיין VM זמני, מערכת Compute Engine יכולה להפסיק אותו לפני שיחלפו 24 שעות. אי אפשר להמיר מכונה וירטואלית שניתנת להפסקת פעולה למכונה וירטואלית רגילה בזמן שהיא פועלת.

אם Compute Engine מפסיק VM זמני בקבוצת מופעי מכונה מנוהלים (MIG) עם התאמה אוטומטית לעומס או באשכול Google Kubernetes Engine, הקבוצה מפעילה מחדש את המכונה הווירטואלית כשהמשאבים הופכים לזמינים שוב.

טיפול בהפקעה באמצעות סקריפט כיבוי

כש-Compute Engine מבצעת דחיקה של מכונת VM, אפשר להשתמש בסקריפט כיבוי כדי לנסות לבצע פעולות ניקוי לפני הדחיקה של מכונת ה-VM. לדוגמה, אתם יכולים להפסיק תהליך שפועל בצורה מסודרת ולהעתיק קובץ של נקודת ביקורת ל-Cloud Storage. חשוב לציין שאורך התקופה המקסימלית של ההשבתה קצר יותר בהודעה על הקצאה מראש מאשר בהשבתה שיזם המשתמש. מידע נוסף על תקופת ההשבתה של הודעת קדימות מופיע במאמר תהליך הקדימות במסמכי התיעוד התיאורטיים.

בהמשך מופיע סקריפט כיבוי שאפשר להוסיף למכונה וירטואלית שניתנת להפסקת פעולה או למכונה וירטואלית חדשה שניתנת להפסקת פעולה כשיוצרים אותה. הסקריפט הזה פועל כשהמכונה הווירטואלית מתחילה להיסגר, לפני שהפקודה הרגילה kill של מערכת ההפעלה מפסיקה את כל התהליכים שנותרו. אחרי שהסקריפט מפסיק את התוכנית שנבחרה בצורה מסודרת, הוא מעלה במקביל קובץ של נקודת ביקורת לקטגוריה של Cloud Storage.

#!/bin/bash

MY_PROGRAM="PROGRAM_NAME" # For example, "apache2" or "nginx"
MY_USER="LOCAL_USERNAME"
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."

כדי להוסיף את הסקריפט הזה למכונה וירטואלית, צריך להגדיר את הסקריפט כך שיפעל עם אפליקציה במכונה הווירטואלית ולהוסיף אותו למטא-נתונים של המכונה הווירטואלית.

  1. מעתיקים או מורידים את סקריפט הכיבוי לתחנת העבודה המקומית.
  2. פותחים את הקובץ לעריכה ומשנים את המשתנים הבאים:
    • PROGRAM_NAME הוא השם של התהליך או התוכנית שרוצים לסגור. לדוגמה, apache2 או nginx.
    • LOCAL_USERNAME הוא שם המשתמש שדרכו אתם מחוברים למכונה הווירטואלית.
    • BUCKET_NAME הוא השם של קטגוריית Cloud Storage שבה רוצים לשמור את קובץ נקודת הבדיקה של התוכנית. שימו לב שבמקרה הזה שם הקטגוריה לא מתחיל ב-gs://.
  3. שומרים את השינויים.
  4. מוסיפים את סקריפט הכיבוי למכונה וירטואלית חדשה או למכונה וירטואלית קיימת.

הסקריפט הזה יוצא מנקודת הנחה לגבי הדברים הבאים:

  • מכונת ה-VM נוצרה עם גישת קריאה/כתיבה לפחות ל-Cloud Storage. הוראות ליצירת מכונת VM עם ההיקפים המתאימים מופיעות במסמכי התיעוד בנושא אימות.

  • יש לכם קטגוריה של Cloud Storage קיימת והרשאה לכתוב בה.

זיהוי מכונות וירטואליות זמניות

כדי לבדוק אם מכונה וירטואלית היא VM זמני, פועלים לפי השלבים לזיהוי מודל ההקצאה ופעולת הסיום של מכונה וירטואלית.

איך בודקים אם מכונה וירטואלית הופסקה לפני הזמן

כדי לבדוק אם מכונה וירטואלית נדחקה, אפשר להשתמש ב-Google Cloud console, ב-ה-CLI של gcloud או ב-API.

המסוף

כדי לבדוק אם מכונה וירטואלית נדחקה, אפשר לעיין ביומני הפעילות של המערכת.

  1. נכנסים לדף Logs במסוף Google Cloud .

    כניסה לדף Logs

  2. בוחרים פרויקט ולוחצים על המשך.

  3. מוסיפים compute.instances.preempted לשדה סינון לפי תווית או חיפוש טקסט.

  4. אופציונלי, אפשר גם להזין שם של מכונה וירטואלית אם רוצים לראות פעולות של קדימות למכונה וירטואלית ספציפית.

  5. מקישים על Enter כדי להחיל את המסננים שצוינו. במסוף Google Cloud מתעדכנת רשימת היומנים ומוצגות רק הפעולות שבהן בוצעה קדימה למכונה וירטואלית.

  6. בוחרים פעולה ברשימה כדי לראות פרטים על המכונה הווירטואלית שנפסקה.

gcloud


משתמשים בפקודה gcloud compute operations list עם הפרמטר filter כדי לקבל רשימה של אירועי קדימות בפרויקט.

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

אפשר להשתמש בפרמטר filter כדי לצמצם עוד יותר את היקף התוצאות. לדוגמה, כדי לראות אירועי הפסקה זמנית רק למכונות וירטואליות בקבוצת מופעי מכונה מנוהלים:

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

gcloud מחזירה תשובה שדומה לזו:

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

סוג הפעולה compute.instances.preempted מציין שהמכונה הווירטואלית נדחקה. אפשר להשתמש בפקודה operations describe כדי לקבל מידע נוסף על פעולת קדימות ספציפית.

gcloud compute operations describe \
    systemevent-yyyyyyyy

gcloud מחזירה תשובה שדומה לזו:

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

REST


כדי לקבל רשימה של פעולות מערכת מהזמן האחרון, שולחים בקשת GET ל-URI של פעולות באזור.

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

התגובה מכילה רשימה של פעולות מהזמן האחרון.

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

כדי להגדיר את היקף התגובה כך שיוצגו רק פעולות של קדימות, אפשר להוסיף מסנן לבקשת ה-API: operationType="compute.instances.preempted". כדי לראות פעולות של קדימות למכונה וירטואלית ספציפית, מוסיפים את הפרמטר targetLink למסנן: operationType="compute.instances.preempted" AND targetLink="https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/VM_NAME".

אפשרות אחרת היא לבדוק אם מכונה וירטואלית נדחקה מתוך המכונה הווירטואלית עצמה. האפשרות הזו שימושית אם רוצים לטפל בסגירה שמתרחשת בגלל הפקעה של Compute Engine באופן שונה מסגירה רגילה בסקריפט סגירה. כדי לעשות זאת, בודקים את הערך preempted במטא-הנתונים של ברירת המחדל של המכונה הווירטואלית בשרת המטא-נתונים.

לדוגמה, אפשר להשתמש ב-curl מתוך המכונה הווירטואלית כדי לקבל את הערך של preempted:

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

אם הערך הוא TRUE, המכונה הווירטואלית נדחקה על ידי Compute Engine, אחרת הערך הוא FALSE.

אם רוצים להשתמש בזה מחוץ לסקריפט כיבוי, אפשר להוסיף את המחרוזת ?wait_for_change=true לכתובת ה-URL. הפעולה הזו מבצעת בקשת HTTP GET תלויה שמחזירה נתונים רק כשהמטא-נתונים השתנו והמכונה הווירטואלית נדחקה.

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

בדיקת הגדרות הקדימות

אתם יכולים להריץ סימולציות של אירועי תחזוקה במכונות הווירטואליות כדי לכפות עליהן את הפסקת הפעולה. אתם יכולים להשתמש בתכונה הזו כדי לבדוק איך האפליקציות שלכם מטפלות במכונות וירטואליות שניתנות להפסקת פעולה. במאמר בדיקת מדיניות הזמינות מוסבר איך לבדוק אירועי תחזוקה במכונות הווירטואליות.

אפשר גם לדמות את תהליך הדחיקה של מכונה וירטואלית על ידי הפסקת המכונה הווירטואלית. אפשר להשתמש בשיטה הזו במקום לדמות אירוע תחזוקה, והיא לא כפופה למגבלות מכסת השימוש.

שיטות מומלצות

ריכזנו כאן כמה שיטות מומלצות שיעזרו לכם להפיק את המקסימום ממופעי מכונות וירטואליות שניתן להפסיק.

שימוש ב-API של מופעים בכמות גדולה

במקום ליצור מכונות וירטואליות בודדות, אפשר להשתמש ב-API ליצירת מכונות בכמות גדולה.

בחירת צורות קטנות יותר של מכונות

המשאבים של מכונות וירטואליות שניתן להפסיק מגיעים מקיבולת עודפת ומגיבוי. Google Cloud לרוב קל יותר לקבל קיבולת עבור סוגי מכונות קטנים יותר, כלומר סוגי מכונות עם פחות משאבים כמו vCPU וזיכרון. יכול להיות שתמצאו יותר קיבולת למכונות וירטואליות שניתנות להפסקה אם תבחרו סוג מכונה בהתאמה אישית שהוא קטן יותר, אבל סביר יותר שתמצאו קיבולת אם תבחרו סוג מכונה מוגדר מראש שהוא קטן יותר. לדוגמה, בהשוואה לקיבולת של מכונה עם קונפיגורציה מוגדרת (predefined) n2-standard-32, סביר יותר שתהיה קיבולת לסוג המכונה n2-custom-24-96 שמוגדר בהתאמה אישית, אבל סביר עוד יותר שתהיה קיבולת למכונה עם קונפיגורציה מוגדרת (predefined) n2-standard-16.

הפעלת אשכולות גדולים של מכונות וירטואליות שניתן לקטוע את הפעולה שלהן בשעות שבהן העומס נמוך

העומס על Google Cloud מרכזי הנתונים משתנה בהתאם למיקום ולשעה ביום, אבל בדרך כלל הוא הכי נמוך בלילות ובסופי שבוע. לכן, הלילות וסופי השבוע הם הזמנים הכי טובים להפעלת אשכולות גדולים של VM זמני.

תכנון האפליקציות כך שיהיו עמידות בפני כשלים ושיבושים

חשוב להיות מוכנים לעובדה שיש שינויים בדפוסי הקדימות בנקודות זמן שונות. לדוגמה, אם יש הפסקת חשמל חלקית באזור מסוים, יכול להיות שמספר גדול של מכונות וירטואליות זמניות יופסקו כדי לפנות מקום למכונות וירטואליות רגילות שצריך להעביר כחלק מהשחזור. בחלון הזמן הקצר הזה, שיעור הקדימות ייראה שונה מאוד ממה שהוא בדרך כלל. אם האפליקציה שלכם מניחה ששיבושים תמיד מתבצעים בקבוצות קטנות, יכול להיות שלא תהיו מוכנים לאירוע כזה. כדי לבדוק את התנהגות האפליקציה במהלך אירוע של קדימות, אפשר לעצור את המכונה הווירטואלית.

ניסיון חוזר ליצירת מכונות וירטואליות שהופסקו לפני הזמן

אם המכונה הווירטואלית שלכם נדחקה, נסו ליצור מכונות וירטואליות חדשות שניתנות לדחיקה פעם או פעמיים לפני שתחזרו למכונות וירטואליות רגילות. בהתאם לדרישות שלכם, כדאי לשלב בין מכונות וירטואליות רגילות לבין מכונות וירטואליות שניתנות להפסקת פעולה באשכולות, כדי להבטיח שהעבודה תתבצע בקצב מתאים.

שימוש בסקריפטים של כיבוי

אפשר לנהל את ההודעות על כיבוי והקצאה מראש באמצעות סקריפט כיבוי שיכול לשמור את ההתקדמות של עבודה מסוימת, כך שהיא תוכל להמשיך מהמקום שבו היא הפסיקה, במקום להתחיל מחדש.

מה השלב הבא?