教學課程:Amazon EC2Spot Instances - AWS SDK for Java 1.x

截至 2024 年 7 月 31 日, AWS SDK for Java 1.x 已進入維護模式,並將end-of-support在 2025 年 12 月 31 日送達。我們建議您遷移至 AWS SDK for Java 2.x,以繼續接收新功能、可用性改善和安全性更新。

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

教學課程:Amazon EC2Spot Instances

概觀

競價型實例使您能夠對未使用的出價Amazon Elastic Compute Cloud(Amazon EC2) 容量與按需實例價格相比,容量不超過 90%,並且只要您的出價超過當前Spot 價格。Amazon EC2根據供需狀況定期更改 Spot 價格,且出價符合或超過 Spot 價格的顧客可存取可用的 Spot 執行個體。像隨需隨需執行個體和預留執行個體,Spot 執行個體提供另一個取得更多運算容量的方式。

競價型實例可以顯著降低您的Amazon EC2成本用於批次處理、科學研究、影像處理、影片編碼、數據和網路爬取、財務分析和測試。此外,Spot 執行個體讓您可以存取大量額外容量,可讓您存取不急需此容量的情況。

若要使用 Spot 執行個體,您可以提出 Spot 執行個體請求,並指定您願意支付每一個小時使用一個執行個體的最高預算;此為您的出價。如果您出價符合或超出目前競價型價格,則會履行您的請求,您的執行個體將會執行,直到選擇終止或競價型價格增加到高於出價 (以先到者為準)。

重要的是要注意:

  • 通常您所付的費用會低於您的出價。Amazon EC2 定期依照需求及供應量調整 Spot 價格。無論出價多高,在該期間每個人支付相同的 Spot 價格。因此,您支付的費用可能會低於您的出價,但永遠不會超過您的出價。

  • 如果您正在執行 Spot 執行個體且您的出價將不符合、也將不超過目前的 Spot 價格,則您的執行個體將被終止。這表示您會想要確保您的工作負載和應用程式具備足夠的彈性,以利用這個機會性容量。

Spot 執行個體的執行動作完全類似於其他 Amazon EC2 執行個體,也像其他 Amazon EC2 執行個體,Spot 執行個體在您不再需要它們時,可被終止。如果您終止了您的執行個體,不足一小時則按一小時支付費用,就像使用 (如您為隨需執行個體或預留執行個體所做的那樣)。然而,如果 Spot 價格上漲超過您的出價,並且您的執行個體由Amazon EC2,您將不會被收取任何部分使用小時的費用。

此教學課程示範如何使用AWS SDK for Java執行下列動作:

  • 提交 Spot 請求

  • 判斷 Spot 請求何時完成

  • 取消 Spot 請求

  • 終止關聯的執行個體

先決條件

若要使用本教學課程,您必須具有AWS SDK for Java,並且已滿足其基本的安裝前提條件。請參閱設定AWS SDK for Java如需詳細資訊,請參

步驟 1:建立您的登入資料

若要開始使用此程式碼範例,您需要設定AWS登入資料。請參閱設定AWS全權證書和區域促進發展如需如何執行此動作的詳細資訊。

注意

我們建議您使用 IAM 用户的證書來提供這些值。如需詳細資訊,請參閱「」註冊AWS並建立 IAM 使用者

現在您已設定您的設定,您可以開始使用示範中的程式碼。

步驟 2:設置安全組

一個安全群組扮演防火牆的角色,可控制允許進出一組執行個體的流量。默認情況下,啟動實例時沒有任何安全組,這意味着任何 TCP 端口上的所有傳入 IP 流量都將被拒絕。因此,在提交競價請求之前,我們將設置一個允許必要的網絡流量的安全組。為了本教程的目的,我們將創建一個名為「GettingStarted Stwork」的新安全組,該安全組允許來自運行應用程序的 IP 地址的安全外殼 (SSH) 流量。要設置新的安全組,您需要包含或運行以下以編程方式設置安全組的代碼示例。

