자습서: Amazon EC2 스팟 인스턴스 - AWS SDK for Java 1.x

곧 출시될 end-of-support AWS SDK for Java (v1) 버전을 발표했습니다. AWS SDK for Java V2로 마이그레이션하실 것을 권장합니다. 마이그레이션 날짜, 추가 세부 정보 및 방법에 대한 자세한 내용은 링크된 공지 사항을 참조하세요.

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

자습서: Amazon EC2 스팟 인스턴스

개요

스폿 인스턴스를 사용하면 온디맨드 인스턴스 가격 대비 최대 90%까지 미사용 Amazon Elastic Compute Cloud(Amazon EC2) 용량을 입찰할 수 있으며 입찰 가격이 현재 스팟 가격을 초과하는 한 획득된 인스턴스를 실행할 수 있습니다. Amazon EC2는 공급과 수요를 기반으로 스팟 가격을 정기적으로 변경하며 입찰 가격이 스팟 가격과 일치하거나 초과하는 고객은 사용 가능한 스팟 인스턴스에 대한 액세스 권한을 얻습니다. 온디맨드 인스턴스 및 예약 인스턴스와 마찬가지로 스팟 인스턴스는 추가 컴퓨팅 파워를 얻기 위한 또 다른 옵션을 제공합니다.

스팟 인스턴스를 통해 배치 처리, 과학 연구, 이미지 처리, 동영상 인코딩, 데이터 및 웹 크롤링, 재무 분석, 테스트에 대한 Amazon EC2 비용을 크게 절감할 수 있습니다. 뿐만 아니라, 스팟 인스턴스를 사용하면 해당 용량이 긴급하게 필요하지 않은 상황에서 대량의 추가 용량에 액세스할 수 있습니다.

스팟 인스턴스를 사용하려면 인스턴스 시간당 지불하려는 최고 가격을 지정하는 스팟 인스턴스 요청을 배치해야 하는데, 이것이 바로 입찰입니다. 입찰 가격이 현재 스팟 가격을 초과하는 경우 요청이 이행되며 사용자가 인스턴스를 종료하거나 스팟 가격이 입찰 가격보다 높아질 때까지(둘 중에 더 빠른 것에 따라) 인스턴스가 실행됩니다.

다음 사항에 유의해야 합니다.

  • 대개 입찰 가격보다 시간당 더 적은 금액을 지불합니다. Amazon EC2에서는 요청이 들어오고 사용 가능한 공급이 변화함에 따라 정기적으로 스팟 가격을 조정합니다. 입찰 가격이 더 높은지 여부와 상관없이 모든 사람이 해당 기간 동안 동일한 스팟 가격을 지불합니다. 따라서 자신의 입찰 가격보다 더 적게 지불할 수는 있어도 그보다 더 지불하는 경우는 있을 수 없습니다.

  • 스팟 인스턴스를 실행 중이고 입찰 가격이 현재 스팟 가격을 충족 또는 초과하지 않는 경우 인스턴스는 종료됩니다. 이는 사용자가 워크로드와 애플리케이션이 이러한 편의적 용량을 활용할 수 있을 만큼 유연한지 확인하고자 한다는 것을 의미합니다.

스팟 인스턴스는 실행 중에 다른 Amazon EC2 인스턴스와 똑같이 기능을 수행하며, 다른 Amazon EC2 인스턴스와 마찬가지로 더 이상 필요하지 않을 때 종료할 수 있습니다. 인스턴스를 종료할 경우 부분적인 사용 시간에 대해 요금을 지불합니다(온디맨드 또는 예약 인스턴스의 경우와 마찬가지로). 그러나 스팟 가격이 입찰 가격을 초과하여 Amazon EC2에서 인스턴스를 종료하는 경우 부분적인 사용 시간에 대해서는 요금이 청구되지 않습니다.

이 자습서에서는 AWS SDK for Java를 사용하여 다음을 수행하는 방법을 보여줍니다.

  • 스팟 요청 제출

  • 스팟 요청이 이행되는 시점 결정

  • 스팟 요청 취소

  • 연결된 인스턴스 종료

