Category: 한국 기술 문서


Amazon DynamoDB 데이터 백업 및 복원 방법 – (2) 리전간 복제 기능을 활용한 데이터 이동

지난 번에 Amazon DynamoDB를 Export/Import 하는 두 가지 방법을 설명 드렸습니다. 즉,  AWS 관리 콘솔을 통해 DataPipeine과 EMR을 활용하는 방법과 github에 공개되어 있는 Import/Export Java Library를 이용하는 방법입니다.

이전 글에서는 Amazon DynamoDB 현재 저장 내용을 백업하는 용도라면, 이번 글은 원본 DynamoDB 테이블에 변경 내용만 다른 테이블에 지속적으로 복제(Replication)하는 방법을 안내해  드립니다.  이 방법은 공식 기술 문서에 따라 Github에 있는 소스 코드를 활용하는 것으로, 여기서 활용되는 리전간 복제 기능(Cross-region replication)은 기존 저장된 데이터를 읽어 옮기지는 않는다는 점에 유의하시기 바랍니다.

Amazon DynamoDB 리전간 복제 기능
Amazon DynamoDB 리전간 복제 기능은 자바(Java)로 개발되어 있습니다. 해당 라이브러리는 원본 DynamoDB ㅌ테이블에서 데이터 변경이나 새로운 데이터가 입력되면,  이동 대상 DynamoDB 테이블에 복제하는 기능을 합니다.  본  기능을 수행하는 라이브러리는 DynamoDB Stream을 통해 만들어졌으며,  이는 Amazon Kinesis를 활용하여  원본 테이블에서 변경된 정보를 Kinesis 스트림에 넣고 이를 가져와서 처리하거나 저장하게 됩니다.

본 기능을 잘 활용하면, 글로벌 멀티 플레이어 게임 서비스에서 각 리전(Region)에서 DynamoDB를  게임 마스터 DB로 사용할 경우,  다른 리전에서 업데이트 되거나 새로 입력된 데이터를 DynamoDB Stream을 통해 가져와  동기화할 수 있습니다. 또한, 변경되거나 새로 생성된 데이터를 DynamoDB Stream으로 받아 분석하거나 모니터링하는 용도로 활용 가능합니다.

또한, 이 도구는 병렬 처리를 지원합니다. 예를 들어, 매우 큰 사이즈의 DynamoDB 테이블의 경우 빠르게 옮기기 위해 이 도구를 여러 EC2 인스턴스에서 실핼 할 수도 있습니다. 이는 내부적으로 체크포인트(Checkpoint)를 사용하여 겹치지 않도록 병렬로 데이터를 옮길 수 있습니다. 병렬 처리 외에 복수 개의 DynamoDB 테이블도 지원합니다. 즉, 원본 테이블이 버지니아 리전에 있고, 서울 리전과 더블린 리전으로 복사하고 싶다면, 별도로 두 개의 프로세스를 실행 시켜서 복제가 가능합니다.

리전간 복제 라이브러리 설치
Amazon DynamoDB 리전간 복제 기능을 실행하기 위해서는 몇 가지 준비가 필요합니다. 우선 설치 후 실행할 리눅스 인스턴스가 필요합니다. 이 블로그에서는 Amazon Linux AMI를 이용하여 서울 리전에 인스턴스를 실행하여 설치하였습니다.

1. 빌드를 위해서는 메이븐(Maven)이 필요합니다. 메이븐을 받아 설치를 합니다. 다운로드 받은 후 원하는 위치에서 압축을 해제합니다. 보통 /usr/local 에 설치합니다. 메이븐 실행 파일인 mvn 을 실행하기 위해서 설치된 폴더 위치를 PATH환경 변수에 등록하여 줍니다. mvn 설치를 확인합니다.

$ wget http://ftp.kddilabs.jp/infosystems/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
$ tar -zxvf ./apache-maven-3.3.9-bin.tar.gz
$ export PATH=/usr/local/apache-maven-3.3.9/bin:$PATH
$ mvn --version
[ec2-user@ip-172-31-13-42 apache-maven-3.3.9]$ mvn --version
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00)
Maven home: /usr/local/apache-maven-3.3.9
Java version: 1.7.0_111, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.111.x86_64/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.4.23-31.54.amzn1.x86_64", arch: "amd64", family: "unix"

2. 미리 Java JDK devel를 설치하여 mvn install 시에 중간에 에러가 없도록 준비합니다.

$ sudo yum install -y java-1.7.0-openjdk-devel

3. Github 의 소스를 Local로 가져옵니다. (만약 git이 설치되어 있지 않다면, sudo yum install git명령으로 먼저 설치합니다.)

$ git clone https://github.com/awslabs/dynamodb-cross-region-library.git

4. dynamodb-cross-region-libray가 복사된 폴더로 이동하여 아래 명령을 통해 빌드와 설치를 진행합니다.

$ sudo env "PATH=$PATH" mvn install
[ec2-user@ip-172-31-13-42 dynamodb-cross-region-library]$ mvn install
[INFO] Scanning for projects...
Downloading: https://repo.maven.apache.org/maven2/com/amazonaws/aws-java-sdk-bom/1.10.77/aws-java-sdk-bom-1.10.77.pom
Downloaded: https://repo.maven.apache.org/maven2/com/amazonaws/aws-java-sdk-bom/1.10.77/aws-java-sdk-bom-1.10.77.pom (14 KB at 9.7 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/com/amazonaws/aws-java-sdk-pom/1.10.77/aws-java-sdk-pom-1.10.77.pom
Downloaded: https://repo.maven.apache.org/maven2/com/amazonaws/aws-java-sdk-pom/1.10.77/aws-java-sdk-pom-1.10.77.pom (9 KB at 27.0 KB/sec)
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building DynamoDB Cross-region Replication 1.1.0
[INFO] ------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.7/maven-resources-plugin-2.7.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/2.7/maven-resources-plugin-2.7.pom (8 KB at 25.3 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-plugins/25/maven-plugins-25.pom
……………………………………
Downloaded: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-utils/3.0.5/plexus-utils-3.0.5.jar (226 KB at 719.0 KB/sec)
[INFO] Installing /usr/local/dynamodb-cross-region-library/target/dynamodb-cross-region-replication-1.1.0.jar to /root/.m2/repository/com/amazonaws/dynamodb-cross-region-replication/1.1.0/dynamodb-cross-region-replication-1.1.0.jar
[INFO] Installing /usr/local/dynamodb-cross-region-library/pom.xml to /root/.m2/repository/com/amazonaws/dynamodb-cross-region-replication/1.1.0/dynamodb-cross-region-replication-1.1.0.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 05:07 min
[INFO] Finished at: 2016-10-24T08:57:10+00:00
[INFO] Final Memory: 34M/128M
[INFO] ------------------------------------------------------------------------

데이터 복제 진행 여부 확인하기
원본 테이블에서 DynamoDB Stream을 활성합니다. 테이블은 이전 블로그 테이블을 활용하였습니다.

새로 저장될 테이블은 DynamoDB-replica1으로 원본 테이블과 동일한 Partition key(resource), Sort key를 설정하여 테이블을 생성합니다.

최종 빌드 된 jar파일이 있는 폴더로 이동하여 Github에 나온 안내를 참고하여 필요한 인자(Argument)를 주어 실행합니다. 위에서 설명드린 것처럼 버지니아 리전에 있는 Kinesis-Data-Visualization-CountsDynamoDBTable-49AHMYW180WC 테이블을 원본 테이블로 지정하고, 버지니아 리전에 DynamoDB-replica1이라는 테이블로 복제하는 명령의 예제입니다. 아래 명령은 실행된 후에 스탠드얼론(Standalone)으로 계속 실행 중이 됩니다.

$ sudo java -jar dynamodb-cross-region-replication-1.1.0.jar --sourceEndpoint dynamodb.us-east-1.amazonaws.com --sourceTable Kinesis-Data-Visualization-CountsDynamoDBTable-49AHMYW180WC --destinationEndpoint dynamodb.us-east-1.amazonaws.com --destinationTable DynamoDB-replica1

원본 테이블에 데이터를 하나 생성하여 입력합니다.

DynamoDB-replica1에 해당 아이템이 생성되었는지 확인합니다.

2회에 걸쳐 Amazon DynamoDB의 내용을 백업 및 복원, 그리고 변경이나 새로운 데이터를 바로 복제하는 방법을 알아 보았습니다. 글로벌 게임 개발 시 다양한 시나리오 따라 활용 사례가 많으므로 도움이 되시길 바랍니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김일호 솔루션즈 아키텍트께서 작성해주셨습니다.

Amazon Cognito로 10분만에 인증 서비스 만들기

Amazon Cognito를 사용하면 모바일과 웹 앱에서 인증 및 보안 관리, 별도 사용자 계정 서비스를 활용하여 다양한 AWS 서비스를 손쉽게 연동하고 권한 및 접근 관리를 할 수 있습니다. 지난 9월 30일 부터 서울 리전-Asia Pacific (Seoul)에서 사용할 수 있게 되었습니다.

또한,  신규 기능으로서 완전 관리형 사용자 디렉토리인 사용자 풀(User Pools)은 몇 분 안에 모바일 앱 혹은 웹앱에 회원 가입 데이터베이스를 만들 수 있습니다. 게임 서비스를 만들 때 항상 필요한 기능인 로그인/로그아웃을 위한  인증 서버를 손쉽게 구성 관리할 수 있는 관리형 서비스입니다.  가입시 필요한 속성(주소, 이메일, 성별, 전화 번호 및 사용자 지정 특성 등)의 입력 방식과 회원 인증 방식도 지정가능합니다.

이번 글에서는 Amazon Cognito UserPools에서 실제 코드에서 어떻게 사용 하는지를 자세히 살펴보고자합니다.

1.  신규 사용자 풀 구성하기
AWS 관리 콘솔에서 Cognito 사용자 풀 생성 화면에서 오른쪽 상단의 Create a User Pool을 누른 후 아래 그림과 같이 이름을 지정하고 아래 Step through settings를 눌러 하나씩 세팅을 하면 쉽게 유저풀을 생성할 수 있습니다.

Attributes에서 회원 가입 시 필요한 속성을 정의하고,  원하는 추가 가입 속성을  설정할수도 있습니다. 사용자 풀을 설정하는 단계적인 방법은 이전의 블로그 글을 참고하시기 바랍니다.

2.  클라이언트 앱 구성하기
회원 가입 및 로그인/로그아웃 같은 기능을 외부 프로그램을  통해 제공하려면,  이에 대한 앱 정보(Apps)를 추가해야 합니다.  아래와 같이 구성 단계에서 Add an app을 선택하고 앱 이름을 선택하면 간단히 앱을 추가할 수 있습니다. 아래에서 AWS Javascript SDK 기반의 샘플 코드를 실행하기 때문에, 반드시 앱 생성시 client secret 체크 박스를 해제해야 합니다.

사용자 풀을 생성하고  나면,  앞에서 만든 앱을 위한 App client id를  조회 할 수 있습니다.  또한,  사용자 풀의 Pool ID  역시 아래 애플리케이션 설정을 할 때, 사용하는 값입니다.

위의 화면에서  Apps 항목의 연필 아이콘을 클릭하면, 아래 화면과 같이 App client id를 확인할 수 있습니다.

3. 회원 가입 기능 테스트하기
이제 우리가 만든 사용자 풀에 접근하는 기능을 만들어보겠습니다.  Amazon Cognito 사용자 풀 기능을 수행하는 예제는 많이 존재하지만,  그 중에서 https://github.com/aws/amazon-cognito-identity-js/에서 제공하는 nodejs를 이용한 예제를 실행해 봅니다.

위의 샘플 코드에서는 회원 가입,  로그인 관련 처리 및 다양한 시나리오에 대한 예제 코드를 제공합니다. 본 샘플 코드 중에서  examples/babel-webpack/src/config.js 파일을 편집해서 사용자 풀 접속 정보를 입력합니다. 이때 필요한 것이 앞에서 언급한 Pool ID와 App client ID입니다. 두 가지 값을 아래와 같이 설정합니다.

export default {
   region: 'ap-northeast-2',
   IdentityPoolId: '',
   UserPoolId: 'ap-northeast-1_d8o3MPLxxx',
   ClientId: '6svcu5kpgu7oklm5rdqufqxxxxx',
}

이렇게 설정을 하고 나면 예제를 실행할 수 있습니다. 예를 들어,  회원 가입의 경우, HTML에서 입력된 가입 속성과 관련된 정보를 저장하거나 가져오기 위해서는 각 속성을 JSON형식으로 지정한 후에 이를 signUP 함수 등에 전달합니다.  아래 Javascript 샘플 코드를 참고하세요.

var attributeList = [];
    var dataEmail = {
        Name : 'email',
        Value : '...' // your email here
    };
    var dataPhoneNumber = {
        Name : 'phone_number',
        Value : '...' // your phone number here with +country code and no delimiters in front
    };
    var attributeEmail = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataEmail);
    var attributePhoneNumber = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataPhoneNumber);

    attributeList.push(attributeEmail);
    attributeList.push(attributePhoneNumber);

    var cognitoUser;
    userPool.signUp('username', 'password', attributeList, null, function(err, result){
        if (err) {
            alert(err);
            return;
        }
        cognitoUser = result.user;
        console.log('user name is ' + cognitoUser.getUsername());
    });

위의 샘플 코드에서는 회원 가입 및 정보 수정 뿐만 아니라 Federated Identities 콘솔에서 Congnito 사용자 풀을 인증 수단으로 추가하면  로그인,  로그아웃 그리고 SMS 및 멀티 팩터 추가 인증  수단 활용 등의 다양한 시나리오도 확인해 볼 수 있습니다.

모바일/웹앱 형 게임을 만들 때 사용자 풀을 사용하면,  Amazon Cognito가 제공하는 페이스북 및 트위터 소셜 로그인 기능과 함께 자체 사용자 데이터베이스를 손쉽게 만들고 기능 확장이 가능하므로 꼭 사용해 보시길 권장합니다. (참고:  Amazon Cognito 사용자 풀 및 아이덴티티 자세한 설정 방법은 Zombie Microservices Workshop 한국어 가이드를 직접 실행해 보셔도 좋습니다.)

또한,  안드로이드나 iOS 애플리케이션에서 사용자 풀을 이용하시려면, AWS 모바일 블로그의  Using Android SDK with Amazon Cognito Your User Pools Android용 사용자 풀 활용 기술 문서iOS용 사용자 풀 활용 기술 문서를 참고하시기 바랍니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 박선용 솔루션즈 아키텍트께서 작성해주셨습니다.

AWS 기반 웹 및 애플리케이션 서버 부하 테스트: A to Z

사용자가 이용하고 있는 온라인 서비스로부터 예상치 못한 느려짐을 겪거나, 접속 불가로 인해 서비스 이용 조차 할 수 없다면, 서비스에 중요한 재방문율(Retention Rate)과 유료 전환율(Conversation Rate)을 하락 시켜 비지니스에 큰 손실을 야기 할 수 있습니다. 이런 성능 관련 문제를 예방하기 위해 성능 테스트, 특히 부하 테스트에 대한 중요성이 크게 증가하고 있으며, 더불어 관련  도구 및 서비스의 다양성 또한 증가하고 있습니다.

이번 글은 AWS 클라우드에서 어떻게 웹/애플리케이션 서버의 부하 테스트를 하는지 모범 사례를 알려드리기 위한 것으로, 부하테스트 목적과 고려 사항 그리고 단계 및 도구 등을 대해 다루고자 합니다.

부하 테스트의 목적
일반적으로 부하 테스트는 서비스 개발 이후, 운영을 하기 직전 수행하는 테스트 중 하나로서, 실제 요구되는 부하를 서비스가 수용할 수 있는지를 확인하기 위한 작업 입니다. 사용자 활동을 시뮬레이션 하고 인프라 및 서버의 동작을 모니터링 함으로써, 전부는 아닐지라도, 많은 부분의 병목 현상(Bottleneck)을 제거할 수 있습니다.

부하 테스트의 목적으로는 보통 다음 세가지가 있습니다.

  • 현재 서비스 구성의 제한(limit)을 찾기 위함
  • 원하는 부하를 수용할 수 있게끔 구성되었는지 확인하기 위함
  • 병목 지점을 찾고 병목 현상을 제거하기 위함

부하 테스트 전 고려되어야 할 점
실제 성능 테스트 시, 부하 테스트를 비중 있게 여기지 않는 경우가 많으며, ApacheBenchJMeter 와 같은 도구를 다운로드 한 뒤, 특정 시나리오를 기반으로 하여 도구를 실행하는 것만으로 충분하다고 생각하는 경우가 있습니다. 하지만, 실제 워크로드는 다양한 변수와 시나리오를 가지고 있기 때문에, 부하 테스트를 진행할 때 충분히 이점을 반영 하여 야지만 실제 서비스에서 예상 가능한 결과를 가져올 수 있다는 것은 분명한 사실 입니다.

그럼 실용적인 부하 테스트를 위해 어떤 부분들을 고려해야 할까요?

  • 충분한 테스트용 서버 자원 확보: 최대의 트래픽을 생성하여 테스트 하기 위해서는 충분한 서버 자원이 요구되며, 부족할 경우 적절한 테스트가 이루어지기 힘듭니다.
  • 테스트 시, 블랙박스 혹은 격리된 환경 제어: 부하 테스트는 많은 요청과 패킷을 생성하기 때문에 사내 인프라의 많은 부분을 포화 상태로 만들기 쉽습니다. 이를 방지하기 위해 제한된 자원만 할당된 블랙 박스 혹은 격리된 환경에서 테스트를 수행하는 경우가 많습니다.
  • 글로벌 기반의 부하 생성: 글로벌 서비스의 경우, 전 세계 각 지역에서 부하를 생성하여 테스트를 진행하여야만 실제 사용 패턴에 가까운 시나리오가 될 수 있습니다. 이를 통해 글로벌 커버리지를 확인할 수 있으며, 실제 워크로드에서 얻을 수 있는 유사한 각종 성능 관련 지표도 얻을 수 있습니다.
  • 높은 비용과 불규칙적인 사용성에 대한 주의: 격리된 환경과 더불어 여러 리전을 커버하기 위한 테스트 환경은 때로 높은 비용을 요구합니다. 구성된 환경은 짧은 기간 동안만 집중적으로 사용되기도 하며, 안정적인 서비스가 유지될 땐 드물게 사용되기도 하기 때문에, 사용하지 않을 때도 유지하여야 하는 등의 자원을 효율적으로 사용하지 못해 불필요한 비용이 발생 하기도 합니다.
  • 높은 아키텍처 복잡성에 대한 주의: 상기 언급된 부분들을 고려하여 부하 테스트 환경을 구성한다면 꽤나 높은 복잡성이 요구 됩니다. 나아가 반복적인 테스트를 위해, 가상의 가짜(Dummy) 데이터 등에 의해 지저분해진 환경을 초기화 시켜줄 수 있는 방안도 필요 합니다.

