흐르는 시간의 블로그...

AWS S3에 Object Upload를 테스트 하는 업무가 있다.

이를 위해 RESTful API, C++ SDK, Go SDK를 사용하여 테스트를 진행하였다.


각각의 SDK는 결국 RESTful API를 wrapping해서 사용하는 것이었다.

내부 코드를 보면 s3 manager 혹은 s3 client 객체를 생성하여 진행한다.


Object를 업로드할때 일정 이상의 용량일때는 multipart 업로드가 유리하다.


다음은 multipart upload에 대한 AWS의 공식 문서와 이를 해 잘 설명한 블로그다.

멀티파트 업로드 개요 - AWS

Amazon S3 Parallel MultiPart File Upload - BLOG(java)


개인적으로 잘 사용하는 C++과 Go lang용 SDK는 다음의 링크에서 확인할 수 있다

AWS SDK C++ - download(github)

AWS SDK GO - download(github)


다음의 링크는 C++ SDK의 통합 테스트코드로 상세하고 다양한 코드샘플을 찾을 수 있다.

https://github.com/aws/aws-sdk-cpp/blob/master/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp


C++ SDK에는 putobject와 multipart upload가 별도로 구분되어 있다.

위의 테스트 코드에서 PutObjectRequest 를 검색하면 기본 업로드 코드를, CreateMultipartUploadRequest 를 검색하면 multipart upload 코드를 찾을 수 있다.


아래 주소에 가면 cpp, go, .net, java, javascript, php, python, ruby의 예제 코드를 확인할 수 있다.

환경 설정이 잘 되어 있으면 즉각 컴파일과 실행이 가능하다.


GO Lang SDK의 sample 코드는 오픈소스라 더 쉽게 내용을 확인할 수 있다.

패키지 s3manager의 Upload를 확인하면 Upload() 함수가 있다.

이 함수를 사용하면 uploader의 PartSize가 s3manager.MinUploadPartSize인 5Mbytes보다 작으면 에러를 발생시킨다.

이후 PartSize 단위로 읽어들여 하나 이하의 chunk가 발생하면 single part upload를 진행한다.

chunk가 둘 이상이면 mulpartuploader 객체를 통해 업로드를 진행한다.



위의 SDK를 실행하려면 기본 환경설정을 해야 한다.

AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 를 설정해야한다.

AWS_SESSION_TOKEN 의 경우 옵션이다.


SDK Configuration - GO Lang


CPP SDK의 경우 CMake를 활용하며 c++ version이 최소 C++11를 지원해야 한다.

SDK 설치시 C++11 혹은 C++14를 지정할 수 있다.

이와 함께 CMake를 사용한다. 

사용하는 linux의 기존 CMake는 버전이 낮아 호환되지 않았다.

yum을 통해 cmake3를 설치하여 사용하였다.

Configuring the SDK - CPP


위의 링크들의 문서와 예제 코드를 사용하면 SDK를 활용하여 AWS를 사용하는 것이 크게 어렵지 않을 것이다.




위의 과정들을 통해 파일 업로드 속도를 간단히 체크한 결과이다.

테스트는 IDC의 linux 서버에서 진행하였으며 multipart upload의 경우 사무실의 개발PC에서 실행하여 체크한 시간이다.



S3 File upload time check - linux server, AWS CLI






시작(ms)

종료(ms)

시간차(ms)

1회

1494548006096

1494548043927

37831

2회

1494548050272

1494548072718

22446

3회

1494548094591

1494548148717

54126

4회

1494548161326

1494548217320

55994

5회

1494548227645

1494548275162

47517

6회

1494548306828

1494548329881

23053

7회

1494548352251

1494548364362

12111

8회

1494548382826

1494548395007

12181

9회

1494548401111

1494548453868

52757

10회

1494548459801

1494548475682

15881

합산



333897

평균



33389.7




S3 File upload time check - linux server, c++ sdk, putobject(single), file size:25826362


소요시간(seconds) - 1회

소요시간(seconds) - 2회

1회

8.70795

18.9716

2회

21.2515

12.0308

3회

34.9919

13.7737

4회

29.1713

6.39977

5회

9.98919

7.33901

6회

15.6894

16.913

7회

29.9045

20.028

8회

53.3282

25.8403

9회

32.0113

42.0073

10회

10.8376

33.4675

합산

245.883

196.771

평균

24.5883

19.6771



S3 File upload time check - linux server, go sdk, Upload(auto multiparts), file size:26189716


소요시간(seconds) - 1회

소요시간(seconds) - 2회

1회

4.6583578

4.6088254

2회

3.9769095

4.1344735

3회

4.0830212

4.2095661

4회

4.7418807

4.1589274

5회

4.1057907

4.6452324

6회

