Amazon EC2 Spot 執行個體教學課程 - AWS SDK for .NET

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

Amazon EC2 Spot 執行個體教學課程

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

概觀

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

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

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

  2. 完成請求時,請像執行任何其他 Amazon EC2 執行個體一樣執行執行個體。

  3. 執行執行個體,只要您想要,然後終止執行個體,除非 Spot 價格發生變更,以便為您終止執行個體。

  4. 當您不再需要 Spot 執行個體請求時,請清除該請求,以便不再建立 Spot 執行個體。

這是 Spot 執行個體的高度概觀。若要進一步了解 Spot 執行個體,請參閱 Amazon EC2 使用者指南中的 Spot 執行個體

關於本教學

遵循本教學課程時,您可以使用 AWS SDK for .NET 執行下列動作:

  • 建立 Spot 執行個體請求

  • 判斷 Spot 執行個體請求何時完成

  • 取消 Spot 執行個體請求

  • 終止關聯的執行個體

下列各節提供此範例的程式碼片段和其他資訊。範例的完整程式碼會顯示在程式碼片段之後,並且可以像原樣建置和執行。

必要條件

如需 APIs 和先決條件的相關資訊,請參閱父區段 (使用 Amazon EC2)。

收集您需要的內容

若要建立 Spot 執行個體請求,您需要幾件事。

  • 您願意按執行個體小時支付的最高價格。您可以在 Amazon EC2 定價頁面上查看所有執行個體類型 (隨需執行個體和 Spot 執行個體) 的價格。本教學課程的預設價格將於稍後說明。

請求 Spot 執行個體的方法有很多種。以下是常見的策略:

  • 提出一定低於隨需定價的請求。

  • 根據產生的運算值提出請求。

  • 提出請求,以便盡快取得運算容量。

下列說明參考 Amazon EC2 使用者指南中的 Spot 執行個體定價歷史記錄

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

您可以使用 Amazon EC2 主控台或 Amazon EC2 來檢查 Spot Price 歷史記錄的執行個體類型API。於一個給定的可用區域內分析您所想要的執行個體類型的歷史價格後,您的出價有兩個替代方式:

  • 在 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,該 ID 包含在傳回的 SpotInstanceRequest 物件SpotInstanceRequestId的成員中。

注意

任何已啟動的 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、其狀態和狀態碼。如需 Spot 執行個體請求狀態碼的詳細資訊,請參閱 Amazon EC2 使用者指南中的 Spot 請求狀態

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

其他考量