AWS lambda 를 통해 이미지 리사이즈 서버 만들기(Node.js)
AWS Lambda 는 이벤트 에 응답하여 코드를 실행 하고 해당 코드에 필요한 컴퓨터 리소스를 자동으로 관리하는 컴퓨터 이다. lambda를 통해 서버에 대한 걱정없이 코드를 실행할 수 있다
이번 실습에서 AWS S3도 활용을 할것인데, S3은 Simple Storage Service의 줄임말로 아마존 웹 서비스에서 제공하는 온라인 스토리지 웹 서비스이다. S3는 웹 서비스 인터페이스를 통해 스토리지를 제공한다 . S3는 모든 타입에 대한 내구성 높은 개체 스토리지 서비스
그리고 또 하나 AWS API GATEWAY도 사용 할 것인데, API란 resource와 method의 집합으로 운영체제나 데이터베이스 관리 시스템과 같은 시스템 프로그램과 통신할 때 사용되는 것으로 API GATEWAY는 API Endpoint를 단일화하고 API 관리를 쉽게 할 수 있드아
준비물 : AWS ID (프리티어 1년 기간 남은걸로) , 편집기(atom , 아무거나) , NPM(요건 inessential) , Post Man
그리고 지역 설정할 때 무조건 아시아 태평양(서울) 로 설정
총 2부로 실습을 해볼 것인데
1부 Lambda With S3
- S3 버켓 생성
- 람다 함수 생성
- 람다 와 S3를 트리거 후 이미지 리사이즈 실습
2부 Lambda With API GATEWAY
- 새로운 람다 함수 생성
- API GATEWAY 에서 API 발급 , 배포
- Post Man을 통해
1부
AWS 홈페이지에 접속, 로그인을 한 후 서비스 에서 S3를 들어갑니다 들어갑니다
그다음으로 원본 이미지가 저장 될 (1)S3 스토리지 와 Lambda에서 (1)S3 스토리지에 원본 이미지가 저장(이벤트)된 것에 대하여 응답하고 리사이즈한 이미지를 저장할 (2)S3 스토리지를 하나 더 만듭니다.
여기서 버킷은 S3 스토리지에 저장하는 모든 타입의 정보에 대한 컨테이너로
아이스버킷 챌린지에서 얼음이 든 양동이를 뒤집어 쓰던 IT업계 들의 CEO들이 기억나실텐데 , 거기서 나온 그 '양동이'가 바로 버킷 입니다. 파일을 담는 폴더의 개념으로 생각하시면 됩니다.
리전은 위와 같이 하시되, 이름은 원본이 저장될 버킷이 'abc'라고 가정 한다면 , 리사이즈 된 이미지가 저장될 버킷은 'abc-resized'로 해주시면 되겠습니다. -resized를 붙이시면 됩니다
그 다음 S3에 원본이 저장될 버킷에 들어 가신 후 업로드 될 폴더를 만들어야 하는데, 이름을 images 라고 하겠습니다.
이제 Lambda 함수를 생성하러 고고씽
일단 서비스에서 Lambda를 들어갑니다.
오른쪽 위에 함수 생성을 누른 후 이름은 각자 알맞게 작성 후 런타임은 현재 Node.js 6.10 버전을 제공 하고 있어 이걸로 선택하고, 역할은 사용자 지정 역할 생성으로 들어간 뒤 lambda_basic_execution으로 주는데, 이것은 Lambda가 어디에 접근 할수 있는지에 대한 권한으로 나중에 다시 IAM 서비스를 통해 다시 설정 할 것이다.
함수를 생성 했으면, Event가 들어왓을 때 Lambda에서 실행할 코드를 작성해야 하는데, 코드를 여기다 올리겠습니다. 편집기 Atom에서 새로운 폴더를 하나 만들어 index.js파일을 만들어 이 소스코드를 저장하겠습니다. 그리고 Node.js에서 AWS에 접근하기 위해서는 aws-sdk 모듈을 사용해야 하는데, Lambda의 Node.js 런타임에 이 모듈이 이미 설치가 되어있고, 필요한 모듈은 Npm으로 직접 설치하여 압축한 후 Lambda에 올리면 되겠습니다. 이 코드를 요약해서 설명하자면, origin 버킷에 이미지를 저장할 경우, 이미지를 리사이즈 하여 -resized 버킷에 저장합니다.
xxxxxxxxxx
$npm install async gm util
위에 터미널에서 NPM으로 필요한 모듈을 설치
xxxxxxxxxx
var async = require('async'); // 'async'모듈을 임포트하여 더 용이하게 함수 작성
var AWS = require('aws-sdk'); // AWS Lambda 실행 환경에 기본값으로 설치되어 있음
var gm = require('gm')
.subClass({ imageMagick: true }); // imageMagick 연동 설정
var util = require('util');
// 썸네일 사이즈 기본값
var MAX_WIDTH = 200;
var MAX_HEIGHT = 200;
var s3 = new AWS.S3();
exports.handler = function(event, context, callback) {
// 이벤트를 발생시킨 버킷 및 S3객체의 키값 가져오기
var srcBucket = event.Records[0].s3.bucket.name;
var srcKey = event.Records[0].s3.object.key;
// 이미지가 저장될 버킷 설정
var dstBucket = srcBucket + "-resized";
var dstKey = srcKey;
// 버킷이 같으면 무한 루프
if (srcBucket == dstBucket) {
callback("Source and destination buckets are the same.");
return;
}
// 이미지 타입에 대한 예외처리
var typeMatch = srcKey.match(/\.([^.]*)$/);
if (!typeMatch) {
callback("Could not determine the image type.");
return;
}
var imageType = typeMatch[1];
if (imageType != "jpg" && imageType != "png") {
callback('Unsupported image type: ${imageType}');
return;
}
async.waterfall([
function download(next) {
// 이미지를 S3에서 가져옴
s3.getObject({
Bucket: srcBucket,
Key: srcKey
},
next);
},
function transform(response, next) {
gm(response.Body).size(function(err, size) {
// 이미지를 변환하고 섬네일 생성
var scalingFactor = Math.min(
MAX_WIDTH / size.width,
MAX_HEIGHT / size.height
);
var width = scalingFactor * size.width;
var height = scalingFactor * size.height;
this.resize(width, height)
.toBuffer(imageType, function(err, buffer) {
if (err) {
next(err);
} else {
next(null, response.ContentType, buffer);
}
});
});
},
function upload(contentType, data, next) {
// 섬네일을 s3에 업로드하는 함수
s3.putObject({
Bucket: dstBucket,
Key: dstKey,
Body: data,
ContentType: contentType
},
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");
}
);
};
이미 세개의 파일을 압축 후 Lambda에 zip파일 형태로 업로드 하면 됨
밑에 보면 기본 설정 카테고리가 있는데, 제한시간을 10초로 지정
함수를 생성 했으면, 이제 Lambda에다 trigger를 설정할 것인데,
여기서 trigger란 어떤 Action 이나 Event가 발생 하였을 때 자동으로 처리하는 프로세스 입니다.
여기서 우리가 해야 할 것은 S3 스토리지에 이미지가 저장 될 때, 이미지가 저장 된 event를 보고 Lambda가 다른 S3 스토리지에 리사이즈 된 이미지를 저장 하는 코드를 실행 할 것입니다.
왼쪽 목록 중 S3선택 후
접두사는 S3에 만든 images 폴더 이며 위와같이 설정하고 추가하면 됨
그리고 저장을 해주면 되겠습니다.
그리고 마지막으로 해야 하는게 , Lambda에게 S3에 접근 할 권한을 부여해야하는데 이것은 IAM을 통해 권한을 만든 후 , Lambda에게 설정을 할 수 있습니다.
IAM 들어간 후 왼쪽 목록에 역할을 클릭 후 서비스에서 Lambda를 선택 한 후 AdministratorAccess를 선택하는데, 이는 Lambda에게 모든 AWS service에 접근할 수 있는 권한을 뜻합니다. 이렇게 역할을 만든 후, 서비스에서 Lambda로 다시 돌아와 lambda_basic_execution에서 방금 만든 역할로 바꾸고, 저장 하면 되겠습니다
이제 실행을 해볼것인데, 동작원리를 위에서 계속 언급한것 처럼 , S3 origin 버킷에 이미지를 Upload 하는 Event를 할 경우, Lambda에서 trigger하여 S3 origin 버킷에 이벤트가 발생 했을경우, index.js의 코드를 실행하여, S3 resized 버킷에 리사이즈 된 이미지를 저장합니다.
이제 S3에 이미지를 저장하는 Event를 해보겠습니다.
S3 origin 버킷으로 들어가 처음에 만든 images 폴더에 png형식의 사진을 업로드 하겠습니다.
위에 두 사진을 비교해 보면, S3 resize 버킷에 크기가 리사이즈된 이미지가 저장된 걸 볼 수 있습니다.
2부
API GATEWAY를 통해 API를 발급 받고, Post Man으로 발급 받은 api를 통해 람다에 접근하여 , S3 origin 버킷에 이미지를 저장 후, S3 resize 버킷에 이미지를 저장해보겠습니다.
일단 새로운 람다 함수를 생성합니다. 처음 방법과 다 똑같이 만들돼, 역할은 아까 모든 접근을 허용하는 역할을 만들었는데, 그 역할을 그대로 사용하겠습니다.(사용자 지정 X , 기존 역할 O)
Lambda에서 실행할 코드는 요거 인데
const originBucket = '
또한 필요한 NPM 모듈을 설치해주면 됩니다.
xxxxxxxxxx
npm install parse-multipart bluebird
xxxxxxxxxx
const AWS = require('aws-sdk');
const multipart = require("parse-multipart");
const s3 = new AWS.S3();
const bluebird = require('bluebird');
exports.handler = function(event, context) {
let result = []
const bodyBuffer = new Buffer( event[ 'body-json' ].toString(), 'base64' );
const boundary = multipart.getBoundary( event.params.header[ 'Content-Type' ] )
const parts = multipart.Parse( bodyBuffer, boundary )
const files = getFiles( parts )
return bluebird.map( files, file => {
console.log('UploadCall')
return upload( file )
.then(
data => {
result.push( {
'bucket': data.Bucket,
'key': data.key,
'fileUrl': file.uploadFile.fullPath })
console.log( `DATA => ${JSON.stringify( data, null, 2 )}` )
},
err => {
console.log( `S3 UPLOAD ERR => ${err}` )
}
)
})
.then(_=> {
return context.succeed(result)
})
}
let upload = function( file ) {
console.log( 'PutObject Call')
return s3.upload( file.params ).promise();
};
let getFiles = function( parts ) {
//let fileExt = 'png'
let files = [];
parts.forEach( part => {
const buffer = part.data
const fileName = part.filename
const fileFullName = fileName;
const originBucket = '<S3-ORIGIN-BUCKET-NAME>/images'
const filefullPath = `https://s3.ap-northeast-2.amazonaws.com/${originBucket}/${fileFullName}`;
const params = {
Bucket: originBucket,
Key: fileFullName,
Body: buffer
};
const uploadFile = {
size: buffer.toString( 'ascii' ).length,
type: part.type,
name: fileName,
fullPath: filefullPath
};
files.push( { params, uploadFile } )
} );
return files
}
이제 API GATEWAY에서 API를 발급받을 건데, 서비스에서 API GATEWAY를 들어갑니다.
요약해서 짧게 설명하면 API발급 -> 리소스 생성(post 방식) -> 메소드 생성
서서서
API를 생성 후 작업에서 리소스 생성을 선택 한 후 , 리소스 이름을 upload 로 설정 후 생성합니다.
upload를 클릭 후 왼쪽 목록에서 설정으로 들어 간 후
이진 미디어 형식을 다음과 같이 추가합니다. multipart/form-data
마무리로 Save Changes
이진 미디어 형식이란 API GATEWAY에서 이미지가 들어오면 자동으로 이미지 변환 처리를 해주는 걸 뜻합니다.
그 다음 메소드 생성 후 post 방식으로 이미지를 보낼 것이기 때문에 post로 설정 후 다음과 같이 설정합니다.
리전에서 ap-northeast-2 를 선택한 이유는 아시아 태평양(서울)이 여기에 해당하기 때문
함수는 2부 처음에 만들었던 람다 함수를 넣으면 된다
아까 만든 메소드에서 Post를 클릭 후 오른쪽 위에 있는 통합 요청을 클릭
밑에 내려보면 본문 매핑 템플릿이 있는데 , 요청 본문 패스스루란 데이터를 어떻게 받아올지 정의 하는 것으로 multipart/form-data로 설정 하고 템플릿 생성에서 메서드 요청 패스스루를 선택 한후 저장 하면된다. 템플릿 생성은 AWS 자체에서 만들어 준다.
이제 마지막으로 API를 배포하면 되는데 , 리소스나 메소드를 생성한것 처럼 생성을 눌러 api 배포를 선택한다
생성하게 되면 URL 호출이 뜨면서
https://{restapi_id}.execute-api.{region}.amazonaws.com/{stage_name}/
이런 형식이 쭈루룩 써있을텐데,
여기서 {restapi_id}는 API 식별자이고, {region}은 API 배포 리전이고, {stage_name}은 API 배포의 단계 이름
이 URL을 통해 API를 호출 할 수 있게 된다.
이제 Lambda에서 만든 API를 trigger를 통해 설정하면된다.
마지막으로 Post Man을 통해 API url을 통해 이미지를 보내 보겠드아.