2019년 1월 10일 목요일

[AWS] Lambda 이미지 리사이징 함수 구현

[AWS] Lambda 이미지 리사이징 함수 구현

[AWS] Lambda 이미지 리사이징 함수 구현

이미지 리사이징 함수 구현을 AWS Lambda로 구현해 본다.
이미지 리사이징이라함은 AWS S3에 업로드한 이미지 객체에 대해서 thumbnail 이미지를 만드는 것이다.
썸네일 이미지의 기준 사이즈를 가로 100px을 기준으로 한다.
참고자료는 다음과 같다.

Lambda 콘솔에서 자유롭게 하려 했으나 자습서 내용에는 AWS CLI 로 기술되어 있기에 AWS CLI를 우선적으로 설치한다.

AWS CLI 설치법

Lambda 사용법

Node.js 배포

  • 자습서 자료: https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/nodejs-create-deployment-pkg.html
    Node.js 배포 패키지 생성에 대해
    위 내용은 결국 Lambda 올릴경우 패키지를 설치한 형태로 업로드가 되야함 디렉토리를 생성하고
    이 하위에 필요 패키지를 npm으로 설치한다. node_modules 생성 후 원하는 Lambda 함수 작동 코드를 index.js를 생성한다. 이를 압축하여 Lambda 콘솔에 업로드함.

예를들면 다음과 같다.
디렉터리는 다음의 구조를 갖게 됩니다.