필수 조건

이 자습서를 사용하려면 AWS SDK for Java가 설치되어 있고 기본 설치 사전 요구 사항이 충족되어야 합니다. 자세한 내용은 AWS SDK for Java 설정 단원을 참조하세요.

1단계: 자격 증명 설정

이 코드 샘플 사용을 시작하려면 AWS 자격 증명을 설정해야 합니다. 작업 방법에 대한 지침은 개발을 위한 AWS 자격 증명 및 리전 설정을 참조하세요.

참고

IAM 사용자 자격 증명을 사용하여 이러한 값을 제공하는 것이 좋습니다. 자세한 내용은 AWS에 가입 및 IAM 사용자 생성을 참조하세요.

이제 설정을 구성했으니 예제의 코드를 사용할 수 있습니다.

2단계: 보안 그룹 설정

보안 그룹은 인스턴스 그룹과 송수신이 허용되는 트래픽을 제어하는 방화벽 역할을 합니다. 기본적으로 인스턴스는 보안 그룹 없이 시작되므로 TCP 포트로 들어오는 모든 IP 트래픽이 거부됩니다. 따라서 스팟 요청을 제출하기 전에 필요한 네트워크 트래픽을 허용하는 보안 그룹을 설정하겠습니다. 이 자습서의 목적상, 애플리케이션을 실행 중인 IP 주소에서 Secure Shell(SSH) 트래픽을 허용하는 "GettingStarted"라는 새 보안 그룹을 생성하겠습니다. 새 보안 그룹을 설정하려면 보안 그룹을 프로그래밍 방식으로 설정하는 다음 코드 샘플을 포함하거나 실행해야 합니다.

AmazonEC2 클라이언트 객체를 생성한 후에는 "GettingStarted" 이름과 이 보안 그룹에 대한 설명을 사용하여 CreateSecurityGroupRequest 객체를 생성합니다. 그 다음에는 ec2.createSecurityGroup API를 호출하여 그룹을 생성합니다.

그룹에 대한 액세스를 활성화하기 위해 IP 주소 범위가 로컬 컴퓨터의 CIDR 표현으로 설정된 ipPermission 객체를 생성합니다. IP 주소의 "/10" 접미사는 지정된 IP 주소용 서브넷을 나타냅니다. 또한 TCP 프로토콜과 포트 22(SSH)를 사용하여 ipPermission 객체를 구성합니다. 마지막 단계는 보안 그룹의 이름과 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단계: 스팟 요청 제출

스팟 요청을 제출하려면 먼저 사용하고자 하는 인스턴스 유형, Amazon 머신 이미지(AMI) 및 최대 입찰 가격을 결정해야 합니다. 또한 원하는 경우 인스턴스에 로그인할 수 있도록 이전에 구성했던 보안 그룹도 포함해야 합니다.

여러 가지 인스턴스 유형 중에 선택할 수 있습니다. 전체 목록은 Amazon EC2 인스턴스 유형을 참조하세요. 이 자습서에서는 사용 가능한 가장 저렴한 인스턴스 유형인 t1.micro를 사용하겠습니다. 다음에는 사용할 AMI 유형을 결정하겠습니다. ami-a9d09ed1을 사용하겠습니다. 이 유형은 이 자습서 작성 당시 사용 가능한 최신 Amazon Linux AMI입니다. 최신 AMI는 시간이 경과됨에 따라 변경될 수도 있지만, 언제든지 다음 단계를 수행하여 최신 버전의 AMI를 확인할 수 있습니다.

  1. Amazon EC2 콘솔을 엽니다.

  2. 인스턴스 실행 버튼을 선택합니다.

  3. 첫 번째 Window는 사용 가능한 AMI를 표시합니다. AMI ID는 각 AMI 제목 옆에 표시됩니다. 또는 DescribeImages API를 사용할 수도 있지만, 이 명령의 활용은 이 자습서의 범위를 벗어납니다.