在我們創建一個AmazonEC2客户端對象,我們創建一個CreateSecurityGroupRequest物件的名稱、「GettingStarted」和安全性羣組的描述。然後我們調用ec2.createSecurityGroupAPI 創建組。

要啟用對組的訪問,我們創建一個ipPermission對象的 IP 地址範圍設置為本地計算機子網的 CIDR 表示;IP 地址上的「/10」後綴表示指定 IP 地址的子網。我們還配置ipPermission物件與 TCP 協定和連接口 22 (SSH)。最後步驟是調用ec2.authorizeSecurityGroupIngress與我們的安全組的名稱和ipPermission物件。

// Create the AmazonEC2 client so we can call various APIs. AmazonEC2 ec2 = AmazonEC2ClientBuilder.defaultClient(); // Create a new security group. try { CreateSecurityGroupRequest securityGroupRequest = new CreateSecurityGroupRequest("GettingStartedGroup", "Getting Started Security Group"); ec2.createSecurityGroup(securityGroupRequest); } catch (AmazonServiceException ase) { // Likely this means that the group is already created, so ignore. System.out.println(ase.getMessage()); } String ipAddr = "0.0.0.0/0"; // Get the IP of the current host, so that we can limit the Security // Group by default to the ip range associated with your subnet. try { InetAddress addr = InetAddress.getLocalHost(); // Get IP Address ipAddr = addr.getHostAddress()+"/10"; } catch (UnknownHostException e) { } // Create a range that you would like to populate. ArrayList<String> ipRanges = new ArrayList<String>(); ipRanges.add(ipAddr); // Open up port 22 for TCP traffic to the associated IP // from above (e.g. ssh traffic). ArrayList<IpPermission> ipPermissions = new ArrayList<IpPermission> (); IpPermission ipPermission = new IpPermission(); ipPermission.setIpProtocol("tcp"); ipPermission.setFromPort(new Integer(22)); ipPermission.setToPort(new Integer(22)); ipPermission.setIpRanges(ipRanges); ipPermissions.add(ipPermission); try { // Authorize the ports to the used. AuthorizeSecurityGroupIngressRequest ingressRequest = new AuthorizeSecurityGroupIngressRequest("GettingStartedGroup",ipPermissions); ec2.authorizeSecurityGroupIngress(ingressRequest); } catch (AmazonServiceException ase) { // Ignore because this likely means the zone has // already been authorized. System.out.println(ase.getMessage()); }

請注意,您只需運行此程序一次,即可建立新的安全羣組。

您也可以使用AWS Toolkit for Eclipse。請參閱從管理安全羣組AWS Cost Explorer如需詳細資訊,請參

步驟 3:提交您的 Sport 請求

若要提交 Spot 請求,您首先需要判斷執行個體的類型、Amazon 機器映像 (AMI),以及您想要使用的最高金額。您還必須有我們先前配置的安全羣組,可讓您登入想要的執行個體。

有多種執行個體類型可供選擇;轉到Amazon EC2完整列表的執行個體類型。在本教程中,我們將使用 t1.micro,這是可用的最便宜的實例類型。接下來,我們將確定想要使用的 AMI 類型。我們將使用 AMI-a9d09ed1,這是我們編寫本教程時可用的最新的亞馬遜 Linux AMI。最新的 AMI 可能會隨着時間的推移而改變,但您始終可以通過以下步驟確定最新版本的 AMI:

  1. 開啟 Amazon EC2 主控台

  2. 選擇啟動執行個體按鈕。

  3. 第一個窗口顯示可用的 AMI。AMI ID 列在每個 AMI 標題旁邊。或者,您也可以使用DescribeImagesAPI,但是利用該命令已超出本教學課程的範圍。

