Amazon EC2 Spot 实例教程 - 适用于 .NET 的 AWS 开发工具包
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

Amazon EC2 Spot 实例教程

本教程介绍如何使用 适用于 .NET 的 AWS 开发工具包 管理 Amazon EC2 Spot 实例。

Overview

通过 Spot 实例,您可以请求低于按需价格的未使用Amazon EC2容量。这可以显著降低可能中断的应用程序的 EC2 成本。

以下是如何请求和使用 Spot 实例的高级摘要。

  1. 创建 Spot 实例请求,并指定您愿意支付的最高价。

  2. 满足请求后,像运行任何其他实例一样运行Amazon EC2实例。

  3. 根据需要运行实例,然后终止实例,除非 Spot 价格发生变化,从而为您终止实例。

  4. 当您不再需要 Spot 实例时清除它,以便不再创建 Spot 实例。

这是 Spot 实例的简要概述。您可以通过在 适用于 Linux 的 EC2 用户指南中阅读有关 Spot 实例的信息来更好地了解它们适用于 Windows 的 EC2 用户指南

关于本教程

在按照本教程操作时,您将使用 适用于 .NET 的 AWS 开发工具包 执行以下操作:

  • 创建 Spot 实例请求

  • 确定 Spot 实例请求的执行时间

  • 取消 Spot 实例请求

  • 终止相关实例

以下部分提供了此示例中的代码段和其他信息。示例完成代码的完整代码显示在代码段之后,可以按原样构建和运行。

Prerequisites

有关 APIs 和先决条件的信息,请参阅父部分 (使用 Amazon EC2)。

收集所需的内容

要创建 Spot 实例请求,您需要几个事物。

有很多方法可以请求 Spot 实例。以下是常见的策略:

  • 发出确保成本低于按需定价的请求。

  • 根据生成的计算值发出请求。

  • 发出请求以便尽快获取计算容量。

以下说明引用了 适用于 Linux 的 EC2 用户指南 或 中的 Spot 适用于 Windows 的 EC2 用户指南 价格历史记录。

您需要进行花费数小时或数天的批处理工作。然而,您可以灵活调整启动和结束时间。您希望看到是否以低于按需实例的成本完成。

您可以使用 Amazon EC2 控制台或 Amazon EC2 API 检查实例类型的 Spot 价格历史记录。在您分析了给定可用区内所需实例类型的价格记录之后,您有两种可供选择的方法发出请求:

  • 指定 Spot 价格范围(这仍然低于按需价格)的上限的请求,预测您的一次性 Spot 实例请求很有可能会执行,并运行足够的连续计算时间来完成作业。

  • 指定在 Spot 价格范围的下限发出请求,随着时间的推移,通过持久的请求,计划结合多种已启动实例。总计一下,该实例会以较低的总成本、花费很长时间来完成这项工作。

您需要进行数据处理工作。您将会非常了解作业结果的价值,从而足以知道它们在计算成本方面的价值。

在分析了实例类型的 Spot 价格记录之后,您选择一个计算时间成本不高于该工作结果成本的价格。由于 Spot 价格的波动,该价格可能会达到或低于您的请求,所以您要创建一个持久请求,并允许它间歇运行。

您对附加容量有一个无法预料的短期需求,该容量不能通过按需实例获得。在您分析了实例类型的 Spot 价格历史记录之后,您选择一个高于历史最高价格的价格,以大幅提高快速执行请求的可能性,并继续计算,直到完成实例。

收集所需内容并选择策略后,您便可以请求 Spot 实例。对于本教程,默认的最高 Spot 实例价格设置为与按需价格相同(本教程为 0.003 美元)。以这种方式设置价格可最大限度地提高请求被执行的机会。

创建 Spot 实例请求

以下代码段说明如何使用之前收集的元素创建 Spot 实例请求。

本主题完成代码末尾的示例显示了此代码段正在使用中。

// // 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,它包含在返回的 SpotInstanceRequestIdSpotInstanceRequest对象的 成员中。

注意

您将需要为启动的任何 Spot 实例付费。为避免产生不必要的费用,请确保取消任何 请求终止任何 实例

确定您的 Spot 实例请求的状态

以下代码段说明如何获取有关 Spot 实例请求的信息。您可以使用该信息在代码中做出某些决策,例如,是否继续等待 Spot 实例请求完成。

本主题完成代码末尾的示例显示了此代码段正在使用中。

// // 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、状态和状态代码。您可以在 适用于 Linux 的 EC2 用户指南中查看 Spot 实例请求的状态代码适用于 Windows 的 EC2 用户指南

清除 Spot 实例请求

当您不再需要请求 Spot 实例时,请务必取消任何未完成的请求以防止这些请求重新执行。以下代码段说明如何取消 Spot 实例请求。

本主题完成代码末尾的示例显示了此代码段正在使用中。

// // 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 实例

为避免产生不必要的费用,终止从 Spot 实例请求启动的任何实例非常重要;只需取消 Spot 实例请求不会终止您的实例,这意味着您需要继续为它们付费。以下代码段向您演示如何在获取活动 Spot 实例的实例标识符后终止实例。

本主题完成代码末尾的示例显示了此代码段正在使用中。

// // 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"); } } }

完成代码

以下代码示例调用上述方法来创建和取消 Spot 实例请求并终止 Spot 实例。

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"); } } } } }

其他注意事项