스팟 인스턴스에 대한 입찰 방법에는 여러 가지가 있습니다. 다양한 방법에 대한 간략한 설명을 보려면 Bidding for Spot Instances 비디오를 시청하세요. 그러나 시작하기 위해서는 세 가지 일반적인 전략, 즉 비용이 온디맨드 요금보다 적은 입찰, 그 결과 얻는 계산 값에 따른 입찰, 가능한 한 빨리 컴퓨팅 용량을 얻기 위한 입찰에 대해 설명이 필요합니다.

  • 비용을 온디맨드 이하로 낮추기 실행하는 데 몇 시간 또는 몇 일이 걸릴 배치 처리 작업이 있습니다. 그러나 시작 및 완료 시점은 유연하게 선택할 수 있습니다. 그 작업을 온디맨드 인스턴스보다 적은 비용으로 완료할 수 있는지 확인할 수 있습니다. AWS Management Console 또는 Amazon EC2 API를 사용하여 인스턴스 유형에 대한 스팟 가격 기록을 살펴보세요. 자세한 내용은 스팟 가격 내역 보기를 참조하세요. 특정 가용 영역에서 원하는 인스턴스 유형의 가격 내역을 분석하였다면 입찰에 사용할 수 있는 다른 방법이 두 가지 있습니다.

    • 일회적인 스팟 요청이 이행되고 해당 작업을 완료하기에 충분한 연속적 컴퓨팅 시간 동안 실행될 가능성이 높을 것이라는 기대로 스팟 가격 범위의 상단(여전히 온디맨드 가격보다는 낮음)에서 입찰할 수 있습니다.

    • 또는 온디맨드 인스턴스 가격의 %로 지정하여 스팟 인스턴스에 지불할 의향이 있는 금액을 지정할 수 있습니다. 그리고 지속적인 요청을 통해 시간이 지남에 따라 시작된 많은 인스턴스를 결합할 계획입니다. 지정된 가격이 초과하면 스팟 인스턴스가 종료됩니다. (이 자습서 후반부에서 이 작업을 자동화하는 방법에 대해 설명할 것입니다.)

  • 결과로 얻은 가치보다 더 많은 요금 지불 금지 실행할 데이터 처리 작업이 있습니다. 작업 당사자는 그 작업의 결과가 지니는 가치를 충분히 이해하여 컴퓨팅 비용 측면에서 그 결과의 가치가 얼마나 되는지 알 수 있습니다. 해당 인스턴스 유형의 스팟 가격 내역을 분석한 후 컴퓨팅 시간의 비용이 해당 작업의 결과가 지니는 가치보다 더 많지 않은 입찰 가격을 선택합니다. 영구 입찰을 생성하여 스팟 가격이 입찰 가격까지 또는 그 이하로 변동하는 과정에서 간헐적으로 실행되도록 합니다.

  • 컴퓨팅 용량을 신속하게 획득 온디맨드 인스턴스를 통해서는 사용할 수 없는 추가 용량이 예기치 않게 단기간 동안 필요한 경우가 있습니다. 이때는 해당 인스턴스 유형의 스팟 가격 내역을 분석한 후 과거의 최고 입찰 가격보다 높게 입찰함으로써 요청이 신속하게 이행되고 완료될 때까지 컴퓨팅이 지속될 수 있는 가능성을 높입니다.