Spot 執行個體的出價有多種方式;若要取得各種方法的概觀,您應該查看競價型執行個體的出價影片。不過,在開始前,我們將描述三種常見的策略:出價競標,以確保成本低於隨需價格;根據計算結果的價值而出價競標;儘快出價競標越快取得運算容量。

  • 降低低於隨需執行個體您可以批次處理任務,需要花費數小時或數天的時間完成。然而,您在開始時間和完成時都有彈性。您想要查看是否能以低於按需執行個體的成本完成此操作。您可以使用 AWS Management Console 或 Amazon EC2 API 檢查執行個體類型的 Spot 歷史價格。如需詳細資訊,請至 查閱 Spot 歷史價格。於一個給定的可用區域內分析您所想要的執行個體類型的歷史價格後,您的出價有兩個替代方式:

    • 您可以出價此 Spot 價格範圍的上限 (仍低於隨需執行個體的價格)、期待您的一次性 Spot 要求可以履行並執行,有一段足夠的連續時間完成工作。

    • 或者,您可以指定願意為 Spot 執行個體支付的金額, 以隨需執行個體價格的百分比表示, 並計畫如何透過一個持久性的請求聯合數個已長期啟動的執行個體。如果超過指定的價格 , 則 Spot 執行個體將會終止。(我們將說明如何使這個任務自動進行。)

  • 不為結果支付超過其價值的金額您有一個要執行的資料正在處理任務。您很了解此任務結果的價值,換言之您知道成本為多少。在您分析執行個體類型的 Spot 歷史價格後,您選擇出價的運算時間成本金額不超過此任務結果的價值。您建立可以長久出價的方式,且允許它間歇性地執行,當 Spot 價格出現波動,或 Spot 價格低於您的出價時。

  • 快速獲得運算容量出乎意料,您需要短期的額外容量,無法透過隨需執行個體取得。在您分析執行個體類型的 Spot 歷史價格後,您選擇出價高於歷史價格的金額,期望能快速履行您的出價,並持續運算直到完成為止。

在您選擇您的出價金額後,您可以要求 Spot 執行個體了。在此教學課程中,我們將出價競標隨需價格 (0.03 USD),以最大化履行出價的機率。您可以判斷可用執行個體的類型和執行個體的隨需價格,方式是轉到Amazon EC2定價頁面。在 Spot 執行個體執行的這段時間,您將持續支付生效的 Spot 價格。Spot 執行個體的價格由 Amazon EC2 制定,然後根據 Spot 執行個體容量的長期供需趨勢逐漸調整。您也可以指定願意為 Spot 執行個體支付的金額, 以隨需執行個體價格的百分比表示。若要請求 Spot 執行個體,您只需要使用您之前選擇的參數生成您的出價。我們從建立RequestSpotInstanceRequest物件。請求物件需要您想啟動的執行個體數量和出價。此外,您需要設定LaunchSpecification,其中含有執行個體類型、AMI ID 及想要使用的安全羣組。填充請求後,您可以調用requestSpotInstances方法AmazonEC2Client物件。以下範例示範如何請求 Spot 執行個體。

// Create the AmazonEC2 client so we can call various APIs. AmazonEC2 ec2 = AmazonEC2ClientBuilder.defaultClient(); // Initializes a Spot Instance Request RequestSpotInstancesRequest requestRequest = new RequestSpotInstancesRequest(); // Request 1 x t1.micro instance with a bid price of $0.03. requestRequest.setSpotPrice("0.03"); requestRequest.setInstanceCount(Integer.valueOf(1)); // Setup the specifications of the launch. This includes the // instance type (e.g. t1.micro) and the latest Amazon Linux // AMI id available. Note, you should always use the latest // Amazon Linux AMI id or another of your choosing. LaunchSpecification launchSpecification = new LaunchSpecification(); launchSpecification.setImageId("ami-a9d09ed1"); launchSpecification.setInstanceType(InstanceType.T1Micro); // Add the security group to the request. ArrayList<String> securityGroups = new ArrayList<String>(); securityGroups.add("GettingStartedGroup"); launchSpecification.setSecurityGroups(securityGroups); // Add the launch specifications to the request. requestRequest.setLaunchSpecification(launchSpecification); // Call the RequestSpotInstance API. RequestSpotInstancesResult requestResult = ec2.requestSpotInstances(requestRequest);