index.js
node_modules/async
node_modules/async/lib
node_modules/async/lib/async.js
node_modules/async/package.json`

폴더의 내용을 압축하여 (배포 패키지) 업로드 후 테스트 수행 하고 문제 없을 시 트리거 등 실제 원하는
이벤트와 연결하여 사용!!!

테스트를 수행하여 제대로 동작 하는 것을 확인을 꼭 해야함! 안그럴 경우 트리거 또는 실전 연결하여 람다를 활용할 경우 문제 상황을 제대로 파악하기 어렵다… 왜냐하면 따로 로그 파악이 어려워서… cloudWatch 인가 이거 잘 이용하면 파악 된다던데…

S3 관련 과정은 다음과 같다.

사용 사례

위 과정을 통해 진행되며 이 포스트에서는 “2.1단계 배포 패키지 생성” 이후를 설명한다.
자세히 잘 설명 되어 있는데 디테일한 설정들이 많고 자기 상황에 맞는 부분을 정확히 수정해야하기 때문에
정신 바짝 차리고 해당 단계를 수정해 봐야한다.

aws cli 명령어 - 현재 lambda 함수 리스트

$ aws lambda list-functions --profile adminuser

명령어를 수행하면 현재 만들어진 람다의 함수 목록이 조회됨.

aws cli 명령어 - 배포

$ aws lambda create-function \
--region ap-northeast-2 \
--function-name CreateThumbnail \
--zip-file fileb:///Users/slicequeue/workplace/nodejs/lambda/ePROImageResizer.zip \
--role arn:aws:iam::075583837999:role/pro-lambda-s3-execution-role \
--handler CreateThumbnail.handler \
--runtime nodejs \
--profile adminuser \
--timeout 10 \
--memory-size 1024

이러한 명령어는 .zip 파일 업로드 뿐 아니라 역할 설정(이는 자습서 내용에서 실행 역할 생성 부분에 언급된 역할 생성 후 arn 값을 따로 기록해 두라고 되어 있는 부분에 내용임), handler 및 실행 환경 내용(nodejs) 등 기타 설정 내용이 믿음직 스럽게 담겨있다.
람다 콘솔에서 해보려고 하면 잘 설정이 안될때가 많았음.

Node.js 람다 이미지 리사이징 수행 코드

이미지 리사이징 수행 Node.js 코드는 aws 에서 예제로 되어 있는 코드를 변경하여 구현하였다.
자습서에 나와있는 코드는 버킷을 달리 두고 한쪽 원본 이미지가 들어오는 버킷에서 이미지가 업로드 된 경우
이미지 썸네일을 만들고 이후에 썸네일용 버킷에 옮겨 담는 예제이다.

nodejs 전체 코드 내용은 다음과 같다.

createThumbnail.js

// dependencies
var async = require('async');
var AWS = require('aws-sdk');
var gm = require('gm')
            .subClass({ imageMagick: true }); // Enable ImageMagick integration.
var util = require('util');

// constants
var MAX_WIDTH  = 100;
var MAX_HEIGHT = 100;

var PREFIX_THUMB = 'thumb-';

// get reference to S3 client 
var s3 = new AWS.S3();
 
exports.handler = function(event, context, callback) {
    // Read options from the event.
    console.log("Reading options from event:\n", util.inspect(event, {depth: 5}));
    var srcBucket = event.Records[0].s3.bucket.name;
    // Object key may have spaces or unicode non-ASCII characters.
    var srcKey    =
    decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));  
    var dstBucket = srcBucket; // + "resized";
    var dstKey    = PREFIX_THUMB + srcKey;
    
    console.log('>>', dstKey);

 //! 이부분 기존 코드 부분 같은 버킷에 위치하는 것으로 하고 싶기에 삭제
    // Sanity check: validate that source and destination are different buckets.
    // if (srcBucket == dstBucket) {
    //     callback("Source and destination buckets are the same.");
    //     return;
    // }

    // Infer the image type.
    var typeMatch = srcKey.match(/\.([^.]*)$/);
    if (!typeMatch) {
        callback("Could not determine the image type.");
        return;
    }
    var imageType = typeMatch[1].toLowerCase();
    if (imageType != "jpg" && imageType != "png" && imageType != "jpeg") {
        callback('Unsupported image type: ${imageType}');
        return;
    }

    // Download the image from S3, transform, and upload to a different S3 bucket.
    async.waterfall([
        function download(next) {
            // Download the image from S3 into a buffer.
            s3.getObject({
                    Bucket: srcBucket,
                    Key: srcKey
                },
                next);
            },
        function transform(response, next) {
            gm(response.Body).size(function(err, size) {
                // Infer the scaling factor to avoid stretching the image unnaturally.
                var scalingFactor = Math.min(
                    MAX_WIDTH / size.width,
                    MAX_HEIGHT / size.height
                );
                var width  = scalingFactor * size.width;
                var height = scalingFactor * size.height;

                // Transform the image buffer in memory.
                this.resize(width, height)
                    .toBuffer(imageType, function(err, buffer) {
                        if (err) {
                            next(err);
                        } else {
                            next(null, response.ContentType, buffer);
                        }
                    });
            });
        },
        function upload(contentType, data, next) {
            // Stream the transformed image to a different S3 bucket.
            s3.putObject({
                    Bucket: dstBucket,
                    Key: dstKey,
                    Body: data,
                    ContentType: contentType
                    //,ACL:'public-read'
                },
                next);
            }
        ], function (err) {
            if (err) {
                console.error(
                    'Unable to resize ' + srcBucket + '/' + srcKey +
                    ' and upload to ' + dstBucket + '/' + dstKey +
                    ' due to an error: ' + err
                );
            } else {
                console.log(
                    'Successfully resized ' + srcBucket + '/' + srcKey +
                    ' and uploaded to ' + dstBucket + '/' + dstKey
                );
            }

            callback(null, "message");
        }
    );
};

기존 코드에는 몇가지 제한사항이 있었다. jpg 파일 확장자가 어떨때는 JPG, JPEG 등 다른 값으로 들어올 때가 있었다. 이때 동작을 하지 않도록 파일 확장자 검사 부분을 수정하였다.

    var imageType = typeMatch[1].toLowerCase();
    if (imageType != "jpg" && imageType != "png" && imageType != "jpeg") {
        callback('Unsupported image type: ${imageType}');
        return;
    }

더불어 버킷을 두개를 운영해야하는 것을 원치 않아 하나의 버킷에서 동작하도록 버킷의 특정 폴더 내에서 이미지 업로드를 통해 객체가 생성될 경우 이미지 썸네일을 만들고 이를 다른 폴더로 저장시키는 코드로 수정하였다.

var PREFIX_THUMB = 'thumb-';
~

var dstKey    = PREFIX_THUMB + srcKey;

위의 변경 사항은 아래 Lambda의 트리거 연결 과정에서 추가 설정을 통해 코드가 제대로 동작 하도록 한다.

앞서 설명한 zip 파일로 만들어 배포를 수행 하였다.
필요 패키지는 gm, async 를 설치해야 하며 파일 구성은 다음과 같다.

CreateThumbnail.js 
/node_modules/gm 
/node_modules/async

주의! 이 파일 폴더 목록을 포함하여 그 위치에서 압축 수행해야함!
이 파일을 포함하는 폴더 자체를 압축하면 안됨!

이 nodejs 배포용 zip 파일을 업로드 하게 되면 Lambda 콘솔 페이지에서 함수 부분에 코드 편집 창 부분에 소스코드가 깔끔하게 올라와 진 것을 확인 할 수 있다! 뿌듯!

테스트

업로드 후에 기능이 제대로 되는지 확인을 해야함
테스트는 Lambda 콘솔의 상단 메뉴에 테스트 부분이 있고 옆에 테스트 종류를 나타내는 드롭다운 메뉴가 있음
이 드롭다운을 클릭하여 테스트 생성을 하게 되면 창에 해당 템플릿을 선택 할 수 있다.

여기서 기존에 업로드한 이미지 객체와 버킷 정보를 담아 생성을 하면 다음과 같다.

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-2",
      "eventTime": "1970-01-01T00:00:00.000Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "EXAMPLE"
      },
      "requestParameters": {
        "sourceIPAddress": "127.0.0.1"
      },
      "responseElements": {
        "x-amz-request-id": "EXAMPLE123456789",
        "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "testConfigRule",
        "bucket": {
          "name": "smcepro.com",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
          },
          "arn": "arn:aws:s3:::smcepro.com"
        },
        "object": {
          "key": "symptom-images/KakaoTalk_Photo_2017-10-12-01-29-31.jpeg",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }
      }
    }
  ]
}

테스트 설정에서 아래 부분 버킷 이름과 오브젝트 이름에서 키값을 잘 설정해 줘야 테스트가 진행된다.

"bucket": {
          "name": "smcepro.com",
          "ownerIdentity": {
            "principalId": "EXAMPLE"
~
"object": {
          "key": "symptom-images/KakaoTalk_Photo_2017-10-12-01-29-31.jpeg",
          "size": 1024,
          "eTag": "0123456789abcdef0123456789abcdef",
          "sequencer": "0A1B2C3D4E5F678901"
        }

요청 송신시 제대로 요청이 될 경우 콘솔 페이지 부분에서 오류가 나지 않으며 실제 버킷에 가보면 리사이징 된 thumb- 폴더에 제대로 객체가 리사이징 된 결과물이 업로드 되어 있다.
이제 제대로 S3의 버킷에 특정 폴더에 이미지 파일이 객체로 생성될 때 그때 이벤트를 후킹하여 트리거 되도록 설정해보자!

함수 트리거 연결

이는 Lambda 콘솔의 함수 페이지에서 상단에 보여지는
함수에 엮여 있는 역할과 트리거 들에 대해 효과적으로 시각화되어 표현된다.
앞서서 배포 aws cli lambda 배포 명령어를 통해 역할들이 잘 지정되어 있다.

AWS 람다 이미지

이미 추가 해둔 것이긴 하지만 왼쪽편에 트리거 추가가 잘 나와있다. 자습서에는 이 부분도 cli로 처리할 것 같은데 그냥 콘솔 화면에서 처리해 보자!

트리거에 S3 를 클릭하면 우리가 원하는 이벤트 유형과 접두사 부분이 보여진다.

  • 버킷: 원하는 버킷을 선택한다.
  • 접두사: 우리는 원하는 특정 폴더의 생성 이벤트를 감지하고 싶기 때문에 “해당폴더이름/” 으로 접두사를 설정하자.
  • 접미사: 원하는 파일 형식의 끝값이 생성되는 오브젝트의 키값으로 오는 경우 지정 할 수 있으나 .jpg .jepg, .png 등등 다수가 있기 때문에 위 코드에 해당 형식 검사에 있으므로 생략한다. (여러개 접미사 지정은 어떻게 하지… 띄어쓰기 콤마로 될려나!?..)
  • 하단부 트리거 활성화: 체크를 한다. 아무래도 활성화를 해야지만이 제대로 연결되서 배포가 되겠지? 우리는 테스트로 문제 없는 것을 확인 했기 때문에 문제 없다고 판단.
  • 이 멘트 상단 “Lambda는 Amazon S3이(가) 이 트리거에서 Lambda 함수를 호출하는 데 필요한 권한을 추가합니다. Lambda 권한 모델에 대해 자세히 알아보기.” 요부분이 있다. 얼마나 고마운지~

이렇게 설정이 끝나면 꼭 상단에 저장 을 눌러야지만 생성이 된다.
생성을 마치고 실제 S3의 해당 버킷에 가서 속성에 이벤트 설정 부분을 가보면 해당 이벤트가 생성된 것을 확인 할 수 있다.

주의! 위 설정이 엉키거나 생성 후 삭제를 하다가 잘 못된 경우 이 부분이 삭제되지 않고 남을 때가 있어서 트리거 생성이 겹쳐서 안되는 에러가 발생 될 경우가 있다. 이경우 버킷에 가서 직접 이 이벤트를 삭제하고 다시 트리거를 설정하면 다시 생성 된다.

실전 배치가 끝났다. 제대로 동작하는 지는 lambda 대쉬보드에서 확인하자! 실제 서비스로 이미지 업로드를 수행하여 테스트를 하면 대쉬보드에서 그래프로 모니터링이 된다.

댓글 없음:

댓글 쓰기