입찰 가격을 선택하고 나면 스팟 인스턴스를 요청할 준비가 된 것입니다. 본 자습서의 목적상 온디맨드 가격(0.03 USD)으로 입찰하여 입찰이 이행될 가능성을 최대화할 것입니다. Amazon EC2 요금 페이지를 참조하여 사용 가능한 인스턴스의 유형과 인스턴스의 온디맨드 요금을 결정할 수 있습니다. 스팟 인스턴스가 실행되는 동안 인스턴스가 실행되는 기간에 대한 스팟 가격만 지불합니다. 스팟 인스턴스 가격은 Amazon EC2에서 정하고, 스팟 인스턴스 용량의 장기적인 수요 공급 추세에 따라 점진적으로 조정됩니다. 또한 스팟 인스턴스에 대해 지불하려는 금액을 온디맨드 인스턴스 가격의 %로 지정할 수 있습니다. 스팟 인스턴스를 요청하려면 이전에 선택한 매개 변수를 사용하여 요청을 작성하면 됩니다. RequestSpotInstanceRequest 객체 생성부터 시작하겠습니다. 요청 객체에는 시작하고자 하는 인스턴스 수와 입찰 가격이 필요합니다. 뿐만 아니라, 요청에 대한 LaunchSpecification을 설정해야 하며, 여기에는 사용할 인스턴스 유형, AMI ID 및 보안 그룹이 포함됩니다. 요청을 작성했으면 requestSpotInstances 객체에 대해 AmazonEC2Client 메서드를 호출합니다. 다음 예제에서는 스팟 인스턴스를 요청하는 방법을 보여줍니다.

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

이 코드를 실행하면 새로운 스팟 인스턴스 요청이 시작됩니다. 스팟 요청을 구성할 때 사용할 수 있는 다른 옵션도 있습니다. 자세한 내용은 자습서: 고급 Amazon EC2 스팟 요청 관리 또는 AWS SDK for Java API 참조의 RequestSpotInstances 클래스를 참조하세요.

참고

실제로 시작되는 모든 스팟 인스턴스에 대해서는 요금이 부과되므로 모든 요청을 취소하고 시작하는 모든 인스턴스를 종료하여 관련된 모든 요금을 줄여야 합니다.

4단계: 스팟 요청 상태 확인

다음에는 마지막 단계로 이동하기 전에 스팟 요청이 "활성" 상태가 될 때까지 기다렸다가 코드를 생성하려고 합니다. 스팟 요청의 상태를 확인하기 위해 describeSpotInstanceRequests 메서드를 통해 모니터링할 스팟 요청 ID의 상태를 폴링합니다.

2단계에서 생성한 요청 ID가 requestSpotInstances 요청에 대한 응답에 포함됩니다. 다음 예제 코드에서는 requestSpotInstances 응답에서 요청 ID를 수집하고 이러한 요청 ID를 사용하여 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단계:: 스팟 요청과 인스턴스 정리

마지막으로 스팟 요청과 인스턴스를 정리해야 합니다. 대기 중인 요청을 모두 취소하고 인스턴스를 모두 종료하는 것이 중요합니다. 요청을 취소하는 것만으로는 인스턴스가 중지되지 않는데, 이는 인스턴스에 대해 계속해서 비용이 부과됨을 뜻합니다. 인스턴스를 종료하면 스팟 요청이 취소될 수 있지만, 인스턴스 종료만으로는 재이행되고 있는 요청을 중지할 수 없는 경우 영구에는 입찰을 사용하는 것과 같은 시나리오도 가능합니다. 따라서 활성화 상태의 입찰을 모두 취소하고 실행 중인 인스턴스를 모두 종료하는 것이 가장 좋습니다.

다음 코드는 요청을 취소하는 방법을 보여줍니다.

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 응답과 연결된 인스턴스 ID를 저장했던 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에 저장된 인스턴스 ID와 다음 코드 조각을 사용하여 실행 중인 인스턴스를 종료합니다.

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 객체를 초기화하겠습니다. 스팟 인스턴스 요청을 제출합니다. 그런 다음 스팟 요청이 "활성" 상태가 될 때까지 기다립니다. 마지막으로 요청과 인스턴스를 정리합니다.

이 예제의 전체 소스 코드는 GitHub에서 확인하거나 다운로드할 수 있습니다.

축하합니다! AWS SDK for Java를 사용한 스팟 인스턴스 소프트웨어 개발 관련 시작하기 자습서를 완료했습니다.

다음 단계

자습서: 고급 Amazon EC2 스팟 요청 관리로 이동하세요.