선점형 VM 만들기 및 사용


이 페이지에서는 선점형 가상 머신(VM) 인스턴스를 만들고 사용하는 방법을 설명합니다. 선점형 VM은 표준 VM 가격에 비해 60~91% 할인된 가격으로 제공됩니다. 하지만 Compute Engine이 다른 작업 때문에 리소스를 회수해야 하는 경우 이러한 VM을 중지(선점)할 수 있습니다. 선점형 VM은 항상 24시간 후에 중지됩니다. 선점형 VM은 VM 선점을 감당할 수 있는 내결함성 애플리케이션에만 권장됩니다. 선점형 VM을 만들기로 결정하기 전에 애플리케이션에서 선점을 처리할 수 있는지 확인하세요. 선점형 VM의 위험과 가치를 이해하려면 선점형 VM 문서를 참조하세요.

시작하기 전에

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

    이 페이지의 샘플 사용 방법에 대한 탭을 선택하세요.

    콘솔

    Google Cloud 콘솔을 사용하여 Google Cloud 서비스 및 API에 액세스할 때는 인증을 설정할 필요가 없습니다.

    gcloud

    1. Google Cloud CLI를 설치한 후 다음 명령어를 실행하여 초기화합니다.

      gcloud init
    2. 기본 리전 및 영역을 설정합니다.

    Go

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

    1. Google Cloud CLI를 설치합니다.
    2. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

      gcloud init
    3. Google 계정의 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

    자세한 내용은 로컬 개발 환경의 인증 설정를 참조하세요.

    Java

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

    1. Google Cloud CLI를 설치합니다.
    2. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

      gcloud init
    3. Google 계정의 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

    자세한 내용은 로컬 개발 환경의 인증 설정를 참조하세요.

    Node.js

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

    1. Google Cloud CLI를 설치합니다.
    2. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

      gcloud init
    3. Google 계정의 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

    자세한 내용은 로컬 개발 환경의 인증 설정를 참조하세요.

    Python

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

    1. Google Cloud CLI를 설치합니다.
    2. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

      gcloud init
    3. Google 계정의 로컬 인증 사용자 인증 정보를 만듭니다.

      gcloud auth application-default login

    자세한 내용은 로컬 개발 환경의 인증 설정를 참조하세요.

    REST

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

      Google Cloud CLI를 설치한 후 다음 명령어를 실행하여 초기화합니다.

      gcloud init

선점형 VM 만들기

gcloud CLI 또는 Compute Engine API를 사용하여 선점형 VM을 만듭니다. Google Cloud 콘솔을 사용하려면 대신 Spot VM을 만듭니다.

gcloud

gcloud compute를 사용하는 경우 일반 VM을 만들 때와 동일한 instances create 명령어를 사용하되, --preemptible 플래그를 추가합니다.

gcloud compute instances create [VM_NAME] --preemptible

여기서 [VM_NAME]은 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
}

자바


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

API에서 일반적인 VM 만들기 요청을 작성하되, scheduling 아래에 preemptible 속성을 포함하고 이를 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 할당량

선점형 VM은 표준 VM과 같은 사용 가능한 CPU 할당량이 필요합니다. 표준 VM에 대해 선점형 VM이 CPU 할당량을 소비하지 않도록 방지하려면 특별한 '선점형 CPU' 할당량을 요청할 수 있습니다. Compute Engine이 이 리전에 선점형 CPU 할당량을 부여하면 모든 선점형 VM이 해당 할당량으로 계산되고 모든 표준 VM은 계속 표준 CPU 할당량으로 계산됩니다.

선점형 CPU 할당량이 없는 리전에서는 표준 CPU 할당량을 사용하여 선점형 VM을 시작할 수 있습니다. 여느 때처럼 충분한 IP와 디스크 할당량도 필요합니다. Compute Engine이 할당량을 부여하지 않으면 gcloud CLI나 Google Cloud 콘솔 할당량 페이지에 선점형 CPU 할당량이 표시되지 않습니다.

할당량에 대한 자세한 내용은 리소스 할당량 페이지를 참조하세요.

선점형 VM 시작

다른 VM과 마찬가지로 선점형 VM이 중지되거나 선점된 경우 VM을 다시 시작하고 RUNNING 상태로 되돌릴 수 있습니다. 선점형 VM을 시작하면 24시간 카운터가 재설정되지만 여전히 선점형 VM이므로 Compute Engine이 24시간 전에 선점될 수 있습니다. 선점형 VM은 실행 중인 표준 VM으로 변환할 수 없습니다.

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

종료 스크립트로 선점 처리

VM이 선점되면 종료 스크립트를 사용하여 VM이 중지되기 전에 정리 작업을 수행할 수 있습니다. 예를 들어 실행 중인 프로세스를 정상적으로 중지하고 체크포인트 파일을 Cloud Storage에 복사할 수 있습니다.