4.2522046

4.0686392

7회

4.5779243

4.0981387

8회

4.6437622

5.8632637

9회

4.2051021

6.0630964

10회

4.1674061

6.753556

합산

43.412359

48.60372

평균

4.3412359

4.860372




AWS를 사용하는데 가장 좋은 방안은 SDK를 사용하는 것이다.

하지만 모든 SDK를 제공하지도 않으며 현재 테스트할 작업에서는 AWS REST API가 적절하다 생각하여 테스트를 했다.

하지만 짧은 영어탓과 제대로 이해하지 못하여 원하는 결과를 내지는 못했다.

그 과정을 정리해본다.


AWS REST API에서 첫번째 관문은 인증이다.

당연한 문제이기도 하다.

인증 방식 중 Signature Version 4를 사용하는 방법을 찾았다.


Authenticating Requests (AWS Signature Version 4)

위의 링크에서 확인할 수 있듯이 HTTP Authorization header과 Query string parameters를 사용할 수 있다.

query string 방식은 미리 인증된 URL을 사용하며 그 링크는 7일간 유효하다.

긴 시간 일관된 방식으로 서비스하기에는 적절하지 않다.

따라서 HTTP Authorization header를 사용하려고 시도하였다.



Authenticating Requests: Using the Authorization Header (AWS Signature Version 4)

인증의 시작은 바로 위의 링크의 내용을 따라 진행한다.

관련하여 Amazone S3 REST API with curl 의 블로그가 매우 잘 설명하고 있다.


인증을 위한 기본 키가 필요한데 해당 내용은 AWS IAM에서 진행한다

AWS IAM Service --> Users --> ID 선택 --> Security credentials 탭 --> Access Key 생성


저 과정을 통해서 KEY ID와 Security Key를 얻어서 사용할 수 있다.

아래의 내용은 위의 내용들과 인터넷에서 구한 쉘 스크립트를 이용해 "GET / "를 작동시켜보았다.

결과로 버킷 내의 모든 파일의 키값과 크기등의 정보를 XML로 반환한다.


#!/bin/bash
function hmac_sha256 {
  key="$1"
  data="$2"
  echo -n "$data" | openssl dgst -sha256 -mac HMAC -macopt "$key" | sed 's/^.* //'
}

#filename=$1
#filesize=$(stat -c%s "$filename")

date=$(date -u +"%Y%m%dT%H%M%SZ")
datek=$(date -u +"%Y%m%d")

reqmethod="GET"
canonicaluri="/"
canonicalquerystring=""
canonicalheaders="host:ubikhan-vod3.s3.amazonaws.com\nx-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:$date"
signedheaders="host;x-amz-content-sha256;x-amz-date"
#hashedpayload=$(openssl dgst -sha256 $filename | sed 's/^.* //')
#echo "hashedpayload:"$hashedpayload
hashedpayload="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

canonicalrequest=$reqmethod"\n"$canonicaluri"\n"$canonicalquerystring"\n"$canonicalheaders"\n\n"$signedheaders"\n"$hashedpayload

echo -e $canonicalrequest

keyid="XXXXXXXXXXXXXXXXXXX"
secret="securityYYYYYYYYYYYYYYYYYYYY"
region="ap-northeast-1"
service="s3"

awsreq="aws4_request"
algorithm="AWS4-HMAC-SHA256"
credentialscope=$datek/$region/$service/$awsreq
hashedcanonicalrequest=$(echo -e -n "$canonicalrequest" | openssl dgst -sha256 | sed 's/^.* //')
strtosign=$algorithm"\n"$date"\n"$credentialscope"\n"$hashedcanonicalrequest

echo -e $strtosign

# Four-step signing key calculation
dateKey=$(hmac_sha256 key:"AWS4$secret" $datek)
dateRegionKey=$(hmac_sha256 hexkey:$dateKey $region)
dateRegionServiceKey=$(hmac_sha256 hexkey:$dateRegionKey $service)
signingKey=$(hmac_sha256 hexkey:$dateRegionServiceKey "aws4_request")
signature=$(hmac_sha256 hexkey:$signingKey $strtosign)
echo $signature

request="https://ubikhan-vod3.s3.amazonaws.com/ -H \"Authorization: AWS4-HMAC-SHA256 Credential=$keyid/$datek/ap-northeast-1/s3/aws4_request, SignedHeaders=$signedheaders, Signature=$signature\" -H \"x-amz-content-sha256: $hashedpayload\" -H \"x-amz-date: $date\" "

echo $request
curl -v $request


canonical request와 payload에 대한 명확하게 이해한다면 REST API를 이용하여 PUT을 해볼 수 있을 것이다.

다만 PUT을 위한 이 부분을 아직도 이해 못하고 있는 상황이다.