AWS 클라우드 기반 부하 테스트의 장점
AWS 클라우드는 부하 테스트를 진행하기 위해 고려할 사항에 대한 적절한 해답을 가지고 있습니다.  AWS 클라우드에서 부하 테스트 하게 된다면 어떤 특징을 가지고 있을까요?

  • 사용한 만큼 지불하는 비용 효율성: Amazon EC2 인스턴스는 사용한 만큼만 비용이 발생하기 때문에, 테스트 조건에 맞는 인스턴스 타입을 선택하여 비용 효율성을 높일 수 있습니다. 예를 들어, 정식 부하 테스트를 진행하기 전에 저렴한 마이크로 인스턴스 수십~수백대를 활용하여, 서버 워밍업을 저렴한 비용으로 할 수 있습니다. 또한, 인스턴스 사용이 끝나면 즉시 종료할 수 있어 불필요한 비용 발생을 줄일 수 있습니다.
  • 충분하고 유연한 컴퓨팅 자원 제공: AWS가 제공하는 컴퓨팅 자원을 활용한다면, 필요한 규모의 부하에 대해 자유롭게 테스트를 수행할 수 있습니다. 또한, 오토스케일링(Auto Scaling)을 통해 부하 테스트 시 자원을 자동으로 증설 혹은 감소 시킬 수 있습니다.
  • 글로벌 리전(Region)으로부터의 부하 생성: AWS가 제공하는 글로벌 인프라를 통해 전 세계 각 리전으로부터의 부하 생성을 손쉽게 할 수 있습니다.
  • 쉽고 단순한 아키텍처 구성 및 관리: AWS CloudFormation 을 비롯한 다양한 배포 자동화 서비스 및 기능을 통해 프로덕션과 테스트 용 환경을 동일한 방법으로 손 쉽게 구성할 수 있습니다. 뿐만 아니라 관리형 서비스를 활용하여 운영 측면에서의 부담도 줄일 수 있어, 테스트 환경 구축에 관한 복잡성을 줄일 수 있습니다.

부하 테스트의 단계
부하 테스트는 서비스 전체 스택을 대상으로 진행할 수도 있지만, 최근에는 마이크로서비스(Microservices) 나 서비스 지향 구조(Service-Oriented Architecture) 등의 형태로 서비스를 디자인하는 경우가 많아, 전체 스택을 구성하고 있는 작은 컴포넌트들부터 진행되는 경우가 많아지고 있습니다.

또, 빠른 병목 현상 발견과 수정을 위해, 애플리케이션 로직이 적용되기 전 순수한 인프라 수준에서 시작하여 작은 컴포넌트들, 솔루션, 그리고 애플리케이션 순으로 부하 테스트를 확대해 나갈 수도 있습니다.

아래는 일반적으로 수행할 수 있는 부하 테스트 단계 입니다.

  • 비결합(Loosely Coupled)된 개별 컴포넌트에 대한 부하 테스트: 이를 통해 각 컴포넌트 별 병목 현상을 보다 빠르게 발견하고 수정할 수 있습니다.
  • 내부 서비스에 대한 부하 테스트: 로그 기록 서비스와 같이 높은 처리량이 요구되는 서비스나, 결제 서비스와 같이 전체 서비스 품질에 있어 중요한 내부 서비스를 대상으로 테스트를 진행 합니다.
  • 외부 서비스에 대한 부하 테스트: Facebook, Twitter 등의 소셜 서비스나 Google 등의 플랫폼에 대한 서비스들, 또는 푸시 알림 서비스 등의 외부 서비스를 대상으로 테스트를 진행 합니다.
  • 전체 스택에 대해 부하 테스트: 개별 컴포넌트들에 대한 테스트를 완료한 뒤, 컴포넌트간의 상호작용을 알기 위해 처음부터 끝까지 전체 스택에 대해서 테스트 진행 합니다.

단계별 부하 테스트 수행 방법
앞서 언급한 것처럼, 부하 테스트는 전체 스택에 대해서도 수행 가능하지만, 작은 비결합된 컴포넌트나 기능 단위로도 수행 가능합니다. 그리고 작은 단위로 수행될 수록 더 분명하게 병목 지점을 파악하기에 수월 합니다. 그렇기에, 가능한 작은 단위부터 단계별로 진행하는게 원하는 결과를 얻어내는데 효율적입니다.

다음은 전형적인 3단계(3-tier) 웹 서비스에 대해 단계별로 부하 테스트를 진행하는 것을 설명하고 있습니다.

load-test-image001

  1. 최초 ‘WEB’ 을 출력하는 웹 페이지를 대상으로 동시 연결성에 대한 테스트 수행 → 결과 평가→최적화 진행 (Client →Web Server)
  2. 웹 서버를 통해 애플리케이션 서버에서 넘겨 받은 ‘APPLICATION’ 을 출력하는 웹 페이지를 대상으로 동시 연결성에 대한 테스트 수행→ 결과 평가 → 최적화 진행 (Client →Web Server → App Server – w/o Logic)
  3. 데이터베이스에서 최소한의 쿼리 결과를 전달 받아 출력하는 웹 페이지를 대상으로 동시 연결성에 대한 테스트 수행 →결과 평가 → 최적화 진행
    (Client →Web Server → App Server – w/o Logic → Database)
  4. 3-tier 스택 전체를 대상으로 애플리케이션 로직이 적용된 페이지에 동시 연결성에 대한 테스트 수행 → 결과 평가 → 최적화 진행
    (Client → Web Server → App Server – with Logic →Database)
  5. 4번을 기반으로 다양한 시나리오를 지정하여 테스트 수행 → (얻고자 하는 지표 기준에 대해서) 결과 평과 → 최적화 진행

부하 테스트 시, 각 레이어별 고려하여야 할 사항
부하 테스트를 수행한 뒤 각 레이어별로 발생할 수 있는 상황과 고려하여야 할 부분에 대해서 알아보도록 하겠습니다.

  1. 네트워크 용량 확인: 테스트 환경이 구성된 인프라와 관련하여 여러가지 지표를 확인할 수 있지만, 대표적으로 아웃바운드 연결이 예상되는 최대의 부하를 처리할 수 있는지 확인할 필요가 있습니다. Amazon EC2 의 경우 인스턴스 타입 마다 서로 다른 네트워킹 성능을 제공하고 있기에, 부하에 따른 적절한 인스턴스 타입 선택이 중요 합니다. 또 사내 인프라와 프라이핏하게 연결되어 있다면, VPN 관련 네트워킹 성능에 대해서도 확인을 하여야 합니다.
  2. 부하 생성 클라이언트: 앞서 설명하였듯이 부하 테스트의 요구사항 중 하나로, 필요한 만큼의 부하를 생성할 수 있는 충분한 인스턴스의 확보가 요구됩니다. 하지만, 설정 및 구현 방식에 의해 하나의 부하 생성 클라이언트가 처리할 수 있는 동시성에 큰 제한이 있다면, 필요 이상으로 복수개의 인스턴스가 요구되어 비용이 증가할 수 있습니다. 이럴 땐 Thread 기반의 툴 보다는 높은 동시성을 제공하는 Async IO 기반의 툴을 사용하여 테스트를 진행하는게 좋습니다.
  3. 로드 밸런싱: ELB(Elastic Load Balancing)는 서비스 전체 부하를 백엔드에 등록된 복수개의 인스턴스로 분산하는 기능을 제공합니다. 부하 테스트를 수행할 때 다양한 이유로 기대치 보다 낮은 결과나 5xx 에러가 발생하는 것을 볼 수 있습니다. 이 때, ELB 가 제공하는 다양한 모니터링 지표들을 확인하면, 어느 곳에서 병목 현상이나 에러가 발생되는지 확인하는데 도움이 됩니다. 가장 자주 발생하는 문제는, 503 Server Unavailable 이나 504 Gateway Timeout 에러 발생과 동시에, ELB 의 모니터링 지표에 SurgeQueueLength 가 1024 로 기록되고 SpilloverCount 가 0 보다 높을 경우 입니다. SurgeQueueLength 는 ELB 에 등록된 백엔드 인스턴스가 요청을 처리하지 못하여 쌓이게 되는 ELB 의 큐 이며, 앞서 언급한 것처럼 1024가 큐의 최대 크기 입니다. 이 최대 크기를 넘어서면 요청을 한 클라이언트에 5xx 에러를 보내게 되고, 동시에 SpilloverCount 가 기록 됩니다. 이 문제를 해결하기 위해서는 적시에 Auto Scaling 이 일어날 수 있도록 적절한 지표를 기준으로 알람을 발생 시킬 수 있도록 해야 합니다. 백엔드 인스턴스에서 동작하는 시스템이 어떤 자원을 더 많이 사용하는지 확인을 하고, 그 자원의 지표를 기준으로 Auto Scaling 을 설정하는 것이 좋습니다. SurgeQueueLength 를 기준으로 알람이 발생하게 하는 것도 한 가지 방법 입니다. ELB 가 제공하는 지표들은 여기에서 확인할 수 있습니다.
    추가로 ELB 도 발생하는 부하에 따라 Auto Scaling 을 통해 규모가 변화되도록 설계 되어 있습니다. 만약, 매우 급격한 트래픽 증가가 일어날 때 ELB 가 확장되는 속도가 증가하는 트래픽을 수용하지 못한다면 5xx 에러가 발생할 수 있습니다. 이를 방지 하기 위해, 미리 점차적으로 증가하는 부하 테스트를 통해 어느정도 ELB 를 확장 시켜 둘 수 있습니다. 또한, ELB 에 등록된 백엔드 인스턴스에서 Keep-Alive 기능을 활성화 시켜, ELB 와 백엔드 인스턴스간에 불필요한 연결 수립이 일어나는 것을 방지한다면 더 높은 성능을 기대할 수 있습니다.
  4. 서버 인스턴스: 서버 인스턴스의 설정 값에 따라 웹/애플리케이션 서버의 자원 사용 효율성이 달라 집니다. 대표적으로 Linux 서버의 open files 숫자를 늘려 두지 않으면, 동시 접속이 기본값인 1024 개로 제한되어 서버 인스턴스를 효과적으로 사용할 수 없습니다.
  5. 애플리케이션 서버: 애플리케이션 서버의 종류마다 다르지만, 일반적으로 Tomcat 등의 Thread 기반의 애플리케이션 서버일 경우, Thread Pool 의 크기가 너무 작다면 처리 되어야할 요청들이 기다리는(waiting) 상태가 길어져 전체 부하 테스트의 효율성을 떨어뜨릴 수 있습니다. 최근에는 Thread 기반의 애플리케이션 서버가 가지고 있는 제약으로 인해, Event 기반의 비동기 형태의 애플리케이션 서버가 자주 사용되어 집니다.
  6. 애플리케이션: 애플리케이션 코드와 프레임워크 둘로 나누어 생각해 볼 수 있습니다. 애플리케이션 코드 내에서 잘못된 방식으로 프레임워크나 API 를 사용할 수 있으며, blocking 코드가 포함되어 있다거나, 불필요한 연산을 진행, 또 테스트 코드를 삭제하지 않았다는 등의 문제가 있을 수 있습니다. 이 때에는 적절한 Unit Test 와 Lint 등을 통해 문제를 조기에 발견할 수 있어야 합니다.  사용하는 웹 프레임워크나 ORM(Object-relational mapping)과 같은 라이브러리가 가지고 있는 버그로 인하여 문제가 발생할 수도 있습니다. 애플리케이션 관련 다양한 문제를 해결하기 위해 APM(Application Performance Monitoring) 을 활용하여 성능을 모니터링 하는 것도 한가지 방법 입니다.
  7. 데이터베이스: CPU 사용률과 응답 시간(response time) 등을 확인할 필요가 있습니다. 특히 데이터베이스를 설정한 방법에 따라 더 요구되는 자원을 눈 여겨 볼 필요가 있습니다. 최근에는 성능을 향상 시키기 위해 메모리를 적극 활용하게끔 설정을 하는 경우가 많으며, 이때에는 메모리 사용률이 특정 수치 이상으로 넘어갈 경우, 알람을 발생 하도록 설정하여 병목 지점 확인이 가능 하겠습니다.