다음은 실행 중인 선점형 VM에 추가하거나 선점형 VM을 만들 때 추가할 수 있는 종료 스크립트입니다. 이 스크립트는 운영체제의 일반 kill 명령어가 나머지 프로세스를 모두 중지하기 전 VM이 종료되기 시작할 때 실행됩니다. 원하는 프로그램을 정상적으로 중지한 후 스크립트는 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."

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

  1. 종료 스크립트를 로컬 워크 스테이션으로 복사하거나 다운로드합니다.
  2. 수정할 파일을 열고 다음 변수를 변경합니다.
    • [PROGRAM_NAME]은 종료할 프로세스나 프로그램의 이름입니다. 예를 들면 apache2 또는 nginx입니다.
    • [LOCAL_USER]는 가상 머신에 로그인할 때 사용한 사용자 이름입니다.
    • [BUCKET_NAME]은 프로그램의 체크포인트 파일을 저장할 Cloud Storage 버킷의 이름입니다. 이 경우 버킷 이름은 gs://로 시작되지 않습니다.
  3. 변경사항을 저장합니다.
  4. 새 VM이나 기존 VM에 종료 스크립트를 추가합니다.

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

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

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

선점형 VM 식별

VM이 선점형 VM인지 확인하려면 VM 프로비저닝 모델 및 종료 작업 식별 단계를 수행합니다.

VM이 선점되었는지 확인

Google Cloud 콘솔, gcloud CLI, API를 통해 VM이 선점되었는지 확인합니다.

콘솔

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

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

    로그로 이동

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

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

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

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

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

gcloud


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

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

filter 매개변수를 사용하여 결과 범위를 세부적으로 지정할 수 있습니다. 예를 들어 관리형 인스턴스 그룹 내의 VM에 대한 선점 이벤트만 확인하려면 다음을 실행합니다.

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

gcloud는 다음과 비슷한 응답을 반환합니다.

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

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

gcloud compute operations describe \
    systemevent-xxxxxxxx

gcloud는 다음과 비슷한 응답을 반환합니다.

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


최근 시스템 작업 목록을 가져오려면 영역 작업의 URI에 GET 요청을 보냅니다.

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

응답에는 최근 작업 목록이 포함됩니다.

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

선점 작업만 표시하도록 응답 범위를 지정하려면 다음과 같이 API 요청에 필터를 추가합니다. operationType="compute.instances.preempted" 특정 VM의 선점 작업을 확인하려면 다음과 같이 필터에 targetLink 매개변수를 추가합니다. operationType="compute.instances.preempted" AND targetLink="https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[VM_NAME]"

VM이 VM 내부에서 선점되었는지 여부도 확인할 수도 있습니다. 이 기능은 Compute Engine 선점으로 인해 종료 스크립트의 정상 종료와 다른 방식으로 종료를 처리하려는 경우에 유용합니다. 이렇게 하려면 메타데이터 서버에서 VM의 기본 인스턴스 메타데이터에 있는 preempted 값을 확인하면 됩니다.

예를 들어 VM 내에서 curl을 사용하여 preempted 메타데이터 경로의 값을 구할 수 있습니다.

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

이 값이 TRUE이면 VM이 Compute Engine에 의해 선점된 것이고, 그렇지 않으면 FALSE입니다.

종료 스크립트 밖에서 이 값을 사용하려면 URL에 ?wait_for_change=true를 추가하면 됩니다. 그러면 메타데이터가 변경되고 VM이 선점된 경우에만 반환되는 중지된 HTTP GET 요청이 수행됩니다.

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

선점 설정 테스트

VM에서 시뮬레이션된 유지보수 이벤트를 실행해 선점을 강제로 적용해 볼 수 있습니다. 이 기능을 이용해 앱이 선점형 VM을 어떻게 처리하는지 테스트해 보세요. VM에서 유지보수 이벤트를 테스트하는 방법은 가용성 정책 테스트를 참조하세요.

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

권장사항

다음은 선점형 VM 인스턴스를 최대한 활용하는 데 도움이 되는 몇 가지 권장사항입니다.

일괄 인스턴스 API 사용

단일 VM을 만드는 대신 일괄 인스턴스 API를 사용할 수 있습니다.

작은 머신 유형 선택

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

사용량이 적을 때 대규모 선점형 VM 클러스터 실행

Google Cloud 데이터 센터의 부하는 위치와 시간에 따라 다르지만 일반적으로 야간과 주말에 가장 낮습니다. 따라서 큰 선점형 VM 클러스터를 실행하기에 가장 좋은 시간은 야간과 주말입니다.

결함 및 선점을 허용하도록 애플리케이션 설계

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

선점된 VM 만들기 재시도

VM 인스턴스가 선점되었으면 표준 VM으로 돌아가기 전에 새로운 선점형 VM을 한두 번 다시 시도하는 것이 좋습니다. 요구사항에 따라 작업이 적절한 속도로 진행되도록 클러스터에서 표준 VM과 선점형 VM을 결합하는 것이 좋습니다.

종료 스크립트 사용

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

다음 단계