

 適用於 .NET 的 AWS SDK V3 已進入維護模式。

我們建議您遷移至 [適用於 .NET 的 AWS SDK V4](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/welcome.html)。如需如何遷移的其他詳細資訊和資訊，請參閱我們的[維護模式公告](https://aws.amazon.com/blogs/developer/aws-sdk-for-net-v3-maintenance-mode-announcement/)。

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# Amazon EC2 Spot 執行個體教學課程
<a name="how-to-spot-instances"></a>

本教學課程說明如何使用 適用於 .NET 的 AWS SDK 來管理 Amazon EC2 Spot 執行個體。

## 概觀
<a name="tutor-spot-net-overview"></a>

Spot 執行個體可讓您以低於隨需價格的價格請求未使用的 Amazon EC2 容量。這可以大幅降低可中斷之應用程式的 EC2 成本。

以下是 Spot 執行個體的請求和使用方式的高階摘要。

1. 建立 Spot 執行個體請求，指定您願意支付的最高價格。

1. 完成請求後，像執行任何其他 Amazon EC2 執行個體一樣執行執行個體。

1. 視需要執行執行個體，然後終止執行個體，除非 *Spot 價格*變更，讓執行個體為您終止。

1. 當您不再需要 Spot 執行個體請求時，請將其清除，以便不再建立 Spot 執行個體。

這是 Spot 執行個體的非常高階概觀。若要進一步了解 Spot 執行個體，請參閱《[Amazon EC2 使用者指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)》中的 [Spot 執行個體](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html)。

## 關於本教學
<a name="about-spot-instances-tutorial"></a>

當您遵循本教學課程時，您可以使用 適用於 .NET 的 AWS SDK 執行下列動作：
+ 建立 Spot 執行個體請求
+ 判斷 Spot 執行個體請求何時完成
+ 取消 Spot 執行個體請求
+ 終止關聯的執行個體

下列各節提供此範例的程式碼片段和其他資訊。[範例的完整程式碼](#tutor-spot-net-main)會顯示在程式碼片段之後，並可依原樣建置和執行。

**Topics**
+ [概觀](#tutor-spot-net-overview)
+ [關於本教學](#about-spot-instances-tutorial)
+ [先決條件](#tutor-spot-net-prereq)
+ [收集您需要的內容](#tutor-spot-net-gather)
+ [建立 Spot 執行個體請求](#tutor-spot-net-submit)
+ [判斷 Spot 執行個體請求的狀態](#tutor-spot-net-request-state)
+ [清除 Spot 執行個體請求](#tutor-spot-net-clean-up-request)
+ [清除 Spot 執行個體](#tutor-spot-net-clean-up-instance)
+ [完成程式碼](#tutor-spot-net-main)
+ [其他考量](#tutor-spot-net-additional)

## 先決條件
<a name="tutor-spot-net-prereq"></a>

如需 APIs和先決條件的相關資訊，請參閱父區段 ([使用 Amazon EC2](ec2-apis-intro.md))。

## 收集您需要的內容
<a name="tutor-spot-net-gather"></a>

若要建立 Spot 執行個體請求，您需要幾件事。
+ 執行個體數量及其執行個體類型。有數種執行個體類型可供選擇。如需詳細資訊，請參閱[《Amazon EC2 使用者指南》中的 Amazon EC2 執行個體類型](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html)。 [Amazon EC2 ](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/) 另請參閱[執行個體類型詳細資訊](https://aws.amazon.com/ec2/instance-types/)和[執行個體類型總](https://aws.amazon.com/ec2/instance-explorer/)管。

  此教學課程的預設編號為 1。
+ 用來建立執行個體的 Amazon Machine Image (AMI)。如需 AMIs的相關資訊，請參閱《[Amazon EC2 使用者指南》中的 Amazon Machine Image (AMIs)](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html)。 [Amazon EC2 ](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/) 特別是，請參閱[尋找 AMI](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/finding-an-ami.html) 和[共用 AMIs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/sharing-amis.html)。
+ 您願意為每個執行個體小時支付的最高價格。您可以在 [Amazon EC2 定價頁面上](https://aws.amazon.com/ec2/pricing/)查看所有執行個體類型 （適用於隨需執行個體和 Spot 執行個體） 的價格。本教學課程的預設價格將於稍後說明。
+ 如果您想要從遠端連線至執行個體，則為具有適當組態和資源的安全群組。這在 中說明，[在 Amazon EC2 中使用安全群組](security-groups.md)以及[收集您需要的內容](run-instance.md#run-instance-gather)並[連接到 中執行個體](run-instance.md#connect-to-instance)的相關資訊[啟動 Amazon EC2 執行個體](run-instance.md)。為求簡化，本教學課程使用****所有較新 AWS 帳戶預設的安全群組。

請求 Spot 執行個體的方法有很多種。以下是常見的策略：
+ 提出一定低於隨需定價的請求。
+ 根據產生的運算值提出請求。
+ 提出請求以盡快取得運算容量。

下列說明參考《[Amazon EC2 使用者指南](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/)》中的 [Spot 執行個體定價歷史記錄](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances-history.html)。

### 將成本降低到低於隨需
<a name="reduce-cost"></a>

您可以批次處理任務，需要花費數小時或數天的時間完成。但是，在開始和結束時都有彈性。您想要查看是否能以低於隨需執行個體的成本將它完成。

您可以使用 Amazon EC2 主控台或 Amazon EC2 API 來檢查執行個體類型的 Spot 價格歷史記錄。於一個給定的可用區域內分析您所想要的執行個體類型的歷史價格後，您的出價有兩個替代方式：
+ 在 Spot 價格範圍的上端指定請求，該請求仍低於隨需價格，預期您的一次性 Spot 執行個體請求最有可能實現並執行足夠的連續運算時間來完成任務。
+ 或者，您可以出價此 Spot 價格範圍的下限，並計畫如何透過一個持久性的請求來結合數個已長期啟動的執行個體。該執行個體將總共要執行一段夠長時間，才能以更低的成本完成任務。

### 支付不超過結果值的費用
<a name="value-of-result"></a>

您有一個要執行的資料正在處理任務。您充分了解任務結果的值，以得知其在運算成本方面的價值。

分析執行個體類型的 Spot 價格歷史記錄後，您可以選擇運算時間成本不超過任務結果值的價格。您建立可以長久出價的方式，且允許它在 Spot 價格出現波動並等於或低於您的出價時，間歇性地執行。

### 快速取得運算容量
<a name="acquire-quickly"></a>

對於無法透過隨需執行個體取得的額外容量，您有非預期的短期需求。分析執行個體類型的 Spot 價格歷史記錄後，您可以選擇高於最高歷史價格的價格，以大幅提高快速完成請求的可能性，並繼續運算，直到完成為止。

在您收集所需的內容並選擇策略之後，您就可以請求 Spot 執行個體。在本教學課程中，預設最大 Spot 執行個體價格設定為與隨需價格相同 (在本教學課程中為 \$10.003 美元)。以這種方法設定價格能夠最大化實現請求的機會。

## 建立 Spot 執行個體請求
<a name="tutor-spot-net-submit"></a>

下列程式碼片段說明如何使用先前收集的元素建立 Spot 執行個體請求。

[本主題結尾](#tutor-spot-net-main)的範例顯示此程式碼片段正在使用中。

```
    //
    // Method to create a Spot Instance request
    private static async Task<SpotInstanceRequest> CreateSpotInstanceRequest(
      IAmazonEC2 ec2Client, string amiId, string securityGroupName,
      InstanceType instanceType, string spotPrice, int instanceCount)
    {
      var launchSpecification = new LaunchSpecification{
        ImageId = amiId,
        InstanceType = instanceType
      };
      launchSpecification.SecurityGroups.Add(securityGroupName);
      var request = new RequestSpotInstancesRequest{
        SpotPrice = spotPrice,
        InstanceCount = instanceCount,
        LaunchSpecification = launchSpecification
      };

      RequestSpotInstancesResponse result =
        await ec2Client.RequestSpotInstancesAsync(request);
      return result.SpotInstanceRequests[0];
    }
```

從此方法傳回的重要值是 Spot 執行個體請求 ID，其中包含在傳回的 [SpotInstanceRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TSpotInstanceRequest.html) 物件`SpotInstanceRequestId`成員中。

**注意**  
您將需要為啟動的任何 Spot 執行個體支付費用。為了避免不必要的成本，請務必[取消任何請求](#tutor-spot-net-clean-up-request)並[終止任何執行個體](#tutor-spot-net-clean-up-instance)。

## 判斷 Spot 執行個體請求的狀態
<a name="tutor-spot-net-request-state"></a>

下列程式碼片段說明如何取得 Spot 執行個體請求的相關資訊。您可以使用該資訊在程式碼中做出特定決策，例如是否繼續等待 Spot 執行個體請求履行。

[本主題結尾](#tutor-spot-net-main)的範例顯示此程式碼片段正在使用中。

```
    //
    // Method to get information about a Spot Instance request, including the status,
    // instance ID, etc.
    // It gets the information for a specific request (as opposed to all requests).
    private static async Task<SpotInstanceRequest> GetSpotInstanceRequestInfo(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
      return describeResponse.SpotInstanceRequests[0];
    }
```

方法會傳回 Spot 執行個體請求的相關資訊，例如執行個體 ID、其狀態和狀態碼。如需 Spot 執行個體請求狀態碼的詳細資訊，請參閱《[Amazon EC2 使用者指南》中的](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/) [Spot 請求狀態](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html#spot-instance-bid-status-understand)。

## 清除 Spot 執行個體請求
<a name="tutor-spot-net-clean-up-request"></a>

當您不再需要請求 Spot 執行個體時，請務必取消任何未完成的請求，以防止這些請求重新履行。下列程式碼片段說明如何取消 Spot 執行個體請求。

[本主題結尾](#tutor-spot-net-main)的範例顯示此程式碼片段正在使用中。

```
    //
    // Method to cancel a Spot Instance request
    private static async Task CancelSpotInstanceRequest(
      IAmazonEC2 ec2Client, string requestId)
    {
      var cancelRequest = new CancelSpotInstanceRequestsRequest();
      cancelRequest.SpotInstanceRequestIds.Add(requestId);

      await ec2Client.CancelSpotInstanceRequestsAsync(cancelRequest);
    }
```

## 清除 Spot 執行個體
<a name="tutor-spot-net-clean-up-instance"></a>

為了避免不必要的成本，請務必終止從 Spot 執行個體請求啟動的任何執行個體；只要取消 Spot 執行個體請求就不會終止您的執行個體，這表示您將繼續支付這些執行個體的費用。下列程式碼片段示範如何在取得作用中 Spot 執行個體的執行個體識別符後終止執行個體。

[本主題結尾](#tutor-spot-net-main)的範例顯示此程式碼片段正在使用中。

```
    //
    // Method to terminate a Spot Instance
    private static async Task TerminateSpotInstance(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      // Retrieve the Spot Instance request to check for running instances.
      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);

      // If there are any running instances, terminate them
      if(   (describeResponse.SpotInstanceRequests[0].Status.Code
              == "request-canceled-and-instance-running")
         || (describeResponse.SpotInstanceRequests[0].State == SpotInstanceState.Active))
      {
        TerminateInstancesResponse response =
          await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
            InstanceIds = new List<string>(){
              describeResponse.SpotInstanceRequests[0].InstanceId } });
        foreach (InstanceStateChange item in response.TerminatingInstances)
        {
          Console.WriteLine($"\n  Terminated instance: {item.InstanceId}");
          Console.WriteLine($"  Instance state: {item.CurrentState.Name}\n");
        }
      }
    }
```

## 完成程式碼
<a name="tutor-spot-net-main"></a>

下列程式碼範例會呼叫先前所述的方法來建立和取消 Spot 執行個體請求，並終止 Spot 執行個體。

### 開發套件參考
<a name="w2aac19c15c17c21c43b5b1"></a>

NuGet 套件：
+ [AWSSDK.EC2](https://www.nuget.org/packages/AWSSDK.EC2)

程式設計元素：
+ 命名空間 [Amazon.EC2](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/NEC2.html)

  [AmazonEC2Client](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TEC2Client.html) 類別

  類別 [InstanceType](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TInstanceType.html)
+ 命名空間 [Amazon.EC2.Model](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/NEC2Model.html)

  類別 [CancelSpotInstanceRequestsRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TCancelSpotInstanceRequestsRequest.html)

  類別 [DescribeSpotInstanceRequestsRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TDescribeSpotInstanceRequestsRequest.html)

  類別 [DescribeSpotInstanceRequestsResponse](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TDescribeSpotInstanceRequestsResponse.html)

  類別 [InstanceStateChange](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TInstanceStateChange.html)

  類別 [LaunchSpecification](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TLaunchSpecification.html)

  Class [RequestSpotInstancesRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TRequestSpotInstancesRequest.html)

  Class [RequestSpotInstancesResponse](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TRequestSpotInstancesResponse.html)

  類別 [SpotInstanceRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TSpotInstanceRequest.html)

  類別 [TerminateInstancesRequest](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TTerminateInstancesRequest.html)

  類別 [TerminateInstancesResponse](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/EC2/TTerminateInstancesResponse.html)

### 程式碼
<a name="w2aac19c15c17c21c43b7b1"></a>

```
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.EC2;
using Amazon.EC2.Model;

namespace EC2SpotInstanceRequests
{
  class Program
  {
    static async Task Main(string[] args)
    {
      // Some default values.
      // These could be made into command-line arguments instead.
      var instanceType = InstanceType.T1Micro;
      string securityGroupName = "default";
      string spotPrice = "0.003";
      int instanceCount = 1;

      // Parse the command line arguments
      if((args.Length != 1) || (!args[0].StartsWith("ami-")))
      {
        Console.WriteLine("\nUsage: EC2SpotInstanceRequests ami");
        Console.WriteLine("  ami: the Amazon Machine Image to use for the Spot Instances.");
        return;
      }

      // Create the Amazon EC2 client.
      var ec2Client = new AmazonEC2Client();

      // Create the Spot Instance request and record its ID
      Console.WriteLine("\nCreating spot instance request...");
      var req = await CreateSpotInstanceRequest(
        ec2Client, args[0], securityGroupName, instanceType, spotPrice, instanceCount);
      string requestId = req.SpotInstanceRequestId;

      // Wait for an EC2 Spot Instance to become active
      Console.WriteLine(
        $"Waiting for Spot Instance request with ID {requestId} to become active...");
      int wait = 1;
      var start = DateTime.Now;
      while(true)
      {
        Console.Write(".");

        // Get and check the status to see if the request has been fulfilled.
        var requestInfo = await GetSpotInstanceRequestInfo(ec2Client, requestId);
        if(requestInfo.Status.Code == "fulfilled")
        {
          Console.WriteLine($"\nSpot Instance request {requestId} " +
            $"has been fulfilled by instance {requestInfo.InstanceId}.\n");
          break;
        }

        // Wait a bit and try again, longer each time (1, 2, 4, ...)
        Thread.Sleep(wait);
        wait = wait * 2;
      }

      // Show the user how long it took to fulfill the Spot Instance request.
      TimeSpan span = DateTime.Now.Subtract(start);
      Console.WriteLine($"That took {span.TotalMilliseconds} milliseconds");

      // Perform actions here as needed.
      // For this example, simply wait for the user to hit a key.
      // That gives them a chance to look at the EC2 console to see
      // the running instance if they want to.
      Console.WriteLine("Press any key to start the cleanup...");
      Console.ReadKey(true);

      // Cancel the request.
      // Do this first to make sure that the request can't be re-fulfilled
      // once the Spot Instance has been terminated.
      Console.WriteLine("Canceling Spot Instance request...");
      await CancelSpotInstanceRequest(ec2Client, requestId);

      // Terminate the Spot Instance that's running.
      Console.WriteLine("Terminating the running Spot Instance...");
      await TerminateSpotInstance(ec2Client, requestId);

      Console.WriteLine("Done. Press any key to exit...");
      Console.ReadKey(true);
    }


    //
    // Method to create a Spot Instance request
    private static async Task<SpotInstanceRequest> CreateSpotInstanceRequest(
      IAmazonEC2 ec2Client, string amiId, string securityGroupName,
      InstanceType instanceType, string spotPrice, int instanceCount)
    {
      var launchSpecification = new LaunchSpecification{
        ImageId = amiId,
        InstanceType = instanceType
      };
      launchSpecification.SecurityGroups.Add(securityGroupName);
      var request = new RequestSpotInstancesRequest{
        SpotPrice = spotPrice,
        InstanceCount = instanceCount,
        LaunchSpecification = launchSpecification
      };

      RequestSpotInstancesResponse result =
        await ec2Client.RequestSpotInstancesAsync(request);
      return result.SpotInstanceRequests[0];
    }


    //
    // Method to get information about a Spot Instance request, including the status,
    // instance ID, etc.
    // It gets the information for a specific request (as opposed to all requests).
    private static async Task<SpotInstanceRequest> GetSpotInstanceRequestInfo(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);
      return describeResponse.SpotInstanceRequests[0];
    }


    //
    // Method to cancel a Spot Instance request
    private static async Task CancelSpotInstanceRequest(
      IAmazonEC2 ec2Client, string requestId)
    {
      var cancelRequest = new CancelSpotInstanceRequestsRequest();
      cancelRequest.SpotInstanceRequestIds.Add(requestId);

      await ec2Client.CancelSpotInstanceRequestsAsync(cancelRequest);
    }


    //
    // Method to terminate a Spot Instance
    private static async Task TerminateSpotInstance(
      IAmazonEC2 ec2Client, string requestId)
    {
      var describeRequest = new DescribeSpotInstanceRequestsRequest();
      describeRequest.SpotInstanceRequestIds.Add(requestId);

      // Retrieve the Spot Instance request to check for running instances.
      DescribeSpotInstanceRequestsResponse describeResponse =
        await ec2Client.DescribeSpotInstanceRequestsAsync(describeRequest);

      // If there are any running instances, terminate them
      if(   (describeResponse.SpotInstanceRequests[0].Status.Code
              == "request-canceled-and-instance-running")
         || (describeResponse.SpotInstanceRequests[0].State == SpotInstanceState.Active))
      {
        TerminateInstancesResponse response =
          await ec2Client.TerminateInstancesAsync(new TerminateInstancesRequest{
            InstanceIds = new List<string>(){
              describeResponse.SpotInstanceRequests[0].InstanceId } });
        foreach (InstanceStateChange item in response.TerminatingInstances)
        {
          Console.WriteLine($"\n  Terminated instance: {item.InstanceId}");
          Console.WriteLine($"  Instance state: {item.CurrentState.Name}\n");
        }
      }
    }
  }
}
```

## 其他考量
<a name="tutor-spot-net-additional"></a>
+ 執行教學課程後，最好登入 [Amazon EC2 主控台](https://console.aws.amazon.com/ec2/)，確認 [Spot 執行個體請求](https://console.aws.amazon.com/ec2/home#SpotInstances:)已取消，且 [Spot 執行個體](https://console.aws.amazon.com/ec2/v2/home#Instances)已終止。