運行此程式碼將啟動新的 Spot 執行個體請求。還有其他方式可設定您的 Spot 請求。如需詳細資訊,請訪問教學課程:進階Amazon EC2Spot 請求管理RequestSpotInstances類別中的AWS SDK for JavaAPI 參考。

注意

您將需要為任何執行個體執行的 Spot 執行個體支付費用,因此確定您取消所有請求和終止您啟動的所有執行個體,以降低相關的費用。

步驟 4:判斷您的 Spot 價格之狀態

接著,我們想要創建代碼,等候直到 Spot 請求成為「活動」狀態,再進行最後一個步驟。要確定競價請求的狀態,我們將輪詢describeSpotInstanceRequests方法,以取得想要監控狀態的 Spot 請求 ID。

在步驟 2 中創建的請求 ID 嵌入到我們的requestSpotInstances請求。以下範例程式碼示範如何從requestSpotInstances響應並使用它們填充ArrayList

// Call the RequestSpotInstance API. RequestSpotInstancesResult requestResult = ec2.requestSpotInstances(requestRequest); List<SpotInstanceRequest> requestResponses = requestResult.getSpotInstanceRequests(); // Setup an arraylist to collect all of the request ids we want to // watch hit the running state. ArrayList<String> spotInstanceRequestIds = new ArrayList<String>(); // Add all of the request ids to the hashset, so we can determine when they hit the // active state. for (SpotInstanceRequest requestResponse : requestResponses) { System.out.println("Created Spot Request: "+requestResponse.getSpotInstanceRequestId()); spotInstanceRequestIds.add(requestResponse.getSpotInstanceRequestId()); }

要監控您的請求 ID,請調用describeSpotInstanceRequests方法,以確定請求的狀態。然後循環直到請求不處於「打開」狀態。請注意,我們監視的狀態不是「打開」,而是「活動」狀態,因為如果您的請求參數存在問題,請求可以直接進入「關閉」。下面的代碼示例提供了有關如何完成此任務的詳細信息。

// Create a variable that will track whether there are any // requests still in the open state. boolean anyOpen; do { // Create the describeRequest object with all of the request ids // to monitor (e.g. that we started). DescribeSpotInstanceRequestsRequest describeRequest = new DescribeSpotInstanceRequestsRequest(); describeRequest.setSpotInstanceRequestIds(spotInstanceRequestIds); // Initialize the anyOpen variable to false - which assumes there // are no requests open unless we find one that is still open. anyOpen=false; try { // Retrieve all of the requests we want to monitor. DescribeSpotInstanceRequestsResult describeResult = ec2.describeSpotInstanceRequests(describeRequest); List<SpotInstanceRequest> describeResponses = describeResult.getSpotInstanceRequests(); // Look through each request and determine if they are all in // the active state. for (SpotInstanceRequest describeResponse : describeResponses) { // If the state is open, it hasn't changed since we attempted // to request it. There is the potential for it to transition // almost immediately to closed or cancelled so we compare // against open instead of active. if (describeResponse.getState().equals("open")) { anyOpen = true; break; } } } catch (AmazonServiceException e) { // If we have an exception, ensure we don't break out of // the loop. This prevents the scenario where there was // blip on the wire. anyOpen = true; } try { // Sleep for 60 seconds. Thread.sleep(60*1000); } catch (Exception e) { // Do nothing because it woke up early. } } while (anyOpen);

運行此代碼後,您的競價型實例請求將已完成或失敗,錯誤將輸出到屏幕。在這兩種情況中,我們都可以繼續執行下一個步驟,以清理所有活動中的請求和終止所有執行中的執行個體。

步驟 5:清除您的 Spot 請求和 Spot 執行個體請

最後,我們需要清除我們的請求和執行個體。取消任何未完成請求非常重要終止任何執行個體。只取消請求並不會終止您的執行個體,也就是説您將繼續為它們付費。如果您終止您的執行個體,您的 Spot 請求可能會取消,但在部分情況中,像是如果您是使用長久出價的方式,那麼只終止您的執行個體還不足以停止重新履行您的出價。因此,最好的方式是取消所有作用中的競價和終止所有執行中的執行個體。