부하 테스트 관련 도구 및 서비스
부하 테스트를 위해 사용할 수 있는 다양한 툴과 서비스들이 존재 합니다. 간단한 소개와 함께 특징들을 살펴보겠습니다.

  • ApacheBench (http://httpd.apache.org/docs/2.2/en/programs/ab.html): 일반적으로 HTTP 웹 서버의 성능을 측정하기 위해서 자주 사용되는 툴입니다. 기본적인 HTTP 연결에 대해서 테스트를 할 때 자주 사용되는 툴이지만, HTTP/1.1 을 지원하지 않고, 한번에 하나의 대상 URL로 테스트가 가능한 것 등의 제한이 있습니다.
  • Siege (https://www.joedog.org/siege-home/): HTTP 부하 테스트와 벤치마킹 유틸리티 입니다. 한번에 복수개의 URL 로 테스트가 가능하고, Basic 인증을 지원하며 HTTP와 HTTPS 프로토콜로 테스트가 가능하는 등 ApacheBench 의 제한을 어느정도 해소 해 줍니다. 또 ApacheBench 와 비슷한 인터페이스를 가지고 있어 다른 툴과의 연계가 자유로운 편입니다. 하지만 Thread 기반으로 구현되어 있어 동시성에 제한이 있으며, 미미할 수 있지만 Context Switching 등으로 인하여 성능에도 영향이 있습니다.
  • JMeter (http://jmeter.apache.org/): 1998 년 부터 시작된 프로젝트로서 오랫동안 기능 강화를 지속해오고 있는 Java 기반의 부하 테스트 툴 입니다. HTTP 뿐만 아니라 다양한 프로토콜을 지원하며, 많은 기능을 가진 GUI 를 제공 합니다. 실제 워크로드를 시뮬레이션 하기 위해 다양한 방법으로 커스터마이징이 가능합니다. 하지만 앞서 언급한것처럼 Thread 기반으로 구현되어 있어 성능과 동시성에 대해서 제한이 있습니다. 이를 해결하기 위해, 복수개의 인스턴스에서 실행시켜 테스트를 수행할 수 있는 Remote Testing 기능을 지원 합니다. 복수개의 EC2를 활용하거나, BlazeMeterFlood.io 와 같은 JMeter 를 지원하는 부하 테스트 서비스를 사용할 수도 있습니다.
  • The Grinder (http://grinder.sourceforge.net/): Java 기반의 부하 테스트 프레임워크 입니다. Agent 가 지정한 값을 기반으로 부하를 생성하며, IDE 형태의 콘솔에서 Agent 들을 제어 및 결과 모니터링이 가능합니다. 마찬가지로 Thread 기반으로 구현되어 있어 성능과 동시성에 대해 제한이 있습니다.
  • Gatling (http://gatling.io/): Akka 와 Netty 기반의 Scala 로 개발된 부하 테스트 프레임워크 입니다. Thread 기반이 아닌 Event 와 Async IO 기반으로 구현되어, 높은 성능을 제공하며 HTML 보고서 생성 기능은 물론 시나리오를 DSL 로 작성하여 부하 테스트에 사용할 수 있는 기능을 제공하고 있습니다.
  • Tsung (http://tsung.erlang-projects.org/): Erlang 으로 개발된 부하 테스트 툴로서, HTTP는 물론 Websocket 이나 인증 시스템, 데이터베이스, MQTT 와 같은 TCP 기반의 다양한 프로토콜을 지원합니다. 동시성 지향 프로그래밍 언어 (concurrency-oriented programming language) 인 Erlang 이 가지고 있는 장점으로 인해, 성능과 확장성, 내결함성(Fault Tolerance)에서 큰 이점을 제공하고 있습니다. GUI 를 제공하지 않아 CLI 또는 Script 를 활용하여야 합니다.
  • Bees (https://github.com/newsapps/beeswithmachineguns): AWS 의 장점을 적극 활용한 오픈 소스 부하 테스트 툴로서, 부하를 분산 생성하기 위해 사용자의 AWS 계정에서 EC2 인스턴스를 지정한 개수만큼 생성하여 부하 테스트 대상에 테스트를 진행합니다. 비용을 줄이기 위해 스팟 인스턴스를 생성하여 활용할 수 있는 옵션도 제공하고 있습니다. 부하 테스트를 위해 ApacheBench 를 사용하기 때문에 ApacheBench 가 가지고 있는 제한을 그대로 가지고 있습니다.
  • Vegeta (https://github.com/tsenart/vegeta): Go 언어로 개발된 오픈소스 HTTP 부하 테스트 툴이며 CLI 로 사용하거나 라이브러리로 사용 가능합니다. 보통의 다른 툴과 다르게 초당 일정한 속도로 특정 수치의 요청을 지속하는데 초점을 맞추고 있어, 예상되는 최대 트래픽이 지속적으로 발생하였을 때 서비스와 인프라의 상태가 어떻게 변화하는지 확인하는데 적합한 툴 입니다.
  • RedLine13 (https://www.redline13.com/): AWS 의 Advanced Technology Partner 중 하나로서, AWS 기반의 부하 테스트를 수행하는 서비스 입니다. 사용자의 AWS 계정에서 IAM 을 생성하여 RedLine13 과 연동을 하면, 사용자의 AWS 계정에서 Agent 가 포함된 인스턴스가 실행되어 테스트가 진행 됩니다. 스팟 인스턴스를 활용할 수 있어 비용을 줄일 수 있습니다. 여기에서 RedLine13 과 Apache JMeter 를 활용하여 모바일 애플리케이션을 테스트 하는 데모를 보실 수 있습니다.
  • Loader.io (https://loader.io/): 클라우드 기반의 부하 테스트 서비스로서, 웹 페이지에서 대상 서버를 선택하고 원하는 동시성 등을 지정할 수 있으며, 그 결과를 보기 좋게 표시해 줍니다. 또한, 진행한 테스트를 추후에 리플레이 할 수도 있습니다.
  • Goad (https://goad.io/): AWS Lambda 를 활용하여 부하를 분산 생성하여 테스트를 수행하는 오픈 소스 툴로서 Go 언어로 개발 되었습니다. 실제 AWS 의 이점과 AWS Lambda 의 강력함을 잘 활용한 툴 입니다. 툴을 실행하면 AWS Lambda 함수를 생성 (혹은 갱신) 하여, 대상 URL 에 동시적으로 부하 테스트를 시작합니다. 그리고 각 결과는 AWS SQS 로 보내어지고, 최종적으로 최초 실행한 툴에서 결과를 취합하여 보여줍니다.
    load-test-image003

그밖에도 다양한 AWS Marketplace에서도 다양한 파트너들의 부하 테스트 솔루션을제공하고있습니다.

부하테스트 시 유용한 팁 모음
AWS 클라우드 기반으로 부하 테스트를 진행하는데 있어서 알아두면 도움이 되는 팁들을 간단하게 소개해 드리고자 합니다.

  • 로그 기록과 확인: 부하 테스트를 진행할 때, 로그 작성은 File IO 를 요구하는 작업이기에, 미미하게 테스트 수행 능력에 영향을 미칠 수 있습니다. 특히나 테스트 환경에서는 로그 수준을 낮추어(verbose 또는 debug) 더 많은 로그가 기록되도록 하는 경우가 많아, 실제 프로덕션에 비해 성능에 미치는 영향이 더 클 수 있습니다. 하지만 가능한 로그를 기록하는게 추후 발생하는 문제 파악에 도움을 줄 수 있기 때문에 기록을 하는게 권장 됩니다. 또는, 최초의 워밍업을 진행할 때 로그를 활성화하여 진행을 하고, 이후의 테스트에 대해서는 로그를 비활성화하여 진행할 수도 있겠습니다. 만약 그 결과가 기대에 미치지 않는다면, 다시 로그를 활성화 한 뒤 테스트를 진행하고, 생성되는 로그를 통해 문제를 파악할 수 있겠습니다.
  • 적절한 인스턴스의 선택: EC2 인스턴스는 요구되는 워크로드에 맞추어 선택할 수 있도록 다양한 종류가 준비되어 있습니다. 사용하는 웹/애플리케이션/데이터베이스에 따라 상대적으로 더 요구되는 자원은 다를 수 있으며, 그에 맞추어 인스턴스를 선택하는 것이 비용도 절약하며 더 적합한 성능을 발휘할 수 있겠습니다. 예를 들어, 특별한 연산이 수행되지 않고 다른 서비스와 통신이 주 목적이라면, 적은 CPU 자원과 적절한 Memory 자원, 그리고 높은 Bandwidth 와 Enhanced Networking 을 제공하는 인스턴스가 비용과 성능 관점에서 좋은 선택일 수 있습니다.
  • 비용 최소화: 유의미한 결과를 얻기 위해 부하 테스트 대상 환경을 가능한 실제 환경과 비슷하게 구성할 필요가 있습니다. 물론, 실제 프로덕션 환경에서 테스트를 수행할 수도 있지만, 이는 가상의 가짜(Dummy) 데이터를 생성하여 프로덕션 환경을 어지럽히고 라이브 서비스의 안정성을 헤치는 등의 위험성이 있기에 적극적으로 피해야 합니다. 실제 프로덕션 환경과 비슷한 환경을 구성하여 부하 테스트를 수행하는데 있어서 가장 먼저 고려하여야 할 사항은 비용 입니다. 일반적으로 최대 부하에 대한 테스트 진행을 위해, 수십에서 수백 대의 인스턴스가 필요할 수 있으며, 큰 규모의 부하를 생성하기 위해서도 많은 인스턴스가 요구되기 때문입니다. 이 때, 비용을 최소화 하기 위해 스팟 인스턴스를 활용할 수 있습니다. 스팟 인스턴스는 EC2 컴퓨팅 예비 용량에 입찰을 통해 온디맨드 요금과 비교하여 할인된 요금으로 사용할 수 있는 방법을 제공하며, 일반적으로 50~90% 저렴한 비용으로 사용할 수 있습니다. 자세한 내용은 여기를 참조 하시기 바랍니다.
  • 다양한 도구와 서비스의 복합적 활용: 앞서 부하 테스트를 위한 다양한 툴과 서비스에 대해서 알아 보았습니다. 부하 테스트를 진행할 때, 어떤 특정 툴 또는 서비스 하나만 선택해서 사용 할 필요는 없습니다. 각 툴과 서비스 별로, 목적성과 고유의 특징을 가지고 있기에, 용도에 맞게 복합하여 사용하는게 더 효율적일 수 있습니다. 예를 들어, GUI 의 완성도가 높고 다양한 시나리오를 설정할 수 있는 JMeter 는 웹 애플리케이션 내에서 사용자의 행동 흐름에 대해 부하 테스트를 하는데 적합할 수 있습니다. 그 이외의 툴과 서비스들도 특징들이 있으며 다음과 같이 활용할 수 있겠습니다.
    도구 명 활용 방법
    JMeter 웹 애플리케이션 내에서 사용자의 행동 흐름에 대해 부하 테스트를 하고 싶을 때
    Tsung API 가 수용할 수 있는 최대치의 부하를 알고 싶을 때
    Vegeta 어떤 API 에 대해 초당 특정 수치의 요청이 지속될 경우 발생하는 상황을 파악하고 싶을 때
    Goad 부하 생성 클라이언트 구성을 포함한 부하 테스트 관련 인프라 구성을 피하고 싶을 때
    RedLine13 JMeter 로 테스트 플랜을 작성하여 활용을 원하지만, 비용을 최소화 하고 싶고 사용한 만큼만 비용이 발생하길 원할 때
    Blazemeter 높은 동시성을 위해 JMeter 의 Remote Testing 기능을 활용하고 싶지만, 테스트 플랜 작성에 집중하고, 부하 테스트 관련 인프라 구성은 하고 싶지 않을 때
    Loader.io 부하 테스트 관련 인프라 구성을 하고 싶지 않고, Tsung 과 비슷한 목적으로 사용하고 싶을 때

부하 테스트에는 보통의 시나리오 기반의 가상의 워크로드에 대한 부하 테스트 뿐만 아니라, 실제 워크로드에 대한 부하 테스트를 위해 프로덕션에서 발생하는 트래픽을 활용하는 방법도 있습니다. 프로덕션 환경의 실제 트래픽을 활용하기 위해서는, 트래픽을 재사용 하기 위해 기록하거나 곧바로 테스트 환경으로 리플레이 해 주는 gor (https://goreplay.org/) 와 같은 툴을 사용할 수 있습니다. 프로덕션 환경의 실제 트래픽을 반복하여 활용할 수 있다는 이점 이외에도, 서비스 및 애플리케이션 업데이트 예정 버전에 실제 트래픽을 흘려 보내 사용성에 대한 테스트도 진행할 수 있는 장점도 있습니다.

추가로, 많은 경우 부하 테스트는 개발 주기 마지막에 진행되는 경우가 많습니다. 애자일 방법론과 지속적인 통합(CI/CD)을 활용하는 경우 조금 더 일찍, 자주 부하 테스트를 진행할 수 있으며, 이를 통해 추후 발생할 수 있는, 비용이 많이 드는 성능 문제를 미리 방지할 수 있습니다. 따라서, 가능한 개발 주기에 부하 테스트를 포함하여 자주 진행하는 것이 바람직하다고 볼 수 있습니다.

AWS 에서 제공하는 방대한 인프라와 이미 부하에 대해 고려되어 설계된 관리형 서비스, 그리고 다양한 규모 관련 기능들을 적극 활용하여 서비스를 구축한다면 가변적인 부하에도 유연한 서비스를 제공할 수 있을 것 입니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김필중 솔루션즈 아키텍트께서 작성해주셨습니다.
load-test-image005

Amazon DynamoDB 데이터 백업 및 복원 방법 – (1) Datapipeline 및 오픈 소스 도구 활용하기

Amazon DynamoDB는 서비스 규모와 관계없이 10밀리초 미만의 지연 시간이 일관되게 요구되는 모든 애플리케이션을 위한 빠르고 유연한 NoSQL 데이터베이스 서비스입니다. 또한, 완전 관리형 클라우드 데이터베이스로서 문서 모델과 키-값 스토어 모델을 모두 지원합니다. 특히, 유연한 데이터 모델과 안정적인 성능을 갖추고 있어 모바일, 웹, 게임, 광고 기술, IoT를 비롯한 그 밖의 많은 애플리케이션에 매우 적합합니다.

특히 Amazon DynamoDB는 AWS 리전의 세 개 시설에 데이터를 동기적으로 적재하는 구조를 가지고 있어, 매우 높은 가용성과 데이터 안정성을 제공합니다. 따라서, 데이터 유실에 대한 우려를 크게 낮출 수 있습니다. 그런데, 필요에 따라서 DynamoDB에 저장되어 있는 데이터를 백업하거나 다른 리전으로 복사해야 할 필요가 생길 수 있습니다. 이는 전통적인 데이터 유실에 대한 데이터 백업이나 복원 목적이라기 보다는 로컬에서 데이터 활용을 하거나, 타 리전에 데이터 이동에 목적으로 생각할 수 있습니다.

이 글에서는 DynamoDB에 저장된 데이터를 백업하는 몇 가지 방법을 알아보도록 하겠습니다. Amazon DynamoDB를 이용하여 테이블(Table)을 생성하면 DynamoDB는 직접 내보내기 및 가져오기 기능을 제공하고 있습니다. 원하면 언제든지 테이블의 내용을 백업하거나, 백업한 테이블의 내용을 다른 테이블로 넣을 수 있습니다.

DynamoDB 내보내기(Export) 기능
먼저 테이블 내보내기 기능 부터 살펴 보겠습니다. 아래의 그림과 같이 원하는 테이블을 선택하고 Export를 실행하면됩니다.

dynamodb-image003

DyanmoDB의 내보내기 기능은 AWS의 다른 서비스 중 하나인 AWS Data Pipeline을 이용합니다. Data Pipeline은 Amazon EMR을 실행하도록 되어 있으며, 바로 EMR Cluster를 생성하고 생성된 EMR Hadoop Cluster가 바로 DyanmoDB의 데이터를 가져오는하는 작업(Job)을 실행하는 구조로 되어 있습니다.

AWS Data Pipeline은 DynamoDB 테이블 백업 뿐만 아니라 온프레미스 데이터 소스 및 다른 AWS 컴퓨팅 및 스토리지 서비스 간에 데이터를 안정적으로 지정된 간격으로 이동할 수 있게 해 주는 서비스입니다. AWS Data Pipeline을 사용하면 저장 데이터에 정기적으로 접근하고, 데이터 크기를 변환 처리하며 Amazon S3, Amazon RDS, Amazon DynamoDB 및 Amazon Elastic MapReduce(EMR)와 같은 AWS 서비스에 그 결과를 효율적으로 전송할 수 있습니다.

DynamoDB에서도 테이블을 선택하고 내보내기(Export)를 누르면 아래와 같이 새로운 파이프라인(Pipeline)을 생성합니다. 테이블의 내용을 바로 Amazon Simple Storage Service(S3)에 저장합니다.

dynamodb-image005

Source DynamoDB table name은 내보내고 싶은 테이블명으로 자동으로 입력이 되어 있습니다. Output S3 folder는 내보내기 후 데이터가 저장될 S3 버킷 폴더를 지정합니다. DynamoDB read throughput ratio는 현재 소스가 되는 DynamoDB의 읽기 용량 중에 어느 정도 비율을 사용할지 지정하는 중요한 파라미터입니다. 만약 운영 중인 서비스의 테이블이라면 읽기 용량의 일부를 지정한 비율만큼 내보내기 작업에 사용하므로, 너무 큰 비율을 지정하면 읽기 용량이 모자라게 되면 서비스에 영향이 있으므로 주의하여야 합니다.

따라서, Consumed read capacity unit을  CloudWatch 통계치를 확인하여 적절한 비율을 입력하도록 합니다. 위의 예제에서는 0.25로 기본 값이 지정되어 있습니다. 즉, Provisioned Read Capacity Unit중에 약 25%를 내보내기를 하는데 사용합니다. Throughput ratio를 정식 서비스 환경에서는 항상 유의하여 지정해야만 합니다. Region은 DynamoDB 테이블이 있는 리전을 선택합니다. Schedule은 Data Pipeline에서 바로 실행할지, 아니면 특정 시기/시간에 주기적으로 실행할지 여부를 구성합니다.

주기적으로 실행하도록 구성하면 Data Pipeline이 알아서 주기적으로 DynamoDB 테이블 내보내기를 진행하여 운영에 편의성을 제공합니다. Logging은 Log 내용을 남길 S3 위치를 지정하게 됩니다. 뒤에 작업(Job)이 실패했을 경우 문제가 어떤 것인지 찾을 수 있도록 로그 정보를 제공합니다.

Activate를 눌러 다음으로 진행하면 Data Pipeline이 생성됩니다. Data Pipeline은 EMR Cluster를 생성하고 데이터 내보내기를 진행하게 됩니다.

dynamodb-image007

위 그림과 같이 작업이 끝나게 되면 Status가 FINISHED로 상태가 바뀌게 되며, Logs에서 작업 중에 남겨진 로그 내용을 직접 확인할 수도 있습니다. 위 예제에서 s3://ilho-virginia-01 S3 Location을 설정 하였으므로 해당 버킷 밑에서 백업된 파일을 직접 확인할 수 있습니다.

dynamodb-image009

작업이 일어난 시간으로 폴더가 생성되고 내보내기한 파일이 조금 복잡한 이름으로 생성되어 저장이 되어 있습니다. 나중에 해당파일을 직접 접근할 때는 manifest파일을 보면 안에 해당 파일에 대한 s3 주소가 저장되어 있어, 직접 확인하지 않고도 접근할 수도 있습니다. 아래는 내보낸 DynamoDB 테이블 데이터 내용입니다. (예제는 웹페이지의 레퍼러(referrer)를 저장한 테이블입니다.)

dynamodb-image011

파일을 열어보면 아래와 같이 JSON 형태로 저장되어 있는 것을 확인 하실 수 있습니다. 따라서, 해당 파일을 데이터 분석과 같은 다른 용도로 사용할 경우, 빠르게 활용할 수 있습니다.

dynamodb-image013

DynamoDB Import 기능 소개
내보내기 파일을 이용하여 새로운 DynamoDB Table에서 해당 데이터를 가져오기(Import)할 수 있습니다. 단, 동일한 키(key)를 가지도록 먼저 대상 테이블(Target table)을 생성하고 가져와야 합니다. 따라서, 먼저 테이블을 생성하고, Primary Partition key, Primary Sort key를 동일하게 설정하여 새로운 테이블을 만듭니다.

아래와 같이 Import-test라는 테이블을 만들고 resource, timestamp를 Partition key와 Sort key로 지정하였습니다. 한가지 고려할 사항은 처리 용량(Provisioned Capacity)을 지정할 때 기본 값보다는 쓰기 용량(Write Capacity units)을 크게 지정하여 가져오기 하는 시간을 단축할 수 있습니다.

dynamodb-image015

새로 생성된 테이블을 선택하고 Import를 메뉴에서 선택합니다. 역시 Data Pipeline을 설정하는 페이지가 열리면서 내보내기할 때와 비슷한 내용을 입력합니다. 단, Import 이므로 반대로 Input S3 folder로 Export에 백업 파일이 있는 위치를 입력합니다. 내보내기할 때는 읽기 용량(Read capacity)사용에 대한 비율을 지정하였으나, 가져오기에서는 반대로 쓰기 용량(Write capacity)에 대해서 설정을 합니다. 새로 만든 테이블이므로 최대한 빠르게 가져오기를 진행하려면 100%로 지정하여, 모든 Write capacity를 가져오기에 사용할 수 있습니다. 단, 이는 예제이므로 서비스 상황에 따라 설정하여 사용합니다.

dynamodb-image017

설정 후에 Activate를 진행하면 Data Pipeline이 생성되고 EMR Cluster를 생성하여 S3에 Export된 데이터로부터 DynamoDB Table에 데이터를 넣는 작업(Job)을 실행합니다. Job이 FINISHED로 끝난 것을 확인하고 대상이 되는 Table에 실제 데이터가 들어왔는지 확인합니다.

dynamodb-image019

테이블에 데이터가 입력된 것을 확인 할 수 있으며, CloudWatch 통계에서 읽기 처리 그래프(Write Capacity Graph)를 보면 입력한 것처럼 전체 읽기 용량을 이용하여 데이터 가져오기 작업을 진행한 것을 확인 할 수 있습니다. (본 예제에서는 쓰기 용량(Write capacity) 값을 100으로 미리 설정(Provisioning)하고, 일정시간 동안 100의 용량이 소모된 것을 볼 수 있습니다.)

dynamodb-image021

한 가지 유의하실 것이 있습니다. DynamoDB의 Table 설명 정보에는 Storage size와 Item count 정보가 있습니다. 이 정보는 현재는 약 6시간 주기로 업데이트 되는 정보로 가져오기를 끝내자 마다 해당 정보 내용을 확인하신다면, 아직 0으로 표기가 되어 있게 됩니다. 따라서 실제 데이터를 직접 읽어 확인해 보는게 필요합니다. AWS CLI에서 보면 아래와 같이 확인이 가능합니다. 참고하여 주십시오.

$ aws dynamodb describe-table --table-name Import-test --query 'Table.{Count:ItemCount,Size:TableSizeBytes}'
{
    "Count": 0,
    "Size": 0
}
$ aws dynamodb scan --table-name Import-test --max-item 2
{
    "Count": 2777,
    "Items": [
        {
            "timestamp": {
                "S": "2014-12-15T12:49:10.147Z"
            },
            "host": {
                "S": "ip-10-97-166-72"
            },
            "resource": {
                "S": "/index3.html"
            },
            "referrerCounts": {
                "S": "[{\"referrer\":\"http://www.yahoo.com\",\"count\":3},{\"referrer\":\"http://www.reddit.com\",\"count\":1},{\"referrer\":\"http://www.bing.com\",\"count\":1},{\"referrer\":\"http://www.amazon.com\",\"count\":1}]"
            }
        },
        {
            "timestamp": {
                "S": "2014-12-15T12:49:10.247Z"
            },
            "host": {
                "S": "ip-10-97-166-72"
            },
            "resource": {
                "S": "/index3.html"
            },
            "referrerCounts": {
                "S": "[{\"referrer\":\"http://www.yahoo.com\",\"count\":3},{\"referrer\":\"http://www.reddit.com\",\"count\":1},{\"referrer\":\"http://www.bing.com\",\"count\":1},{\"referrer\":\"http://www.amazon.com\",\"count\":1}]"
            }
        }
    ],
    "NextToken": "eyJFeGNsdXNpdmVTdGFydEtleSI6IG51bGwsICJib3RvX3RydW5jYXRlX2Ftb3VudCI6IDJ9",
    "ScannedCount": 2777,
    "ConsumedCapacity": null
}

DynamoDB Import Export 도구
AWS 콘솔에서 제공하는 가져오기 및 내보내기 기능이 아닌 오픈 소스로 제공되는 https://github.com/awslabs/dynamodb-import-export-tool이 Github에 공개되어 있습니다. 간략히 설명 드리면, Java로 개발된 도구로 Data Pipeline이나 EMR을 사용하지 않고, 프로그램이 직접 DynamoDB의 데이터를 가져와서 타겟 DynamoDB Table에 데이터를 넣어주는 일을 하게 됩니다. 또한 병렬 검색(Parallel Scan)을 지원하여, 단일 EC2 인스턴스가 아닌 여러 인스턴스를 사용하여 병렬로 작업이 가능합니다. 아래 예제는 Amazon Linux AMI를 이용해 새로운 인스턴스를 생성하고 백업 및 복원을 진행 하는 방법입니다.

  1. 빌드를 위해서는 maven이 필요합니다. Maven을 받아 설치를 합니다.
    $ wget http://ftp.kddilabs.jp/infosystems/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
    $ tar -zxvf ./apache-maven-3.3.9-bin.tar.gz
  2. 미리 Java JDK를 설치하여 mvn install 시에 중간에 에러가 없도록 준비합니다.
    $ sudo yum install -y java-1.7.0-openjdk-devel
  3. Github 의 소스를 Local로 가져옵니다.
    $ git clone https://github.com/awslabs/dynamodb-import-export-tool.git
  4. 아래 명령을 통해 빌드와 설치를 진행합니다.
    $ mvn install
    ……………………………………
    [INFO] Replacing /opt/dynamodb-import-export-tool/target/dynamodb-import-export-tool-1.0.1.jar with /opt/dynamodb-import-export-tool/target/dynamodb-import-export-tool-1.0.1-shaded.jar
    [INFO]
    [INFO] --- maven-gpg-plugin:1.6:sign (sign-artifacts) @ dynamodb-import-export-tool ---
    Downloading: https://repo.maven.apache.org/maven2/org/sonatype/plexus/plexus-sec-dispatcher/1.4/plexus-sec-dispatcher-1.4.pom
    Downloaded: https://repo.maven.apache.org/maven2/org/sonatype/plexus/plexus-sec-dispatcher/1.4/plexus-sec-dispatcher-1.4.pom (3 KB at 9.9 KB/sec)
    Downloading: https://repo.maven.apache.org/maven2/org/sonatype/plexus/plexus-sec-dispatcher/1.4/plexus-sec-dispatcher-1.4.jar
    Downloaded: https://repo.maven.apache.org/maven2/org/sonatype/plexus/plexus-sec-dispatcher/1.4/plexus-sec-dispatcher-1.4.jar (28 KB at 90.8 KB/sec)
    [INFO]
    [INFO] --- maven-install-plugin:2.4:install (default-install) @ dynamodb-import-export-tool ---
    [INFO] Installing /opt/dynamodb-import-export-tool/target/dynamodb-import-export-tool-1.0.1.jar to /root/.m2/repository/com/amazonaws/dynamodb-import-export-tool/1.0.1/dynamodb-import-export-tool-1.0.1.jar
    [INFO] Installing /opt/dynamodb-import-export-tool/pom.xml to /root/.m2/repository/com/amazonaws/dynamodb-import-export-tool/1.0.1/dynamodb-import-export-tool-1.0.1.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 34.017 s
    [INFO] Finished at: 2016-08-01T01:12:26+00:00
    [INFO] Final Memory: 20M/50M
    [INFO] ------------------------------------------------------------------------
  5. 최종 빌드 된 jar파일이 있는 폴더로 이동하여 Github에 나온 안내를 참고하여 필요한 인자(Argument)를 주어 실행합니다. 아래 예제는 버지니아 리전에 있는 Kinesis-Data-Visualization-CountsDynamoDBTable-49AHMYW180WC 테이블을 동일한 리전에 Kinesis-target-3이라는 테이블로 복제하는 명령입니다. 이 도구에서도 읽기 혹은 쓰기 용량(read/write capacity)에 대해서 비율를 지정하여 얼마만큼의 Provisioned Capacity를 사용할지 지정을 하게 됩니다. 여기서는 1로 지정하여 100%를 다 사용하도록 지정하였습니다.
    $ java -jar dynamodb-import-export-tool-1.0.1.jar --sourceEndpoint dynamodb.us-east-1.amazonaws.com --sourceTable Kinesis-Data-Visualization-CountsDynamoDBTable-49AHMYW180WC --destinationEndpoint dynamodb.us-east-1.amazonaws.com --destinationTable Kinesis-target-3 --readThroughputRatio 1 --writeThroughputRatio 1

앞서 말씀 드린 대로 Amazon DynamoDB는 고가용성과 안정성을 지원하도록 디자인된 관리형 서비스입니다. 가용성 측면보다 데이터 활용 측면에서 데이터 이동이 필요한 경우에는 Datapipeline을 통한 DynamoDB 가져오기 및 내보내기 기능 및 오픈 소스 도구를 활용할 수 있습니다.

또한, DynamoDB Stream을 통한 크로스 리전 복제 솔루션 역시 사용이 가능합니다. 다음에는 오픈 소스로 공개되어 있는 DynamoDB Cross-Region Replication Library를 통해 실시간 복제 방법에 대해서 알아보겠습니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김일호 솔루션즈 아키텍트께서 작성해주셨습니다.

Transit VPC를 통한 Cloud HUB 디자인하기 – AWS 서드 파티 도구 기반

Amazon Virtual Private Cloud(VPC)는 AWS 클라우드의 가상네트워크 환경을 구성하는 기본 서비스로서, 필요에 따라 원하는 수 만큼의(기본: 리전당 5개) 가상 네트워크 환경을 생성하실 수 있습니다. 그러나, 기존 데이터 센터나 사무실의 온-프레미스 네트워크 혹은 복수의 VPC를 서로 연동해야하는 경우가 있습니다. 이때, VPC간 연결 혹은 VPC와 온 프레미스(데이터 센터)를 어떻게 연동할지, 그리고 더 나아가 구성된 네트워크 환경에서 운용될 어플리케이션 서비스의 트래픽 흐름은 어떻게 될지 등을 면밀히 검토해 보아야 합니다.

온 프레미스(데이터 센터)와  연결 구성시 추천하는 첫번째 방법은 AWS Direct Connect를 활용하여, 모든 VPC에 가상 회선(Virtual Circuit) 을 할당하여 상호간 라우팅이 가능하도록 구성하는 것입니다. 그런데, 단일 리전 연동이 아닌 복수개의 리전에 걸쳐 AWS클라우드 가상 네트워크 인프라를 구성해야 하는 요건이 있을 경우에는 모든 VPC에 Direct Connect의 가상 회선을 연결하는 것은 사실상 불가능에 가깝습니다.

이러한 상황에서 고려해 볼 수 있는 아키텍쳐 디자인이 바로, 두번째로 Transit VPC를 통한 Cloud HUB 디자인 입니다. Cloud HUB는 말그대로 하나의 VPC가 모든 연결을 맺는 HUB VPC가 되고, 온 프레미스(데이터센터) 및 다른 VPC들은 Spoke VPC 가 되게 한후, 모든 통신은 HUB VPC를 경유하게 하는 구성입니다.

CSR100V를 통한 Cloud HUB 디자인
이러한 구성을 위한 필수 요건으로 Transitive Routing(전달받은 라우팅 경로를 3자에게 전달 방식)이라고 하는 기능이 필요하며, AWS Marketplace의 Cisco 소프트웨어 라우터인 CSR1000V와 같은 서드 파티 제품을 사용하여 아래와 같이 구성할 수 있습니다.

image001[그림1. Cisco CSR1000V를 이용한 Transitive Routing 구성 방식]

Cisco CSR1000V는 Cisco의 하드웨어 라우터 제품 중 ASR(Aggregation Services Router) 이라고 하는 엔터프라이즈 엣지(Edge)용 라우터를 가상 머신(VM)의 형태로 출시한 소프트웨어 라우터입니다. OSPF, EIGRP, BGP 등의 다양한 라우팅 프로토콜 및 IPSEC VPN ,방화벽 등의 보안기능들을 지원합니다. 또한, CSR 이 가지고있는 IOS-XE라는 운영체제(OS)는 하드웨어 제품과 동일한 CLI 및 인터페이스를 제공하여 많은  네트워크 엔지니어 들에게 친숙한 구성 환경을 제공한다는 장점을 가지고 있습니다.

상기 디자인과 같이 CSR1000V를 HUB VPC의 라우터로 구성하면 Spoke VPC가 생성된 AWS리전에 상관없이 스포크 일대일(Spoke-to-Spoke)통신 또는 스포크와 허브간(Spoke-to-Hub) 통신이 IPSEC 터널을 통해 쉽게 이루어지 됩니다. 특히, Cisco 라우터 설정에 익숙하지 않은 사용자라고 할지라도 Transit Network VPC (Cisco CSR 1000V 기반 아키텍쳐) 백서에 포함된 CloudFormation 템플릿을 사용하면,  손쉽게 위와 같은 Cloud HUB구성을 하실 수 있습니다. 또한, 새로운 VPC 추가 시에도 간단히 VGW에 특정 태그(Tagging)만 추가하면 자동으로 Cloud HUB환경에 Spoke VPC로 등록 되는 환경이 만들어 집니다.

다만, 아직 서울리전에는 Cisco CSR 1000V제품이 출시되어 있지않아 서울 리전을 HUB로 지정할 수는 없습니다. Cisco CSR1000V를 사용하기 어려울 시에는 AWS Marketplace에 등록되어 있는 Vyatta 또는 Fork버전인 VyOS 를 사용하여 유사한 구성을 하실 수 있습니다. Vyatta는 리눅스 기반으로 Qugga등의 오픈소스 라우팅 소프트웨어들이 패키지의 형태로 구성된 제품으로서, 현재 저렴한 커뮤니티 버전이 제공되며, AWS Marketplace를 통해 서울리전에서도 사용하실 수 있습니다. 이에 따라, 해당 제품을 기반으로 서울 리전을 HUB 로 구성가능한 Cloud HUB디자인을 살펴보도록 하겠습니다.

Vyatta 및 VyOS를 통한 Cloud HUB 디자인

image003[그림2. Vyatta 혹은 VyOS를 이용한 Transitive Routing 구성 방식]

상기 디자인은 앞에 사용된 Cisco CSR1000V 이 있던 자리를 Vyatta 혹은 VyOS라우터로 변경한 디자인 입니다. Vyatta 라우터도 CSR1000V과 같이 BGP를 포함한 다양한 라우팅 프로토콜을 지원하여, 기능적으로 손쉽게 CSR1000V과 같은 역할을 수행할 수 있습니다. 그런데, 실제 IPSEC 터널 구성을 해보면 아래와 같이, IP SEC 터널은 정상적으로 맺어져 UP 상태가 되더라도, BGP Neighbor로부터 아무 경로도 업데이트 받지 못하는 모습을 보시게 됩니다.

image005[그림 3. Transit VPC에서 확인하는 BGP 라우팅 테이블]

image007[그림 4. Spoke VPC의 IPSEC 터널 정보 : Tunnel UP상태지만 라우팅 정보는 수신 안됨]

위와 같은 증상은 타 리전간의 구성(AS Number가 다를 경우)에서는 나타나지 않고, 같은 리전 내의 VPC간 구성에서 나타납니다. 그 원인은 BGP 라우팅 프로토콜이 가진 기본적인 루프 방지 기능(Loop prevention)때문에, 동일 AS Number를 가진 BGP Neighbor로부터 들어오는 라우팅 업데이트는 차단을 하도록 되어 있는 라우팅 규칙 때문입니다.

Cisco CSR1000V 제품으로 구성된 이전 디자인에서는 정상적으로 라우팅 테이블이 업데이트 되는 모습을 보실수 있는데 , 이는 Cisco CSR1000V 의 경우 ‘as-override’ 라는 AS Number 정보를 무시하는(Override) 기능을 가지고 있기 때문인데, Vyatta 라우터의 경우는 아직 해당 기능을  제공하지 않습니다. 따라서, Vyatta라우터를 사용한 위와 같은 VPC 구성 시에는 Spoke VPC들이 서로 다른 리전(서로 다른 AS Number사용시)에 있을 때만 구성하실 수 있다는 제약이 있습니다.

단일 VPC(VGW)를 통한 Cloud HUB 디자인 아키텍처
이러한 Vyatta 라우터가 가진 제약을 극복할 수 있는 방안으로 고려해 보실 수 있는 새로운 VPC 디자인은 아래와 같이 하나의 VPC(VGW)를 HUB로 지정하고, 각각의 Spoke VPC에 Vyatta 라우터를 구성한 후 HUB VPC에 연결하는 방법입니다. 이때 각각의 Spoke VPC는 IPSEC 터널 구성시 원하는 숫자(지원 AS Number 대역 : 64512 ~ 65535)를 AS Number로 지정할 수 있으므로 이전 디자인상의 제약 사항 이었던 AS Number의 중복으로 인한 문제는 사라지게 됩니다.

image009Amazon VPC에서 IP SEC VPN을 구성하는 절차는 이 글의 주제에 포함되지 않으므로 자세히 말씀 드리진 않겠지만, 간략히 다음과 같은 단계에 의해 구성됩니다. (더 자세한 사항은 기술 문서를 참고하시기 바랍니다.)

  1. CGW생성 : 생성시 온 프레미스 VPN장비의 Public IP 주소 설정 (또는 EC2 EIP)
  2. VGW생성 후 구성을 원하는 Spoke VPC에 부착 (Attach)
  3. 생성한 CGW와 VGW를 연결하여 VPN 연결 설정 후, 라우터 설정 파일을 다운로드 받아 VPN장비(하드웨어 또는 EC2 인스턴스)에 설정

 Amazon VPC는 사용자들이 보다 쉽고 빠르게 다양한 제품들과의 IPSEC 터널 구성을 지원하고 있는데, 아래 화면과 같이 다양한 장비에 대한 설정파일을 제공하고 있어서, 사용자는 손 쉽게 원하는 장비에 대한 설정파일을 받아 설정을 완료할 수 있습니다.

image011

Cloud HUB 구성 시작하기
본격적으로 Vyatta에 대한 설정파일을 다운로드 받아, 해당 내용을 그대로 Vyatta 라우터에 적용하여 구성해보겠습니다. 이때 주의할 사항으로 Vyatta인스턴스에 해당 설정을 적용할 때에는, 기본적으로 Public IP(EIP) 정보가 설정되어 있는 로컬 어드레스(Local-address) 부분에 현재 Vyatta 라우터 인스턴스의 eth0인터페이스에 할당되어 있는 Private IP정보로 변경해 주시면 됩니다.

set vpn ipsec ike-group AWS lifetime '28800'
set vpn ipsec ike-group AWS proposal 1 dh-group '2'
set vpn ipsec ike-group AWS proposal 1 encryption 'aes128'
set vpn ipsec ike-group AWS proposal 1 hash 'sha1'
set vpn ipsec site-to-site peer 52.193.81.195 authentication mode 'pre-shared-secret'
set vpn ipsec site-to-site peer 52.193.81.195 authentication pre-shared-secret '@#$@#$'
set vpn ipsec site-to-site peer 52.193.81.195 description 'VPC tunnel 1'
set vpn ipsec site-to-site peer 52.193.81.195 ike-group 'AWS'                                    
set vpn ipsec site-to-site peer 52.193.81.195 local-address '52.197.159.126' -> Private IP변경
set vpn ipsec site-to-site peer 52.193.81.195 vti bind 'vti0'
set vpn ipsec site-to-site peer 52.193.81.195 vti esp-group 'AWS'

또한, BPG 라우팅 설정 부분에서도 아래와 같이 Spoke VPN의 Subnet중 원하는 대역 혹은 VPC의 모든 CIDR대역을 HUB로 광고(Advertise)하는 설정을 추가해야 합니다.

이때 만약 Vyatta인스턴스가 속한 Subnet이 아닌 VPC 전체에 대한 CIDR범위를 BGP 설정에 추가하면, 정상적으로 HUB VPC에 라우팅이 광고 되지 않으며, 반대로 Vyatta 인스턴스가 속한 Subnet 범위에 맞는 CIDR 범위만 을 BGP설정에 추가하면, 정상적으로 라우팅이 HUB VPC(VGW)로 광고 되는 모습을 볼 수 있지만, 해당 VPC가 가지는 다른 Subnet 영역에 대한 경로들은 추가되지 않아 궁극적으로 우리가 원하는 Spoke VPC의 Private Subnet 간 통신을 위한 라우팅 구성이 불가하게 됩니다.

위의 문제를 해결하기 위해서는, 각 Spoke VPC의 Vyatta 라우터 설정에서, 통신을 위해 라우팅 설정이 필요한 VPC CIDR 범위를 Static 라우팅을 통해 먼저 설정한 후, BGP설정에도 해당 대역을 추가해 주어야 합니다.

이 작업까지 완료하면, Vyatta 라우터가 위치한 Subnet 뿐만 아니라, 전체 VPC의 CIDR을 HUB VPC의 VGW로 광고하게 되며, 모든 경로(Route)들은 HUB VPC에 연결된 Spoke VPC로 전파되어 Any to Any 의 라우팅 경로가 구성됩니다. 아래는 Spoke VPC A – CIDR : 10.0.0.0/16 설정 예시입니다.

set protocols bgp 65002 network 0.0.0.0/0
set protocols static 10.0.0.0/16 next-hop 10.0.0.1 distance 10
#(10.0.0.0/16은 VPC 전체의 CIDR이며, 10.0.0.1은 해당 VPC에서 사용하는 Default G/W로써, 여러분이 사용하시는 VPC CIDR의 .1 주소는 기본적으로 Default G/W로 설정되어 있습니다.)
set protocols bgp 65002 network 10.0.0.0/16 
# BGP에 해당 CIDR (10.0.0.0/16)을 추가

모든 설정을 마치면, 아래와 같이 HUB VPC의 VPN 터널의 상태가 UP으로 변경됩니다.

image013

라우팅 경로가 모든 VPC에 제대로 업데이트 되었는지 여부는 다음과 같이 확인해 볼 수 있습니다.

  1. HUB VPC : VGW로부터 연결된 Spoke VPC 들의 경로가 자동으로 업데이트 되는지 확인 image015
  2. Spoke VPC A : HUB VPC – 100.64.127.224/27 , Spoke VPC B – 20.0.0.0/16 경로 확인
    image017
  3. Spoke VPC A 의 Route 테이블 확인
    image019
  4. Spoke VPC B : HUB VPC – 100.64.127.224/27 , Spoke VPC A – 10.0.0.0/16 경로 확인
    image021
  5. Spoke VPC B 의 Route 테이블 확인
    image023

Cloud HUB 구성 테스트 하기

위와 같이 라우팅 정보가 정상적으로 확인되면 모든 설정이 끝난 것입니다. 마지막으로 Spoke to Spoke VPC 의 통신이 제대로 이루어 지는지 Ping테스트를 통해 확인해 보도록 하겠습니다.

image025

  • Spoke VPC A 의 Private Subnet 에 있는 EC2 인스턴스(0.1.40) 에서 Spoke VPC B의 Private Subnet 에 있는 EC2인스턴스(20.0.1.138)로 Ping 테스트
    image027
  • Spoke VPC A 의 Private Subnet 에 있는EC2인스턴스(0.1.40) 에서 HUB VPC의 Private Subnet에 있는 EC2인스턴스(100.64.127.231)로 Ping 테스트
    image029

이 글에서 우리는 Cloud HUB VPC 구성을 위한 몇 가지 옵션들과 주의점들을 살펴 보았습니다. 만약 여러분이 글로벌 어플리케이션 서비스 혹은 공유 서비스 기반의 멀티 테넌트 어플리케이션 서비스를 구축 하시고자 한다면, 이 글에 설명된 것처럼Cloud HUB VPC 디자인을 통해 IP SEC 터널 기반의 멀티 VPC연동을 통한 확장성 있는 네트워크 인프라를 구성하실 수 있습니다.

자세한 것은 차근 차근 순서대로 설정 방법을 담은 가이드 문서를 참고하시기 바랍니다.

더 자세히 알아보기

  1. Transit Network VPC (Cisco CSR 1000V 기반 아키텍쳐)
  2. Multiple VPC VPN Connection Sharing
  3. Single Data Center HA Network Connectivity
  4. Multiple Data Center HA Network Connectivity

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김용우 솔루션즈 아키텍트께서 작성해주셨습니다.
speaker_Kevin1

AWS Lambda기반 VPC 지원 활용하기

지난 2월 블로그 글을 통해 AWS Lambda의 VPC 지원 기능을 소개해 드렸습니다. Lambda 함수를 통해 Amazon Redshift 데이터웨어 하우스, Amazon ElastiCache 클러스터, Amazon Relational Database Service (RDS) 인스턴스 및 특정 VPC 내에서만 접근할 수 있는 서비스 엔드 포인트에 접근할 수 있습니다. 이를 위해 자신의 VPC를 하나 선택하고 적절한 서브넷과 보안 그룹을 지정하면 됩니다. Lambda 함수가 VPC 내의 리소스에 접근할 수 있도록 하기 위해 Lambda는 이러한 정보를 바탕으로 Elastic Network Interfaces (ENI)와 사설 IP 주소를 지정합니다.

먼저 AWS Lambda 함수 설정시, 아래 그림과 같이 VPC 사용 여부를 정할 수 있습니다.

VPC 지원을 선택하면, VPC내 서브넷(Subnet)에 생성한 서비스, 예를 들어 RDS, Elasticache에 대해 접근이 가능하게 해주는 기능입니다. VPC 지원을 선택하면, 어떤 VPC를 선택할지 그 내부에 어떤 서브넷을 지원할 지를 선택할 수 있도록 해줄 수 있습니다. 이렇게 되면 선택된 서브넷에서는 람다를 위한 ENI가 생성되고 이를 통해 람다는 각 서브넷과 통신을 하게 됩니다.

본 기능이 만들어진 배경은 프라이빗 서브넷 안에 존재하는 리소스에 접근하기 위함입니다. 예를 들어, RDS나 Redshift 같은 서비스를 프라이빗 서브넷 안에 만들고 특정 작업을 하기 위해 람다를 이용해서 접근을 해야 한다고 생각해봅시다. 과거에는 불가능했기 때문에 RDS나 Redshift를 퍼블릭 서브넷에 두어야 했다. 이 경우 보안상의 문제로 인해서 문제가 될 수 있습니다.

그런데, 이렇게 VPC 를 지원하게 되면 람다는 VPC의 선택된 서브넷만 접근을 할 수 있게 됩니다. 사용자 입장에서는 하나의 람다 함수 안에서 RDS 나 Elasticache의 정보를 조회한 이후에 SNS에 메시지를 보낸다던가 S3에 특정 작업을 하고 싶을 수 있습니다. 이런 경우 프라이빗 서브넷에서 퍼블릭 망으로 접근 할 수 있는 기능이 존재해야 한합니다. 이를 위해 프라이빗 서브넷 안에 NAT 역할을 하는 인스턴스 혹은 NAT Gateway가 설정되어 있어야 합니다.

이번 글에서는 VPC 지원 활성과 퍼블릭 네트워크로 접근을 하는 기능 구현에 대해 알아보고자 아래 샘플 코드를 참고하겠습니다.

const AWS = require('aws-sdk');

exports.handler = (event, context, callback) => {
 console.log("\n\nLoading handler\n\n");
    var sns = new AWS.SNS();
    
    // req1();

    sns.publish({
        Message: 'Test publish to SNS from Lambda',
        TopicArn: 'arn:aws:sns:us-east-1:550622896891:crr-virginia-topic'
    }, function(err, data) {
        if (err) {
            console.log(err.stack);
            return;
        }
        console.log('push sent');
        console.log(data);
        context.done(null, 'Function Finished!');  
    });
};

var http = require('http');

function req1()
{
    var options = {
      host: 'amazon.com',
      path: '/',
      port: '80',
      //This is the only line that is neindew. `headers` is an object with the headers to request
      headers: {'custom': 'Custom Header Demo works'}
    };
    
    var req = http.request(options, function(response) {
      var str = ''
      response.on('data', function (chunk) {
        str += chunk;
      });
    
      response.on('end', function () {
        console.log(str);
      });
    });
    req.end();
};

우선 VPC 설정을 합니다. 아래 그림은 VPC와 서브넷의 설정 화면이다. 여러분이 원하는 대상을 지정하면 됩니다.

이제 다음으로 중요한 것은 프라이빗 서브넷에 NAT를 설정하는 것입니다. 손쉽게 설정하는 방법은 AWS의 NAT Gateway를 이용합니다. NAT Gateway는 VPC 메뉴 중에 존재하며, 아래 그림은 새롭게 NAT Gateway를 생성하는 화면입니다. 여기서 원하는 프라이빗 서브넷을 선택해서 생성합니다.

생성한 NAT Gateway를 활용하기 위해서 Route Tables에서 라우팅 정보를 추가하면 모든 설정은 끝납니다. 아래 그림은 라우팅 정보를 세팅한 모습입니다.

이제 테스트를 진행할 차례입니다. 람다 함수에도 테스트 기능이 존재하며, Req1() 함수만을 활성화 한 후에 테스트를 해봅시다. 람다 함수의 테스트 버튼을 눌러 하단의 Log output 화면에서 아래와 같은 결과가 나오면 성공한 것이다.

AWS Lambda의 VPC 지원 기능은 사용자에게 보안과 유연성이라는 두 장점을 같이 제공합니다. 더 자세한 사항은 Configuring a Lambda Function to Access Resources in an Amazon VPCVPC NAT Gateway 설명서를 참고하세요.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 박선용 솔루션즈 아키텍트께서 작성해주셨습니다.

고성능 애플리케이션 제작을 위한 AWS C++ SDK 커스터마이징

게임과 같은 고성능 애플리케이션 제작의 경우, 가상머신(VM)기반의 언어 사용시 성능상 제약이 있기에 C++와 같은 네이티브 바이너리를 직접 생성하는 언어를 쓰게 됩니다. C++의 경우 가비지 컬렉션(Garbage Collection)기능이 없는 언어이기 때문에 사용자가 직접 메모리 관리를 해주어야 합니다. 빈번히 메모리를 할당(new)하고 해제(delete)하는 일은 실행 시간 비용 측면에서도 상당히 비싼 편에 속합니다.

그래서, 고성능을 요구하는 게임 애플리케이션의 경우, 빈번한 메모리 할당과 파편화(memory fragmentation) 문제를 막기 위해서 메모리 풀링(pooling)을 주로 사용하게 됩니다. 이러한 기능을 제공하는 잘 알려진 tcmalloc이나 jemalloc과 같은 범용 메모리 관리자를 사용하는 경우가 많지만, 게임 엔진이나 서버 프레임워크의 경우에는 자체적으로 메모리 관리자를 제공하는 경우가 많습니다. 이러한 환경에서 AWS C++ SDK를 사용하기 위해서는 별도의 커스터마이징 작업이 필요합니다. 그래서, 지난 글에 이어 이번에는 AWS C++ SDK를 사용자의 상황에 맞게 커스터마이징하는 방법에 대해 다루겠습니다.

AWS C++ SDK는 메모리 관리자뿐만 아니라 HTTP 클라이언트, 작업 실행용 스레드(worker thread), 로그 에이전트(log agent)등 여러 부분에 대해 직접 커스터마이징 할 수 있는 인터페이스를 제공합니다.  이번 글에서는 커스텀 메모리 관리자 구현 방법 및 작업 실행을 위한 커스텀 스레드풀(worker thread pool)을 사용하는 방법에 대해 설명 드리겠습니다.

1. 커스텀 메모리 관리자 사용을 위한 AWS C++ SDK 빌드
커스텀 메모리 관리자를 사용하기 위하여 AWS C++ SDK를 동적 라이브러리 형태(DLL)로 빌드해 보도록 하겠습니다.  시작하기 전에 AWS C++ SDK의 코드를 최신 상태로 업데이트 하시기 바랍니다. 이후 AWS C++ SDK의 루트 폴더로 이동한 후, 빌드의 결과물이 생성될 폴더를 하나 만들고 cmake 명령을 통하여 Visual Studio용 솔루션 환경을 생성합니다. 동적 라이브러리 형태로 빌드하기 위해 이번에는 STATIC_LINKING 옵션을 주지 않습니다. 대신, 커스텀 메모리 관리자 사용을 알려주기 위하여CUSTOM_MEMORY_MANAGEMENT옵션을 주었습니다.

PS>  md sdk-dynamic-64
PS>  cd sdk-dynamic-64
PS> cmake .. -G "Visual Studio 14 2015 Win64" -DCUSTOM_MEMORY_MANAGEMENT=1 

위의 명령이 완료되면 sdk-dynamic-64 폴더 내에 AWS C++ SDK 빌드를 위한 Visual Studio 솔루션 파일이 생성되어 있을 것입니다. 솔루션 파일을 열고 빌드를 하기 전에, 커스텀 메모리 매니저를 사용하기 위해서 전처리 선언을 하나 추가해주어야 합니다. AWSMemory.cpp 파일에 대해 Property Pages를 열고 전처리 선언 부분에서USE_AWS_MEMORY_MANAGEMENT를 추가한 후에 빌드를 하시기 바랍니다. 빌드가 완료되면 해당 폴더 하위에 동적 라이브러리의 형태로 AWS C++ SDK가 생성이 됩니다. 생성된 DLL 파일들(예: aws-cpp-sdk-core.dll 등)은 추후에 애플리케이션 실행 시 필요하기에 따로 복사해 두시기 바랍니다.

<< AWSMemory.cpp에서 USE_AWS_MEMORY_MANAGEMENT 선언 추가 >>

2.커스텀 메모리 관리자를 사용하는 애플리케이션 작성
앞 단계에서 빌드한 AWS C++ SDK 동적 라이브러리를 사용하는 애플리케이션(ddb-sample: DynamoDB에 테이블을 생성하고 아이템을 대량으로 추가하고 읽어오는 프로그램)을 작성해보도록 하겠습니다.  프로그램 본체에 해당하는 main.cpp를 작성하면 다음과 같습니다. (동작 과정에 대한 자세한 내용은 주석을 참고)

#include <aws/core/Aws.h>
#include <aws/core/client/AsyncCallerContext.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/client/CoreErrors.h>
#include <aws/core/http/HttpTypes.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/ratelimiter/DefaultRateLimiter.h>
#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/DynamoDBErrors.h>
#include <aws/dynamodb/model/CreateTableRequest.h>
#include <aws/dynamodb/model/DeleteTableRequest.h>
#include <aws/dynamodb/model/DescribeTableRequest.h>
#include <aws/dynamodb/model/ListTablesRequest.h>
#include <aws/dynamodb/model/UpdateTableRequest.h>
#include <aws/dynamodb/model/PutItemRequest.h>
#include <aws/dynamodb/model/GetItemRequest.h>
#include <aws/dynamodb/model/ScanRequest.h>
#include <aws/dynamodb/model/UpdateItemRequest.h>
#include <aws/dynamodb/model/DeleteItemRequest.h>
#include <aws/core/utils/threading/Executor.h>

// 커스텀 메모리 관리자
#include "MyCustomMemoryManager.h"

using namespace Aws;
using namespace Aws::Auth;
using namespace Aws::Http;
using namespace Aws::Client;
using namespace Aws::DynamoDB;
using namespace Aws::DynamoDB::Model;


// 생성할 테이블 이름 및 파티션 키 설정
static const char* HASH_KEY_NAME = "HashKey1";
static const char* SIMPLE_TABLE = "TestSimpleTable1";
static const char* ALLOCATION_TAG = "DynamoDbTest";

class DynamoDbTest
{
public:

	// 테스트용 클라이언트 생성
	void SetUpTest()
	{
		auto limiter = Aws::MakeShared<Aws::Utils::RateLimits::DefaultRateLimiter<>>(ALLOCATION_TAG, 200000);
	
		ClientConfiguration config;
		config.scheme = Scheme::HTTPS;
		config.connectTimeoutMs = 30000;
		config.requestTimeoutMs = 30000;
		config.readRateLimiter = limiter;
		config.writeRateLimiter = limiter;
		config.region = Region::AP_NORTHEAST_2;

		m_client = Aws::MakeShared<DynamoDBClient>(ALLOCATION_TAG, config);
	}
	
	// 테이블 생성 테스트
	void CreateTableTest()
	{
		CreateTableRequest createTableRequest;
		AttributeDefinition hashKey;
		hashKey.SetAttributeName(HASH_KEY_NAME);
		hashKey.SetAttributeType(ScalarAttributeType::S);
		createTableRequest.AddAttributeDefinitions(hashKey);
		KeySchemaElement hashKeySchemaElement;
		hashKeySchemaElement.WithAttributeName(HASH_KEY_NAME).WithKeyType(KeyType::HASH);
		createTableRequest.AddKeySchema(hashKeySchemaElement);
		ProvisionedThroughput provisionedThroughput;
		provisionedThroughput.SetReadCapacityUnits(10);
		provisionedThroughput.SetWriteCapacityUnits(10);
		createTableRequest.WithProvisionedThroughput(provisionedThroughput);
		createTableRequest.WithTableName(SIMPLE_TABLE);

		CreateTableOutcome createTableOutcome = m_client->CreateTable(createTableRequest);
		if (createTableOutcome.IsSuccess())
		{
			printf("[OK] CreateTable: %s\n", createTableOutcome.GetResult().GetTableDescription().GetTableName().c_str());
		}
		else
		{
			assert(createTableOutcome.GetError().GetErrorType() == DynamoDBErrors::RESOURCE_IN_USE);
		}

		// 테이블이 생성될 때까지 기다림
		DescribeTableRequest describeTableRequest;
		describeTableRequest.SetTableName(SIMPLE_TABLE);

		DescribeTableOutcome outcome = m_client->DescribeTable(describeTableRequest);

		while (true)
		{
			assert(outcome.IsSuccess());
			if (outcome.GetResult().GetTable().GetTableStatus() == TableStatus::ACTIVE)
			{
				break;
			}
			else
			{
				std::this_thread::sleep_for(std::chrono::seconds(1));
			}

			outcome = m_client->DescribeTable(describeTableRequest);
		}
	}

	// 테이블 삭제 테스트
	void DeleteTableTest()
	{
		DeleteTableRequest deleteTableRequest;
		deleteTableRequest.SetTableName(SIMPLE_TABLE);

		DeleteTableOutcome deleteTableOutcome = m_client->DeleteTable(deleteTableRequest);

		if (!deleteTableOutcome.IsSuccess())
		{
			assert(DynamoDBErrors::RESOURCE_NOT_FOUND == deleteTableOutcome.GetError().GetErrorType());
		}
		else
		{
			printf("[OK] DeleteTable: %s\n", deleteTableOutcome.GetResult().GetTableDescription().GetTableName().c_str());
		}
	}

	// 테이블에 아이템을 집어넣고 읽기 테스트 (비동기 방식으로)
	void PutAndGetItemAsyncTest()
	{
		auto putItemHandler = std::bind(&DynamoDbTest::PutItemOutcomeReceived, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
		auto getItemHandler = std::bind(&DynamoDbTest::GetItemOutcomeReceived, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);

		/// 50개의 아이템을 비동기로 생성 요청
		Aws::String testValueColumnName = "TestValue";
		Aws::StringStream ss;

		for (int i = 0; i < 50; ++i)
		{
			ss << HASH_KEY_NAME << i;
			PutItemRequest putItemRequest;
			putItemRequest.SetTableName(SIMPLE_TABLE);
			AttributeValue hashKeyAttribute;
			hashKeyAttribute.SetS(ss.str());
			ss.str("");
			putItemRequest.AddItem(HASH_KEY_NAME, hashKeyAttribute);
			AttributeValue testValueAttribute;
			ss << testValueColumnName << i; 
                        testValueAttribute.SetS(ss.str()); 
                        putItemRequest.AddItem(testValueColumnName, testValueAttribute); 
                        ss.str(""); 
                        m_client->PutItemAsync(putItemRequest, putItemHandler);
		}

		// 모든 작업이 완료될 때까지 기다림
		std::unique_lock putItemResultLock(putItemResultMutex);
		putItemResultSemaphore.wait(putItemResultLock);

		// 기록된 아이템들을 다시 읽어와보기
		for (int i = 0; i < 50; ++i)
		{
			GetItemRequest getItemRequest;
			ss << HASH_KEY_NAME << i;
			AttributeValue hashKey;
			hashKey.SetS(ss.str());
			getItemRequest.AddKey(HASH_KEY_NAME, hashKey);
			getItemRequest.SetTableName(SIMPLE_TABLE);

			Aws::Vector attributesToGet;
			attributesToGet.push_back(HASH_KEY_NAME);
			attributesToGet.push_back(testValueColumnName);
			ss.str("");
			m_client->GetItemAsync(getItemRequest, getItemHandler);
		}

		std::unique_lock getItemResultLock(getItemResultMutex);
		getItemResultSemaphore.wait(getItemResultLock);

		for (int i = 0; i < 50; ++i)
		{
			GetItemOutcome outcome = getItemResultsFromCallbackTest[i];
			assert(outcome.IsSuccess());

			GetItemResult result = outcome.GetResult();
			auto returnedItemCollection = result.GetItem();

			printf("%s\n", returnedItemCollection[testValueColumnName].GetS().c_str());
		}

	}


public:
	// 아이템 집어넣기에 관한 핸들러
	void PutItemOutcomeReceived(const DynamoDBClient* sender, const PutItemRequest& request, const PutItemOutcome& outcome, const std::shared_ptr<const AsyncCallerContext>& context)
	{
		std::lock_guard locker(putItemResultMutex);
		putItemResultsFromCallbackTest.push_back(outcome);

		if (putItemResultsFromCallbackTest.size() == 50)
		{
			putItemResultSemaphore.notify_all();
		}
	}

	// 아이템 읽어오기에 관한 핸들러
	void GetItemOutcomeReceived(const DynamoDBClient* sender, const GetItemRequest& request, const GetItemOutcome& outcome, const std::shared_ptr<const AsyncCallerContext>& context)
	{
		std::lock_guard locker(getItemResultMutex);
		getItemResultsFromCallbackTest.push_back(outcome);

		if (getItemResultsFromCallbackTest.size() == 50)
		{
			getItemResultSemaphore.notify_all();
		}
	}


private:

	std::shared_ptr<DynamoDBClient> m_client;

	std::mutex putItemResultMutex;
	std::condition_variable putItemResultSemaphore;

	std::mutex getItemResultMutex;
	std::condition_variable getItemResultSemaphore;

	Aws::Vector<PutItemOutcome> putItemResultsFromCallbackTest;
	Aws::Vector<GetItemOutcome> getItemResultsFromCallbackTest;
};


int main()
{
	// 커스텀 메모리 관리자 선언
	MyCustomMemorySystem memorySystem;
	
	// SDK를 사용하기 위한 초기화
	Aws::SDKOptions options;
	options.memoryManagementOptions.memoryManager = &memorySystem;
	options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
	Aws::InitAPI(options);


	// DynamoDB Test Sample
	{
		DynamoDbTest test;
		test.SetUpTest();
		
		// 테이블 생성 테스트 
		test.CreateTableTest();

		// 아이템 put/get 테스트
		test.PutAndGetItemAsyncTest();

		// 테이블 삭제 테스트
		test.DeleteTableTest();
	}


	// SDK에서 사용하는 내부 자원 해제
	Aws::ShutdownAPI(options);
    return 0;  
}

main함수의 첫 부분에서 커스텀 메모리 관리자를 사용하고 있음을 확인할 수 있습니다. 커스텀 메모리 관리자의 구현을 위해서는 AWS C++ SDK의Aws::Utils::Memory::MemorySystemInterface를 상속받아 구현하여야 합니다. 위의 코드에서 사용된 MyCustomMemoryInterface는 다음과 같은 방식으로 구현할 수 있습니다.

// MyCustomMemoryManager.h

#include <aws/core/utils/memory/MemorySystemInterface.h>
#include <aws/core/utils/memory/AWSMemory.h>

class MyCustomMemorySystem : public Aws::Utils::Memory::MemorySystemInterface
{
public:
	MyCustomMemorySystem() {}
	virtual ~MyCustomMemorySystem() {}

	// 커스텀 메모리 매니저 시작과 끝에서 불리는 함수. 필요시 구현할 것
	virtual void Begin() override {}
	virtual void End() override {}

	// 할당(new)시 불리는 AllocateMemory 인터페이스 구현
	virtual void* AllocateMemory(std::size_t blockSize, std::size_t alignment, const char *allocationTag = nullptr) override
	{
		// 여기에 메모리 할당 로직 직접 구현
		// 예: tcmalloc을 사용한다면, tc_malloc(blocksize);
		// 예: UnrealEngine에서 제공하는 할당자를 사용한다면, FMemory::Malloc()

		return _aligned_malloc(blockSize, alignment);
	}

	// 해제(delete)시 불리는 FreeMemory 인터페이스 구현
	virtual void FreeMemory(void* memoryPtr) override
	{
		// 여기에 메모리 해제 로직 직접 구현
		// 예: tcmalloc을 사용한다면, tc_free(memoryPtr)
		// 예: UnrealEngine의 경우는, FMalloc::Free()

		_aligned_free(memoryPtr);
	}

};

이런 방식으로 AWS C++ SDK에서 제공하는 메모리 시스템 인터페이스를 구현(override)하여 사용하면, 시스템 전역적인 기본 메모리 할당자 대신, 여러분이 직접 지정한 커스텀 메모리 관리자를 사용할 수 있습니다.  위의 샘플 코드에서는 직접 AllocateMemory 및 FreeMemory 멤버 함수를 구현한 내용을 담고는 있지는 않습니다만, Windows환경에서 제공하는 API를 이용한 고성능 커스텀 메모리 관리자의 구현 예제는 AWSTestSample의 LockFreeMemorySystem.h/.cpp 파일을 참고하시기 바랍니다.

그런데, 커스텀 메모리 관리자를 사용할 경우 주의할 점이 있습니다. AWS C++ SDK라이브러리로 (API 호출시) 전달되는 STL 객체의 경우, 표준 STL 컨테이너(std::vector 등)를 그대로 사용하면 안된다는 것입니다. 표준 STL 컨테이너는 기본적으로 시스템 전역 할당자(malloc/free)를 사용하기 때문입니다. 그래서, 커스텀 메모리 관리자를 사용하도록 (표준 STL 타입들을 wrapping한) 수정된STL 타입을 제공하고 있습니다. Aws::Vector, Aws::Map, Aws::String 등이 그 예입니다.  자세한 사용 예는 위의 샘플 코드(main.cpp)에서 확인하실 수 있습니다.

3. 애플리케이션을 위한 Visual Studio 솔루션 생성 및 실행
이전 단계에서 작성한 ddb-sample 애플리케이션용 Visual Studio 솔루션을 생성하기 위한 CMakeLists.txt를 작성하면 다음과 같습니다. 지난번 글과는 다르게AWS C++ SDK를 동적 라이브러리 형태로 사용하기 위해서 USE_IMPORT_EXPORT 선언을 추가하였습니다.

cmake_minimum_required(VERSION 2.8)
project(ddb-sample)
find_package(aws-sdk-cpp)
add_definitions(-DUSE_IMPORT_EXPORT)
add_executable(ddb-sample main.cpp MyCustomMemoryManager.h)
target_link_libraries(ddb-sample aws-cpp-sdk-dynamodb)

CMakeLists.txt 작성이 완료되었다면, 아래의 명령을 통하여 ddb-sample 애플리케이션용 Visual Studio 솔루션을 생성합니다.

PS > md ddb-sample
PS > cd ddb-sample
PS >  cmake –Daws-sdk-cpp_DIR=..\sdk-dynamic-64 -G "Visual Studio 14 2015 Win64" 

Ddb-sample 폴더 내에 생성되어 있는 솔루션(.sln)파일을 열어서 빌드를 하고 실행해보시기 바랍니다. (이전AWS C++ SDK 빌드 단계에서 생성된DLL파일이 필요합니다.) 테이블이 생성되고 50개의 아이템이 생성되는 것을 직접 확인하실 수 있습니다. 이 과정에서 커스텀 메모리 매니저의 사용 여부를 알고 싶다면, MyCustomMemorySystem클래스의 AllocateMemory/FreeMemory 멤버함수에 중단점(breakpoint)을 걸어보시기 바랍니다.

4. 커스텀 스레드 풀을 사용하여 애플리케이션 최적화하기
위의 ddb-sample 애플리케이션에서는 DynamoDB에 아이템 생성(put)을 요청할 때, 비동기 방식으로 하게 됩니다.  즉, AWS C++ SDK의 요청 API중 “Async”라는 접미어 (예: DynamoDBClient::GetItemAsync)가 붙은 API를 통해 요청하는 경우는 그 결과가 모두 오기 전까지 기다리지 않습니다. 결과는 콜백 함수(callback handler)를 통해서 받아야 합니다.

이러한 비동기 방식의 요청에 대해서는 내부적으로 Executor(ClientConfiguration::executor를 통해 지정 가능)라는 작업 실행기를 통해서 요청을 수행하게 되고 수행이 완료되면 콜백 함수를 불러(invoke)주게 됩니다. 그런데, 명시적으로 Executor를 따로 지정해주지 않게 되면, 내부적으로 기본 실행기(DefaultExecutor)를 사용하게 됩니다. DefaultExecutor의 구현은 아래와 같은 형태로 되어 있습니다.

bool DefaultExecutor::SubmitToThread(std::function<void()>&&  fx)
{
    std::thread t(fx);
    t.detach();
    return true;
}

보시다시피, 요청마다 일일이 스레드 생성을 통하여 함수를 실행하도록 구현되어 있습니다. 위의 ddb-sample 애플리케이션의 경우에는 DynamoDB에 50개의 아이템 생성을 비동기로 요청하게 되어 있는데, 이 경우에는 50개의 스레드를 생성해서 처리하게 됩니다.  빈번한 메모리 할당 요청과 마찬가지로 빈번한 스레드 생성 요청의 경우도 운영체제 입장에서는 꽤 비싼 것이 사실이고 성능에 감소에 영향을 주게 됩니다.

그래서, AWS C++ SDK는 이러한 Executor를 직접 구현해서 교체(override)할 수 있도록 인터페이스를 제공할 뿐만 아니라 기본적으로 스레드 풀링(thread pooling)을 사용하는 PooledThreadExecutor도 따로 제공해 드립니다. 아래 코드는 ddb-sample 애플리케이션에서 기본 실행기(DefaultExecutor)대신 PooledThreadExecutor를 사용하는 방법을 보여줍니다. SetUpTest멤버 함수의 ClientConfiguration::executor에 새로운 Executor를 지정하기만 하면 됩니다.

// ddb-sample 테스트를 위한 클라이언트 설정 부분
DynamoDbTest::SetUpTest()
{
	ClientConfiguration config;
              // … … …
              // 10개의 스레드를 사용하는 스레드풀 executor 생성후 지정하는 예
	config.executor = Aws::MakeShared<Aws::Utils::Threading::PooledThreadExecutor>(ALLOCATION_TAG, 10);

	m_client = Aws::MakeShared<DynamoDBClient>(ALLOCATION_TAG, config);
}

물론, 사용자의 환경에 따라 직접 구현한 것을 사용하거나 사용중인 프레임워크, 라이브러리 등에서 제공하는 스레드풀을 사용하는 방법도 가능합니다. Windows 환경에서 성능상의 이점을 위하여Executor를 직접 구현하여 사용하는 예는 이곳의 LockFreeExecutor.h/.cpp 파일을 참고하시기 바랍니다.

성능이 크게 문제가 안되거나 가상머신(VM)위에서 동작하는 일반적인 애플리케이션의 경우, 필요한 SDK나 라이브러리를 패키지 관리자로부터 바로 얻어서 기본 설정 그대로 사용할 수 있는 경우가 많습니다. 그러나, C++을 사용하여 애플리케이션을 작성하는 이유의 상당수가 성능 문제인 경우가 많기에 기본 설정 그대로 사용하기 부적합한 경우가 많습니다.  그래서, 이번에 다룬 내용을 토대로 필요한 경우 얼마든지AWS C++ SDK를 사용자의 환경에 맞게 직접 커스터마이징하여 사용하시기 바랍니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 구승모 솔루션즈 아키텍트께서 작성해주셨습니다.

Amazon CloudFront를 활용한 동적 콘텐츠 전송 성능 개선하기

게임 서비스에 있어서 데이터 전송 성능은 매우 중요한 요소입니다. 성능 향상을 통해 더 많은 페이지 뷰, 풍부한 사용자 경험, 높은 전환율을 얻을 수 있으며, 이는 비지니스 성공과도 직결된 중요한 요소들이라고 볼 수 있습니다. 한 통계 자료에 따르면 페이지 로딩시의 1초 지연은 7%의 전환율 하락을 가져올 수 있다고 합니다.  뿐만 아니라, 글로벌 서비스에서 최종 사용자 경험을 향상 시키는데 있어서 콘텐츠 전송 성능은 매우 중요한 요소 입니다.

CDN을 통한 동적 콘텐츠 전송에 대한 이해
Amazon CloudFront는 AWS의 콘텐츠 전송 서비스(CDN) 로서, 다른 서비스들과 통합되어 최종 사용자에게 효과적으로 콘텐츠를 쉽고 빠르게 전송할 수 있습니다. CDN은 최종 사용자와 원본 저장소(Origin) 사이에서 프록시(Proxy) 역할을 하며,  전 세계 56개(2016년 6월 20일 기준) 엣지 로케이션(Edge Location)에 미리 캐시 함으로써 빠르게 사용자에게 콘텐츠 전송을 할 수 있습니다.

일반적으로 이미지나 동영상 파일 같은 정적 콘텐츠 전송을 위해서 Amazon CloudFront를 많이 활용하고 있지만, 동적 콘텐츠 전송을 위해 사용하는 것에 대해서는 몇 가지 이유로 고민을 합니다. 동적 콘텐츠는 대게 자주 변경되는 정보를 가지고 있거나, 사용자가 보낸 요청에 포함된 요인에 따라 그 내용이 변경되는 특성을 가지고 있습니다.

그래서 항상 최신의 콘텐츠를 가져오기 위해 TTL 을 0 으로 설정하여 원본 저장소에 매번 접속 하여야 하기 때문에, 엣지에 캐시를 함으로써 향상되는 콘텐츠 전송 가속의 효과가 미비하다고 생각되는 이유 때문입니다. 하지만, TTL 을 0으로 설정하여 매번 원본 저장소에 접속하여 동적 콘텐츠를 제공하더라도, CloudFront를 프록시로 사용함으로써 전송 성능을 향상 시킬 수 있습니다.

먼저 콘텐츠 전송 성능에 대해서 얘기하기 앞서, 요청에 대한 응답 시간을 어떤 요인들로 구분하여 생각할 수 있는지 알아보는게 도움이 될 것입니다.

데이터 전송 관점에서 성능 향상 방법
아래 그림에서 보시다시피 응답 시간은 간단히 DNS Lookup 시간과, 연결 수립 시간, 최초 바이트를 받기까지의 시간, 콘텐츠 다운로드 시간의 합으로 생각해 볼 수 있습니다.

최종 사용자가 요청을 보내면, 먼저 DNS Lookup 을 하게 됩니다. 이 후 대상 서버와 TCP handshakes/retries 등의 과정으로 연결을 수립하게 됩니다. 그리고 TTFB 로 알려져 있는 최초의 응답을 기다리게 되고, 그 이후 콘텐츠를 다운로드 합니다.

이 과정이 완료되면 요청에 대한 응답이 완료되었고, 콘텐츠가 전송 되었다고 볼 수 있습니다. 앞서 나열한 요인들을 포함한 응답 시간을 CloudFront는 어떻게 처리하여 콘텐츠 전송 성능을 향상 시킬까요?

  1. 최적화된 네트워크: Amazon CloudFront 엣지와 AWS 리전 사이의 네트워크는 지속적으로 모니터링되고 성능 및 가용성면에서 최적화 됩니다. 게다가 네트워크상에 문제가 발생할 경우 신속하게 감지 및 수정되고, 최종 사용자를 다른 네트워크 경로로 자동 라우팅하여 최종 사용자에 미치는 영향을 최소화 합니다. 이를 통해 더 적은 패킷 감소와 더 나은 성능을 기대할 수 있습니다.
  2. 원본과의 지속적인 연결 유지: IP 기반의 네트워크를 통해 TCP/IP 연결을 맺기 위해서는 TCP 3-way handshake 라는 과정을 거치게 됩니다. 이 과정에 필요한 패킷 사이즈는 그렇게 크지 않기 때문에 큰 오버헤드가 아닐 수 있습니다. 하지만 글로벌 서비스와 같은 경우 지리적으로 떨어져 있는 곳에서 최종 사용자가 요청을 하게 되고, 지리적인 요건으로 인한 높은 RTT(Round-Trip Time)로 인해 매우 큰 오버헤드가 될 수 있습니다. CloudFront는 엣지에서 오리진으로의 지속적인 연결을 유지 및 재사용 하여 대게 수백 밀리세컨드가 소요되는 연결 설정 시간을 줄여 줍니다. 글로벌 서비스에서는 물론, 초당 요청 건수가 많아지면 많아질수록 지속적인 연결 유지로 얻을 수 있는 콘텐츠 전송 속도 향상의 효과는 커집니다.
  3. TCP/IP 최적화: 네트워크의 대역폭은 다양한 이유로 시시때때로 바뀌게 됩니다. CloudFront의 모든 호스트에는 엣지와 최종 사용자 간의 시시때때로 변하는 네트워크 대역폭 사용을 최대화 하기 위해 TCP initcwnd(initial congestion window) 값이 최적화 되어 있습니다.
  4. Gzip 압축 사용: Amazon CloudFront가 제공하는 GZIP 압축을 사용한다면 전송되는 데이터의 크기를 줄여, 트래픽을 줄일 수 있습니다. 이를 통해, 데이터 전송은 더 짧은 시간 안에 완료할 수 있을 뿐만 아니라, 데이터 전송 비용도 절감할 수 있습니다. 특히나 동적 콘텐츠는 대부분 매우 높은 압축률을 보여주는 텍스트 형태이기에 압축을 통한 효율이 더 높습니다. 또, CloudFront 엣지에서 압축 작업을 수행함으로써 오리진에서 압축을 위해 자원을 사용할 필요가 없어지는 장점도 있습니다.

앞서 설명한 대부분의 기능과 방법들은 CloudFront 엣지와 오리진 사이의 최적화에 관련된 내용 입니다. 최종 사용자와 엣지 사이의 최적화는 어떨까요? CloudFront는 지속적으로 지연 시간을 측정하여 최종 사용자가 항상 더 나은 경험을 할 수 있는 엣지로 연결을 하도록 합니다. 그리고, 여러 리전에 오리진을 배치하여 실제 물리적 거리를 줄일 수 있을 경우 Amazon Route 53의 LBR(Latency Based Routing)을 사용하여 최종 사용자로부터 최적의 리전에 있는 오리진에서 콘텐츠가 전송될 수 있도록 합니다.

지금까지 설명한 내용으로 앞서 언급한 응답 시간 관여 요인들을 최적화 하여 응답 시간을 줄일 수 있습니다.

  • DNS Lookup 시간: CloudFront의 라우팅 기술, Route 53과의 통합
  • 연결 수립 시간: CloudFront의 지속적인 연결 유지
  • 최초의 응답 시간(TTFB): CloudFront의 지속적인 연결 유지, Gzip 압축
  • 콘텐츠 다운로드 시간: CloudFront의 TCP/IP 최적화, Gzip 압축

CloudFront를 활용한 동적 콘텐츠 전송 테스트
Amazon CloudFront가 제공하는 응답 시간을 줄이기 위한 다양한 방법들을 살펴보았습니다. 이를 통해 어느 정도 응답 시간을 줄일 수 있는지는 간단한 테스트로 확인할 수 있습니다.

아래는 Amazon CloudFront를 활용한 동적 콘텐츠 전송 테스트 시나리오입니다.

  • 미국 동부(버지니아)에 있는 사용자가 한국에 있는 개인 서버로 접속하여, 1.7KB 정도의 동적 콘텐츠를 요청
  • CloudFront 엣지와 오리진과의 네트워크 성능에 집중하기 위해, 모든 캐시는 비활성
  • 마찬가지로 네트워크 성능에 집중하기 위해, 요청을 보낸 후 응답을 받은 뒤, 곧바로 다시 요청을 보내는 방법으로 진행 (Python 스크립트 사용)
  • 1일 03:09:00 초 ~ 5일 19:29:30 초 까지 5일 가까운 기간 동안 테스트 진행
  • 테스트1(빨간색 라인): 버지니아 EC2 -> CloudFront (No-cache, TTL 0) -> 오리진 (서울, 개인 서버, Node.js)
  • 테스트 2(파란색 라인): 버지니아 EC2 -> 오리진 (서울, 개인 서버, Node.js)

아래는 저의 인터넷 환경에서 얻은 결과입니다. (여러분의 환경과 설정 값들로 인해, 결과는 다를 수 있음을 유의해 주시기 바랍니다.)

횟수 평균 최소 최대 90백분위 10백분위 > 1초
CloudFront 1,902,091번 0.212초 0.187초 9.605초 0.209초 0.197초 40번
직접 연결 1,027,763번 0.393초 0.365초 7.415초 0.403초 0.384초 118번

동일한 기간 동안 테스트를 진행하였고, 그 결과를 확인해보면, CloudFront를 사용하였을 때 87만번 이상의 요청을 더 처리하였으며, 응답 시간은 직접 연결했을 때 보다 평균 181ms로 이는 약 1.8배 정도 더 빠른 결과 입니다. 또한, CloudFront를 사용하였을 때 1초 이상의 응답 지연은 직접 연결한 것에 비해 34% 정도 밖에 되지 않았습니다. 이를 통해, 직접 연결 보다 CloudFront를 활용하였을 때 더욱 빠르고 안정적인 서비스가 가능할 수 있음을 알 수 있습니다.

추가로 미국 서부(오레곤) 리전의 EC2에서 요청을 보냈을 때, 지리적인 이점으로 인해 더 낮은 평균 응답 시간을 얻을 수 있었습니다.

  • 테스트1: 오레곤 EC2 -> CloudFront (No-cache, TTL 0) -> 오리진 (서울, 개인서버, Node.js)
  • 테스트2: 오레곤 EC2 -> 오리진 (서울, 개인서버, Node.js)

아래는 저의 인터넷 환경에서 얻은 결과입니다. (여러분의 환경과 설정 값들로 인해, 결과는 다를 수 있음을 유의해 주시기 바랍니다.)

횟수 평균 최소 최대 90백분위 10백분위 > 1초
CloudFront 2,689,804번 0.150초 0.129초 5.223초 0.191초 0.134초 52번
직접 연결 1,642,063번 0.249초 0.236초 3.700초 0.248초 0.241초 90번

간단한 테스트를 통하여 Amazon CloudFront를 사용하면 동적 콘텐츠 전송을 가속할 수 있음을 알 수 있었습니다. 이러한 동적 콘텐츠를 위해서 캐시 기능을 사용할 수 있다면 어떨까요? 분명 더 나은 콘텐츠 전송 속도는 물론 원본 저장소 자원 사용도 줄일 수 있을 것 입니다.

동적 콘텐츠에 대한 캐싱을 통한 성능 향상하기
웹 애플리케이션 및 게임 서비스 등에서 제공하는 다양한 기능들에서 동적 콘텐츠 임에도 불구하고 일정 기간 동안은 갱신되지 않아도 크게 영향이 없는 것들을 찾을 수 있습니다. 이런 동적 콘텐츠들은 짧게는 수십초부터 길게는 수일까지 내용을 갱신할 필요가 없으며, 캐싱을 통하여 더 빠르게 전송될 수 있습니다.

아래는 동적 콘텐츠를 캐싱할 수 있을만한 애플리케이션 및 게임 서비스 내의 기능들의 예 입니다.

  1. 리더보드: http://www.example.com/leaderboards/?page=50&lowest=true – 리더 보드는 소셜 기능을 포함하는 많은 서비스들, 특히 게임에서 많이 요구되는 기능입니다. 게임을 예를 들면, 게임 플레이어들이 전투와 같은 게임 내 활동을 통해 점수가 업데이트 되고, 백그라운드에서 순위가 집계 및 계산이 된 후, 그 결과가 갱신되어, 리더보드에서 순위 정보가 표시됩니다. 게임과 같이 경쟁을 요하는 서비스에서 최종 사용자로부터 자주 요청되는 콘텐츠 입니다. 만약 이 순위 정보를 즉각 계산하고 갱신하고자 한다면 많은 자원을 필요로 할 것입니다. 하지만, 순위 정보는 항상 최신일 필요성이 적다는 점을 감안하면, CloudFront에 캐싱 하여 더 빠르게 응답을 처리하는 동시에 오리진의 자원을 효율적으로 사용할 수 있을 것입니다.

    <게임Castle Clash의 리더보드. 매일 자정 리더보드 갱신>
  2. 통계 정보: http://www.example.com/stats/player1/ – 최종 사용자는 추가적인 정보를 얻기 위해서 통계 정보를 요청합니다. 이 통계 정보는 특정한 활동에 대한 집계 일 수도 있으며, 최종 사용자의 활동으로부터 예측한 정보일 수도 있습니다. 이러한 정보는 최종 사용자의 지난 정보들을 수집한 뒤 생성되는 것으로서 자주 요청될 필요성이 적습니다. CloudFront 의 캐시 기능과 보다 긴 TTL 설정을 통하여 더 빠르게 정보를 제공할 수 있습니다.

    <World of Warships 포럼의 게임 플레이어 통계 정보>
  3. 각종 리플레이 기반의 기능: http://www.example.com/replay/player1/20/ – 게임 내에서 전투와 같은 활동은 추후 다시 재현하기 위해 리플레이로 저장할 수 있습니다. 리플레이는 일반적으로 게임 플레이어와 상대방(AI 혹은 다른 게임 플레이어)이 언제 어떤 활동 및 명령을 수행 했는지에 대한 순차적인 데이터들의 모음입니다. 이런 데이터를 기반으로 멀티플레이어 게임과 같은 기능을 구현할 수 있습니다. 리플레이 데이터는 한번 생성된 후 수정이 필요하지 않은 정적 콘텐츠의 특징을 가지고 있으며 CloudFront 의 캐시 기능을 백분 활용할 수 있습니다.

    <게임 Drag Racing Classic의 멀티플레이어 화면>

뿐만 아니라 VarnishSquid 등의 Caching Proxy를 CloudFront와 함께 사용한다면 더 세밀하고, 더 효율적으로 동적 콘텐츠를 캐싱할 수 있습니다.

지금까지 Amazon CloudFront를 활용하여 동적 콘텐츠의 전송을 가속 할 수 있는 방법을 알아 보았습니다. Amazon CloudFront가 제공하는 비용 효율성과 동적 콘텐츠 가속을 위한 기능들을 사용하여 보다 쉽고 빠르게 콘텐츠를 전송하여 최종 사용자 경험 향상은 물론 인프라의 부하도 줄일 수 있습니다. 이외에도 Amazon Route 53, Amazon S3 등의 AWS 서비스와 파트너 솔루션과의 통합으로도 성능 및 가용성을 향상 시킬 수 있습니다.

더 자세한 정보는 Amazon CloudFront – 동적 콘텐츠 전송 페이지와 Amazon CloudFront 개발자 안내서를 참고하시기 바랍니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김필중 솔루션즈 아키텍트께서 작성해주셨습니다.

윈도 Visual Studio 환경에서 AWS C++ SDK 활용하기

게임제작에 있어서 클라이언트 개발은 물론 서버 개발의 경우도 윈도 환경 하에서 C++을 사용하여 게임을 만드는 경우가 많을 것입니다. 온라인 게임뿐만 아니라 모바일 게임의 경우도 Cocos2d-x엔진을 쓰거나 Unreal Engine을 사용한다면 이와 같은 환경에서 개발하는 경우가 대부분일 것입니다.

AWS는 지난해 9월 AWS C++ SDK를 공개하였고, 이를 게임 엔진이나 게임서버에서 사용하게 되면 AWS의 다양한 서비스들을 게임에서 직접 활용하고 제어할 수 있습니다.

AWS C++ SDK의 경우, 타 언어 AWS SDK와는 다르게 설치 프로그램(installer)나 패키지 관리자(Nuget 등)를 통해서 설치하는 방법을 제공하지 않기 때문에 직접 소스 코드로부터 빌드하여 사용하여야 합니다. 게임 개발자들이 많이 쓰는 환경인 Windows 및 Visual Studio에서 AWS C++ SDK를 바로 빌드하여 사용하기에는 주의해야 할 점들이 존재합니다. 이 글에서는여러분이 AWS C++SDK을 Windows 환경의 Visual Studio 사용 단계 및 알아두시면 좋은 점 위주로 설명 드리고자 합니다.

1. 사전 준비 작업
AWS C++ SDK를 Windows 환경에서 빌드하기 위해서는 Visual Studio뿐만 아니라 플랫폼 독립적인 빌드 환경 생성 도구인 CMAKE가 필요합니다. 또한, AWS C++ SDK를 받기 위해서는 GIT 클라이언트도 미리 준비해두시기 바랍니다. 마지막으로, AWS상의 자원(S3 등)를 제어하기 위해서는 그에 맞는 권한이 있어야 합니다. 해당 권한을 갖는 IAM 사용자를 만드신 후, Access Key 및 Secret Key를 미리 설정해놓으시기 바랍니다. AWS C++ SDK가 자동으로 해당 Access Key를 검색하는 과정은 다음과 같습니다.

  1. 환경 변수 검색
    1. AWS_ACCESS_KEY_ID = < Access Key 값 >
    2. AWS_SECRET_ACCESS_KEY = < Secret Key 값 >
    3. AWS_DEFAULT_REGION= < 기본적으로 사용할 AWS리전, (예) ap-northeast-2 >
  2. 사용자 HOME디렉토리 내의 .aws 폴더 아래의 credentials 파일 검색
    1. 이 파일은 AWS CLI의 “aws configure”명령을 통해 생성이 가능합니다.
  3. EC2 인스턴스 위에서 동작하는 경우 기본적으로 EC2MetadataInstanceProfile을 검색
    1. 해당 권한을 갖고 있는 IAM Role이 있는지 찾음

본 글의 예시에서는 Visual Studio 2015 버전 및 CMAKE 3.x 버전 사용을 가정하고 예를 들겠습니다.  사전 준비를 마쳤다면, Github에서 AWS C++ SDK를 다음 명령을 통해 내려 받습니다.

PS > git clone https://github.com/aws/aws-sdk-cpp <sdk-root-folder>

명령 수행이 완료되면, 지정한 <sdk-root-folder>내에 AWS C++ SDK 소스 코드가 다운됩니다.

2. AWS C++ SDK를 정적 라이브러리 형태로 빌드하기
이제 본격적으로 AWS C++ SDK를 빌드해 보도록 하겠습니다. 우선 빌드 결과물(라이브러리 파일 등)이 생성될 폴더를 하나 만듭니다. 본 예제에서는 <sdk-root-folder>하위에 sdk-build-64 폴더를 만들고 이곳에서 빌드를 해보도록 하겠습니다. 아래 cmake명령은 <sdk-root-folder>에 있는 AWS C++ SDK의 CMakeLists.txt 파일을 참조하여Visual Studio 2015 버전 형태의 솔루션(.sln)파일 생성하여 줍니다. 정적 라이브러리 형태로 빌드하기 위해STATIC_LINKING 옵션을 주었습니다.

PS > md sdk-build-64
PS > cd sdk-build-64
PS > cmake .. -G "Visual Studio 14 2015 Win64" -DSTATIC_LINKING=1

참고로, -G옵션은 Visual Studio 솔루션 형태의 프로젝트 생성뿐만 아니라 다양한 형태의 프로젝트 파일을 생성(generation)할 수 있도록 해줍니다. 구체적으로 지원하는 형태는 cmake –G를 실행해보시면 확인하실 수 있습니다. STATIC_LINKING 옵션을 주지 않으면 기본적으로 동적 라이브러리(DLL)형태로 빌드가 되도록 구성 됩니다. (AWS C++ SDK를 동적 라이브러리 형태로 사용할 경우는 몇 가지 주의할 사항이 있습니다. 이 부분에 대해서는 아래에 따로 설명하겠습니다.)

위의 명령이 완료되면 sdk-build-64 폴더 내에 AWS C++ SDK 빌드를 위한 솔루션 파일이(aws-sdk-cpp-all.sln) 생성되어 있을 겁니다. 이 파일을 Visual Studio에서 열고 빌드를 하시면 됩니다.  빌드가 완료되면 sdk-build-64의 하위 디렉토리에 AWS C++ SDK의 라이브러리(.lib)파일들이 생성됩니다.

만일, 소스 코드 파일의 인코딩 타입 문제로 빌드가 되지 않는다면, 해당 코드 파일을 열어서 UTF-8 형태로 다시 저장하시기 바랍니다. Visual Studio에서 에러가 발생한 파일을 열고 File 메뉴에서Advanced Save Options항목을 선택한 다음에 “Unicode (UTF-8 with signature)”항목을 선택하고 저장하시면 됩니다. UTF-8형태로 바꿔야 하는 파일이 많다면 아래의 Powershell 스크립트를 활용하면 소스코드를 일괄적으로 UTF-8형태로 바꾸어 저장할 수 있습니다.

Function  SaveAsUtf8([string] $path)
{
  [String[]] $files = Get-ChildItem $path -Recurse -Include *.h, *.cpp;
  foreach ($file  in $files)
    {
      "Saving As $file...";
       [String]$s = [IO.File]::ReadAllText($file);
       [IO.File]::WriteAllText($file, $s, [Text.Encoding]::UTF8);
     }
}

Windows에 기본적으로 포함된 Powershell을 열고 <sdk-root-folder>로 이동한 후 에서 위의 파일(save_as_utf8.ps1)에 정의된 함수를 다음과 같은 방법으로 실행하게 되면 <sdk-root-folder>하위의 모든 소스 코드를 UTF-8의 형태로 재 저장하게 됩니다.

PS > . .\save_as_utf8.ps1 
PS > SaveAsUtf8 .

3. 샘플 프로그램을 통해 AWS C++ SDK 사용해보기
앞서 빌드한 AWS C++ SDK를 직접 사용하는 간단한 C++ 프로그램(S3에 특정 문자열이 포함된 파일을 업로드)을 제작해보도록 하겠습니다.  우선 편의를 위하여 <sdk-root-folder>하위에 app-test라는 폴더를 만들고 이곳에 해당 프로그램을 위한 프로젝트 파일들을 cmake를 통하여 생성해보도록 합니다. 우선, 프로그램 본체에 해당하는 main.cpp를 작성하고, cmake를 통하여 Visual Studio 솔루션 파일 생성을 위한 CMakeLists.txt를 작성하면 다음과 같습니다.

#include <aws/core/Aws.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/core/utils/memory/stl/AwsStringStream.h> 

using namespace Aws::S3;
using namespace Aws::S3::Model;

static const char* KEY = "s3_my_sample_key3";
static const char* BUCKET = "s3-demo-korea";

int main()
{
    // SDK를 사용을 위한 내부 자원 초기화 및 옵션 설정 (지금은 default)
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    S3Client client;
    
    // s3에 put 요청 생성 
    PutObjectRequest putObjectRequest;
    putObjectRequest.WithKey(KEY).WithBucket(BUCKET);

    // 파일에 기록할 스트림 생성
    auto requestStream = Aws::MakeShared("s3-sample");
    *requestStream << "Hello World!";
    
    // 스트림을 put요청에 연결
    putObjectRequest.SetBody(requestStream);

    auto putObjectOutcome = client.PutObject(putObjectRequest);

    if (putObjectOutcome.IsSuccess())
    {
        std::cout << "Put object succeeded" << std::endl;
    }
    else
    {
        std::cout << "Error while putting Object " <<
               putObjectOutcome.GetError().GetExceptionName() << 
               " " << putObjectOutcome.GetError().GetMessage() << std::endl;
    }

    // S3에 기록된 파일 내용을 다시 GET해보는 요청 생성 
    GetObjectRequest getObjectRequest;
    getObjectRequest.WithBucket(BUCKET).WithKey(KEY);

    auto getObjectOutcome = client.GetObject(getObjectRequest);

    if(getObjectOutcome.IsSuccess())
    {
        std::cout << "Successfully retrieved object from s3 with value: " << std::endl;
        std::cout << getObjectOutcome.GetResult().GetBody().rdbuf() << std::endl 
        << std::endl;
    }
    else
    {
        std::cout << "Error while getting object " <<
             getObjectOutcome.GetError().GetExceptionName() <<
             " " << getObjectOutcome.GetError().GetMessage() << std::endl;
    }

    // SDK에서 사용된 내부 자원 해제
    Aws::ShutdownAPI(options);
    return 0;  
}

AWS C++ SDK를 사용하는 코드 작성시, main함수의 첫 부분에서 반드시”Aws::InitAPI(options);”를 호출하여SDK 초기화를 해주어야 합니다. 이 과정에서 SDK사용에 필요한 각종 옵션들(로깅 여부, 커스텀 메모리 관리자 설정, HTTP 클라이언트 설정 등)을 직접 지정할 수 있습니다. 사용할 수 있는 옵션에 관한 자세한 내용은 AWS C++ SDK 블로그를 참조하시기 바랍니다.

AWS C++ SDK를 사용하는 애플리케이션 코드를 작성하였다면, 이를 포함하는 Visual Studio 솔루션 파일을 생성하는 cmake용 프로젝트 파일(CMakeLIsts.txt)을 아래와 같이 생성하여야 합니다. 이 파일에서 라이브러리 의존성 관계를 설정할 수 있습니다.

cmake_minimum_required(VERSION 2.8)
# 생성될 프로젝트 이름 설정
project(s3-sample)

# 이 프로젝트에서 사용할 AWS C++ SDK 찾아서 의존성 설정
find_package(aws-sdk-cpp)

# 이 프로젝트에 실행파일로 생성될 대상 코드 지정
add_executable(s3-sample main.cpp)

#링크가 필요한 AWS C++ SDK 라이브러리 지정 (이 예에서는 S3관련 기능만 사용함)
target_link_libraries(s3-sample aws-cpp-sdk-s3)

CMakeList.txt를 작성하였다면 아래의 명령을 통하여 샘플 프로그램에 해당하는 Visual Studio용 솔루션 파일을 생성할 수 있습니다.

PS > md app-test
PS > cd app-test
PS > cmake -Daws-sdk-cpp_DIR=..\sdk-build-64 .  –G “Visual Studio 14 2015 Win64”

app-test 폴더 내에s3-sample.sln이 생성되어 있음을 확인하실 수 있습니다. Visual Studio로 s3-sample 프로젝트를 열어서 빌드를 하고 실행을 해보시기 바랍니다. 위의 main.cpp에서 작성한 코드가 제대로 실행되었는지 확인하기 위해AWS 콘솔에 접속하여 위의 코드에서 지정한 S3버킷 내에 파일이 생성되었는지 직접 확인해 보시기 바랍니다.

4. AWS C++ SDK 사용시 주의할 점
지금까지 AWS C++ SDK를 정적 라이브러리(static library) 형태로 빌드 하고 이를 사용하는 방법에 대해서 알아보았습니다. cmake를 통하여 AWS C++ SDK를 구성하는 과정에서 STATIC_LINKING 옵션을 주지 않으면 기본적으로 동적 라이브러리 형태로 AWS C++ SDK가 빌드가 되고, 추가적으로 DLL파일이 생성됩니다.

이 파일을 애플리케이션의 실행 파일과 함께 두거나 Windows시스템이 기본적으로 DLL파일을 찾는 폴더(Windows\System32 등)에 두어야 해당 애플리케이션이 실행이 됩니다.  또한, AWS C++ SDK를 사용하는 프로젝트의 속성 페이지에서 “USE_IMPORT_EXPORT” 전처리 선언을 다음과 같이 추가 해주어야 합니다.

또한, 동적 라이브러리의 형태로 AWS C++ SDK를 빌드하는 경우 자동으로 커스텀 메모리 관리자를 사용하도록 설정이 됩니다.  그 이유는 메모리 경계(boundary) 문제가 있기 때문입니다. 즉, AWS C++ SDK DLL내에서 할당한(new)메모리의 경우는 반드시 DLL내에서 해제(delete)되어야 하고, 애플리케이션(.EXE) 내에서 할당한 경우는 반드시 해당 애플리케이션 내에서 해제되어야 하기 때문입니다. 그런데, C++의 경우 STL컨테이너를 많이 활용하게 됩니다.

STL컨테이너 특성상 내부에서 자동으로 메모리가 할당되고 해제되는 경우가 많기 때문에, 애플리케이션과 라이브러리간 STL객체를 주고 받는다면, STL내부에서 일어나는 메모리 할당/해제를 명백히 제어하기 어렵습니다. 즉, 애플리케이션 내에서 자동 할당된 메모리가 DLL내부에서 해제되거나, 그 반대의 경우가 있을 수 있습니다. 그래서, 커스텀 메모리 관리자를 통하여 DLL 및 애플리케이션의 메모리 할당/해제가 한 곳에서 일어나게끔 일원화 할 수 있도록 한 것입니다.

커스텀 메모리 관리자는 다음과 같은 형식으로 “Aws::InitAPI()”시에 지정할 수 있습니다만, 동적 라이브러리 형태로 사용하는 경우 따로 커스텀 메모리 관리자를 명시적으로 구현하여 지정하지 않더라도 AWS C++ SDK에서 제공하는 기본(default) 커스텀 메모리 관리자가 자동으로 설정이 됩니다. 기본 커스텀 메모리 관리자는 메모리 할당/해제 시에 C++ 런타임 시스템의 기본 할당자(default new/delete)를 사용하게 되지만 위에서 설명한 DLL 경계 문제를 해결해 준다는 장점이 있습니다.

// 커스텀 메모리 관리자를 직접 구현하여 사용하는 경우,
// MemorySystemInterface를 상속받아 AllocateMemory및 FreeMemory 구현

class MyMemoryManager : public Aws::Utils::Memory::MemorySystemInterface
{
  public:
    // ...
    virtual void* AllocateMemory(std::size_t blockSize, std::size_t alignment,
                                 const char *allocationTag = nullptr) override;
    virtual void FreeMemory(void* memoryPtr) override;
};

// main함수의 첫 부분에서 다음과 같은 방식으로 사용
MyMemoryManager sdkMemoryManager;
Aws::SDKOptions options;
options.memoryManagementOptions.memoryManager = &sdkMemoryManager;
Aws::InitAPI(options);

사용자가 자체적으로 커스텀 메모리 관리자를 어떻게 구현하여 사용할 수 있는지에 관한 내용은 다음에 구체적으로 다루도록 하겠습니다.

추가적으로, 동적 라이브러리 형태로 사용하는 경우에 있어서, AWS C++ SDK (.DLL)를 빌드할 때 사용한 컴파일러 종류 및 버전과, 애플리케이션(.EXE)을 빌드할 때 사용한 컴파일러가 반드시 같아야 합니다. (이 예제의 경우 Visual Studio 2015, MSVC 14) 그 이유는 Application Binary Interface (ABI) 호환 문제가 발생할 수 있기 때문입니다.

동적 라이브러리 형태로 사용할 경우 이와 같이 고려해야 할 면들이 많습니다. 그래서, 특별한 경우(AWS C++ SDK DLL을 여러 프로세스에서 공유해서 사용하는 경우 또는 자체 구현한 커스텀 메모리 관리자 사용 등)가 아니면 정적 라이브러리의 형태로 빌드해서 사용하는 것을 추천합니다. (cmake를 통하여 정적 라이브러리로 구성해서 사용하는 경우는 자동으로 전처리 선언이 꺼지기 때문에 커스텀 메모리 매니저는 사용하지 않게 됩니다.)

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 구승모 솔루션즈 아키텍트께서 작성해주셨습니다.

EC2 전용 호스팅으로 자체 라이센스로 윈도 서버 구동하기

얼마전 서울 리전에 Amazon EC2 전용 호스팅(Dedicated Hosting) 기능을 공개 되었습니다. 이 기능은 고객 전용의 EC2 인스턴스 용량을 갖춘 물리적 서버입니다. 전용 호스팅을 사용하면 기존 서버에 한정된 소프트웨어 라이선스를 사용할 수 있으므로 규정 준수 요구 사항을 해결하면서 비용도 절감할 수 있습니다. (자세한 기능 소개를 참고 하시기 바랍니다.)

오늘은 여러분이 기존 회사나 집에 Microsoft Windows 의 라이센스를 가지고 있고, 그대로 AWS 상에서 사용을 하고자 할 경우, 이 때  소켓 혹은 물리적 코어에 한정된 라이센스 약관을 해결할 수 있는 방법으로 Amazon EC2 전용 호스팅 사용 방법을 알아보겠습니다.

기존 소프트웨어 라이선스에 약관에 따르면, Microsoft Windows Server, Microsoft SQL Server, SUSE Linux Enterprise Server, Red Hat Enterprise Linux  등은 소켓당, 코어당 또는 VM 소트프웨어당 라이선스를 사용할 수 있습니다.

여러분이 전용 호스팅 서버를 선택하면 하나의 물리적인 서버가 할당됩니다. 본인의 EC2 인스턴스를 이 전용 호스트에 올려 실행할 수 있습니다. 즉, 특정 물리 서버에 본인이 원하는 사이즈의 인스턴스를 직접 배치함으로써 제어 및 가시성 역시 강화할 수 있습니다. 금융 등 기업 규정 및 규제가 존재하는 곳에서는 그 요구사항을 충족하는 구성이 가능하게 된다는 점에서 중요한 부분이라 할 수 있습니다.

Amazon EC2 전용 호스트 생성
전용 호스팅으로 본인의 라이선스를 그대로 적용하려면 기존에 운영하던 서버를 그대로 옮겨와야 합니다. 여러분이 Microsoft Windows 서버를 가상환경에서 운영을 하고 있는 경우, 온프레미스 환경의 윈도우 서버 이미지를 VM Import 기능을 이용해서 전용 호스트 위로 이동시킬 수 있습니다.

이 문서에서는VM Import기능을 이용해서 여러분의 윈도우 서버를 전용 호스트로 옮기는 방법을 살펴보도록 하겠습니다.

먼저 전용호스트를 생성합니다. 전용 호스트를 만드는 것은 매우 간단합니다. 콘솔의 EC2 에서 좌측 메뉴를 보면 “dedicated host”란 항목이 존재합니다.


[그림 1] dedicated host 메뉴

dedicated host 메뉴를 들어가서 상단에 존재하는 Allocate Dedicate Host 버튼을 눌러 원하는 전용 호스트를 생성합니다. 생성할 때 인스턴스 타입을 선택하게 되는데, 이는 전용 호스트에 올릴 수 있는 인스턴스 타입을 의미합니다.

m4.large 인스턴스 타입을 선택해서 호스트를 생성한 결과가 그림 2입니다. 하단의 정보를 보면 물리적 코어갯수(Physical Cores), 소켓수(Sockets) 등을 확인할 수 있습니다.


[그림 2] 전용 호스트 생성 모습

Windows VM Import 사전준비
여러분이 가상화 환경을 사용한다면 운용중인 서버 이미지를 생성해서 VM Import를 사용해서 AWS로 이동 시킬 수 있습니다. 작업을 하기 전에 먼저 확인을 해야하는 사항들이 존재합니다. 크게 보면 지원하는 O/S종류와 생성하는 이미지 파일의 포맷입니다. (더 자세한 정보는 기술 문서를 참고하시기 바랍니다.)

여기에는 지원하는 각종 O/S 종류와 지원이 안되는 종류(예를 들어 32비트 버전 Microsoft Windows 2012 R2)등이 자세히 기록되어 있습니다. 지원 이미지 형식은 RAW, 정적 혹은 동적 VHD, 스트림 최적화 ESX VMDK, OVA 형식입니다.

여기서는 OVA이미지 형식으로 만들었다고 가정합시다. (이 단계는 각자의 가상화 환경에 따라 다를 수 있으므로 자세한 기술은 피하도록 하겠습니다. 다만 위에 기술되어 있는 지원 가능한 이미지 형식으로 전환하시기를 바랍니다.)

제 경우는 맥 윈도우 환경에서 vmware fusion을 사용하는 환경에서 OVA를 만들어 보았습니다. 이 경우에는 vmware fusion이 메뉴에서 Export 기능을 지원하지 않아 ovftools을 이용해서 만들었습니다. 명령을 실행하기 위해서는 /Applications/VMware Fusion.app/Contents/Library/VMware OVF Tool 디렉토리로 이동해서 다음과 같은 명령을 실행합니다.

명령의 실행 예는 아래와 같습니다.

$ ./ovftool --acceptAllEulas   /Users/seon/Documents/vmware/win.vmwarevm/win81.vmx /Users/seon/Documents/win81.ova

그 다음은 두 가지를 준비해야 합니다. 생성된 OVA를 Import하기 위한 역할과 이미지가 올라갈 S3 버킷의 설정입니다. 먼저 S3 버킷을 적절히 생성합니다. 여기서는 서울 리전에seon-vmimport-2016 라는 이름으로 생성을 했습니다.

그 다음은 역할을 설정합니다. vmimport라는 역할을 생성합니다. 다음 링크의 json을 이용해서 생성합니다. (더 자세한 것은 기술 문서를 참고하시기 바랍니다.)

명령어는 다음과 같이 실행합니다.

$ aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

그 다음은 역할에 적용할 정책입니다. Role-policy.json이란 정책을 적용합니다. 이때 중요한 점은 이 정책에서 앞서 생성한 S3 버킷의 이름을 정확히 지정해서 주어야 한다는 점입니다. 다음 소스에서 붉은 색으로 된 부분이 버킷이름입니다.

{
"Version":"2012-10-17",
"Statement":[
    {
      "Effect":"Allow",
         "Action":[
          "s3:ListBucket",
          "s3:GetBucketLocation"
         ],
      "Resource":[
          "arn:aws:s3:::seon-vmimport-2016"
      ]
},
{
      "Effect":"Allow",
          "Action":[
            "s3:GetObject"
           ],
          "Resource":[
             "arn:aws:s3:::seon-vmimport-2016/*"
           ]
},
{
       "Effect":"Allow",
          "Action":[
            "ec2:ModifySnapshotAttribute",
            "ec2:CopySnapshot",
            "ec2:RegisterImage",
            "ec2:Describe*"
         ],
       "Resource":"*"
   }
  ]
}

다음 명령으로 위의 정책을 역할에 적용합니다. 설명서에 나와 있는대로 role-policy.json 이라는 json 파일을 이용해서 역할을 생성합니다.

$ aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

마지막으로 IAM 유저로 작업을 할 경우에는 그에 적절한 권한이 주어져아 하는데, 필요한 권한은 기술 문서를 통해서 확인할 수 있습니다.

VM의 준비
윈도우즈나 리눅스의 경우 해당 이미지를 생성할 때 O/S 내부에서 몇 가지 설정을 바꿔 줄 필요가 있습니다. 예를 들어 윈도우즈의 경우 Vmware tool 을 제거한다든가, RDP 를 활성화 한다던가 하는 작업이 필요합니다. 사전 준비내용은 기술 문서에서 확인할 수 있습니다.

O/S를 이 조건대로 설정한 이후에 이것을 OVA  파일등으로 이미지로 만듭니다. 이후에 앞서 만든 버킷에 업로드를 합니다. 아래 명령을 이용해 앞서 만든 버킷에 올리도록 하겠습니다.

$ aws s3 cp win8.ova s3://seon-vmimport-2016/win8.ova

자 이제 모든 준비가 끝났습니다.

앞서 만든 OVA 파일을 임포트 해보겠습니다. 관련 정보는 기술 문서를 참조하시면 됩니다.

임포트에는 위 올린 버킷의 정보를 기술하는 containter.json을 이용해서 명령을 수행합니다.

[{
  "Description": "First CLI task",
  "Format": "ova",
  "UserBucket": {
  "S3Bucket": "seon-vmimport-2016",
  "S3Key": "win8.ova"
  }
}]

 

명령은 다음과 같습니다.

$ aws ec2 import-image --description "Windows 2008 OVA" --disk-containers file://containers.json

임포트 진행 상태는 다음으로 확인합니다.

$aws ec2 describe-import-image-tasks

응답 예는 다음과 같습니다.

{
"ImportImageTasks": [
  {
    "Status": "active",
    "Description": "Windows 2008 OVA",
    "Progress": "2", 
    "SnapshotDetails": [
      {
        "UserBucket": {
        "S3Bucket": "seon-vmimport-2016",
        "S3Key": "win8.ova"
      },
    "DiskImageSize": 0.0,
    "Format": "OVA"
   }
  ],
  "StatusMessage": "pending",
  "ImportTaskId": "import-ami-fgmj6b"
  }
 ]
}

만약 vmdk 이미지로 여러 파일로 나누어져 있는 형식이라면 container.json 을 다음과 같이 변경하기 바랍니다.

[{
    "Description": "First CLI task",
    "Format": "vmdk",
    "UserBucket": {
       "S3Bucket": "my-import-bucket",
       "S3Key": "my-windows-2008-vm-disk1.vmdk"
    }
},
{
    "Description": "Second CLI task",
    "Format": "vmdk",
    "UserBucket": {
       "S3Bucket": "my-import-bucket",
       "S3Key": "my-windows-2008-vm-disk2.vmdk"
}
}]

status가 completed 로 변경되면, 가져오기가 완료된 것입니다. 완료되면 EC2 콘솔의 전용호스트 메뉴의 “Action”에서 “Launch Instance(s) onto Host” 명령을 실행한후My AMIs 가면 가져오기가 성공한 이미지를 선택해서 런치할 수 있습니다.


[그림 3] 전용 호스트에서 가져온 이미지 런치

vm-import 기능은 한번만 수행해 보면 쉽게 사용할 수 있습니다. 전용 호스트를 사용해서 비용 절감 뿐 아니라 적절한 최적화 인스턴스를 사용하여 성능향상의 혜택까지 누리시길 바랍니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 박선용 솔루션즈 아키텍트께서 작성해주셨습니다.