以下程式碼示範如何取消您的請求。

try { // Cancel requests. CancelSpotInstanceRequestsRequest cancelRequest = new CancelSpotInstanceRequestsRequest(spotInstanceRequestIds); ec2.cancelSpotInstanceRequests(cancelRequest); } catch (AmazonServiceException e) { // Write out any exceptions that may have occurred. System.out.println("Error cancelling instances"); System.out.println("Caught Exception: " + e.getMessage()); System.out.println("Reponse Status Code: " + e.getStatusCode()); System.out.println("Error Code: " + e.getErrorCode()); System.out.println("Request ID: " + e.getRequestId()); }

要終止任何未完成的實例,您需要與啟動它們的請求相關聯的實例 ID。下面的代碼示例採用我們的原始代碼來監控實例,並添加了一個ArrayList,我們在其中存儲與describeInstance回應。

// Create a variable that will track whether there are any requests // still in the open state. boolean anyOpen; // Initialize variables. ArrayList<String> instanceIds = new ArrayList<String>(); do { // Create the describeRequest with all of the request ids to // monitor (e.g. that we started). DescribeSpotInstanceRequestsRequest describeRequest = new DescribeSpotInstanceRequestsRequest(); describeRequest.setSpotInstanceRequestIds(spotInstanceRequestIds); // Initialize the anyOpen variable to false, which assumes there // are no requests open unless we find one that is still open. anyOpen = false; try { // Retrieve all of the requests we want to monitor. DescribeSpotInstanceRequestsResult describeResult = ec2.describeSpotInstanceRequests(describeRequest); List<SpotInstanceRequest> describeResponses = describeResult.getSpotInstanceRequests(); // Look through each request and determine if they are all // in the active state. for (SpotInstanceRequest describeResponse : describeResponses) { // If the state is open, it hasn't changed since we // attempted to request it. There is the potential for // it to transition almost immediately to closed or // cancelled so we compare against open instead of active. if (describeResponse.getState().equals("open")) { anyOpen = true; break; } // Add the instance id to the list we will // eventually terminate. instanceIds.add(describeResponse.getInstanceId()); } } catch (AmazonServiceException e) { // If we have an exception, ensure we don't break out // of the loop. This prevents the scenario where there // was blip on the wire. anyOpen = true; } try { // Sleep for 60 seconds. Thread.sleep(60*1000); } catch (Exception e) { // Do nothing because it woke up early. } } while (anyOpen);

使用存儲在ArrayList使用下列程式碼片段終止所有正在運行的執行個體。

try { // Terminate instances. TerminateInstancesRequest terminateRequest = new TerminateInstancesRequest(instanceIds); ec2.terminateInstances(terminateRequest); } catch (AmazonServiceException e) { // Write out any exceptions that may have occurred. System.out.println("Error terminating instances"); System.out.println("Caught Exception: " + e.getMessage()); System.out.println("Reponse Status Code: " + e.getStatusCode()); System.out.println("Error Code: " + e.getErrorCode()); System.out.println("Request ID: " + e.getRequestId()); }

整合練習

為了整合這一切,我們提供了一種更面向對象的方法,它結合了我們展示的前面步驟:初始化 EC2 客户端、提交競價請求、確定競價請求何時不再處於打開狀態,以及清理任何延遲的競價請求和相關實例。我們創建了一個名為Requests執行這些動作。

我們還創建了一個GettingStartedApp類,它有一個主方法,我們執行高級函數調用。具體來説,我們初始化Requests物件。我們提交 Spot 執行個體請求。然後,我們等待競價請求達到「活動」狀態。最後,我們清理請求和實例。

此範例的完整源程式碼可在GitHub

恭喜您!您剛剛完成了開發競價型實例軟件的入門教程。AWS SDK for Java。

後續步驟

繼續進行教學課程:進階Amazon EC2Spot 請求管理