# 학습 목표
- 이미지가 업로드되면, 원본과 별도로 썸네일을 생성하고, 이를 별도의 버킷에 저장해야 합니다.
- 아래 과제 제출 방법을 참고해 GitHub에 제출합니다.
# 해결 과제
- 과제를 달성하면, S3 이벤트가 SQS로 전송되게 만들고, SQS로부터 이벤트를 받아 람다가 실행하게 만들어봅시다.
- 썸네일 생성이 완료되면, 메일로 해당 썸네일 URL과 함께 전송이 되게 만들어봅시다.
- S3의 Pre-signed URL 기능을 이용하여, 업로드 전용 URL을 획득하고, 이를 통해 이미지를 S3 업로드할 수 있게 만들어봅시다.
# 실습 자료
- sam init 명령을 이용해 Quick Start Template으로부터 Standalone function을 하나 생성합니다.
- lambda 함수의 파라미터를 정의합니다. 이는 이벤트 소스로부터 트리거가 발생했을 때 이벤트의 형태를 확인하기 위함입니다. 다음과 같이 코드를 작성합니다.
exports.helloFromLambdaHandler = async (event, context) => {
console.log(event)
console.log(context)
return 'Hello from Lambda!';
}
# 과제 항목별 진행 상황
1. Lambda 함수 생성
javascript 코드 작성
const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const sharp = require('sharp');
exports.helloFromLambdaHandler = async (event, context) => {
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/+/g, ' '));
const params = {
Bucket: bucket,
Key: key,
};
// 원본 버킷으로부터 파일 읽기
const s3Object = await s3.getObject(params).promise();
// 이미지 리사이즈, sharp 라이브러리가 필요합니다.
const data = await sharp(s3Object.Body).resize(200).jpeg({ mozjpeg: true }).toBuffer()
// param 버킷을 대상 버킷으로 변경
params.Bucket = process.env.BUCKET_TARGET
// 대상 버킷으로 파일 쓰기
const result = await s3.putObject({
...params,
ContentType: 'image/jpeg',
Body: data,
ACL: 'public-read'
}).promise()
}
build & deploy
oh@devops ~/codeStates/sam-image-app sam build
Starting Build use cache
Manifest file is changed (new hash: da81938432ee64c3c397dcbd39b15e4b) or dependency folder (.aws-sam/deps/d3b653c2-eee4-4088-8292-77f8ae83bc69) is missing for (helloFromLambdaFunction), downloading dependencies and copying/building source
Building codeuri: /home/oh/codeStates/sam-image-app runtime: nodejs14.x metadata: {} architecture: x86_64 functions: helloFromLambdaFunction
Running NodejsNpmBuilder:NpmPack
Running NodejsNpmBuilder:CopyNpmrcAndLockfile
Running NodejsNpmBuilder:CopySource
Running NodejsNpmBuilder:NpmInstall
Running NodejsNpmBuilder:CleanUp
Running NodejsNpmBuilder:CopyDependencies
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Running NodejsNpmBuilder:LockfileCleanUp
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
oh@devops ~/codeStates/sam-image-app sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-image-app]:
AWS Region [ap-northeast-2]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: n
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: n
Capabilities [['CAPABILITY_IAM']]:
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N
Save arguments to configuration file [Y/n]: n
Looking for resources needed for deployment:
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-qo0co9c7ixez
A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False
Uploading to sam-image-app/0761243950ce57d64feac4a3f69d0c92 19550613 / 19550613 (100.00%)
Deploying with following values
===============================
Stack name : sam-image-app
Region : ap-northeast-2
Confirm changeset : False
Disable rollback : False
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-qo0co9c7ixez
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
Uploading to sam-image-app/ab80d8e45bac96e41d1a0abc18153d11.template 673 / 673 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------
* Modify helloFromLambdaFunction AWS::Lambda::Function False
-----------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-2:057440442371:changeSet/samcli-deploy1683701668/a7c43724-60fd-4c88-98c2-d3808a88326b
2023-05-10 15:54:34 - Waiting for stack create/update to complete
CloudFormation events from stack operations (refresh every 5.0 seconds)
-----------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-----------------------------------------------------------------------------------------------------------------------------------------
UPDATE_IN_PROGRESS AWS::Lambda::Function helloFromLambdaFunction -
UPDATE_COMPLETE AWS::Lambda::Function helloFromLambdaFunction -
UPDATE_COMPLETE_CLEANUP_IN_PROGR AWS::CloudFormation::Stack sam-image-app -
ESS
UPDATE_COMPLETE AWS::CloudFormation::Stack sam-image-app -
-----------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - sam-image-app in ap-northeast-2
2. S3 버킷 생성
소스 버킷 생성

타겟 버킷 생성

타겟 버킷 ACL 활성화

3. Lambda 트리거 추가

4. Lambda 실행 역할에 권한 정책 추가
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3GetObject",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bighead-image-src/*"
},
{
"Sid": "S3PutObject",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::bighead-image-tg/*"
}
]
}
5. 테스트
이미지 업로드

Lambda 실행 확인

썸네일 이미지 생성 확인



# Advanced Challenge
🔥 S3 이벤트가 SQS로 전송되게 만들고, SQS로부터 이벤트를 받아 람다가 실행하게 만들어봅시다.
1. SQS 대기열 생성

2. S3 이벤트 알림 추가

3. Lambda 실행 역할에 권한 정책 추가
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor",
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:ap-northeast-2:057440442371:bighead-image-convert"
}
]
}
4. Lambda 트리거 추가

5. Lambda 소스 코드 수정
트리거를 SQS로 변경 시, S3 Event 관련 정보가
event.Records[0].body.Records[0].s3
안으로 들어온다.
const aws = require('aws-sdk')
const region = process.env.AWS_REGION
aws.config.update(region);
const s3 = new aws.S3({ apiVersion: '2006-03-01' })
const sharp = require('sharp')
exports.helloFromLambdaHandler = async (event, context) => {
const body = JSON.parse(event.Records[0].body)
const s3Event = body.Records[0].s3
const bucketSrc = s3Event.bucket.name
const bucketTg = process.env.BUCKET_TARGET
const key = decodeURIComponent(s3Event.object.key.replace(/+/g, ' '))
const s3Params = {
Bucket: bucketSrc,
Key: key,
}
// 원본 버킷으로부터 파일 읽기
const s3Object = await s3.getObject(s3Params).promise()
// 이미지 리사이즈, sharp 라이브러리가 필요합니다.
const data = await sharp(s3Object.Body).resize(200).jpeg({ mozjpeg: true }).toBuffer()
// param 버킷을 대상 버킷으로 변경
s3Params.Bucket = bucketTg
const result = await s3.putObject({
...s3Params,
ContentType: 'image/jpeg',
Body: data,
ACL: 'public-read'
}).promise()
}
🔥 썸네일 생성이 완료되면 Amazon SNS를 활용하여, 메일로 해당 썸네일 URL과 함께 전송이 되게 만들어봅시다.
1. SNS 주제 생성

2. SNS 구독 생성

3. Lambda 실행 역할에 권한 정책 추가
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor",
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:ap-northeast-2:057440442371:bighead-image-email"
}
]
}
4. Lambda 소스 코드 수정
const aws = require('aws-sdk')
const region = process.env.AWS_REGION
aws.config.update(region);
const s3 = new aws.S3({ apiVersion: '2006-03-01' })
const sns = new aws.SNS({apiVersion: '2010-03-31'})
const sharp = require('sharp')
exports.helloFromLambdaHandler = async (event, context) => {
const body = JSON.parse(event.Records[0].body)
const s3Event = body.Records[0].s3
const bucketSrc = s3Event.bucket.name
const bucketTg = process.env.BUCKET_TARGET
const key = decodeURIComponent(s3Event.object.key.replace(/+/g, ' '))
const s3Params = {
Bucket: bucketSrc,
Key: key,
}
// 원본 버킷으로부터 파일 읽기
const s3Object = await s3.getObject(s3Params).promise()
// 이미지 리사이즈, sharp 라이브러리가 필요합니다.
const data = await sharp(s3Object.Body).resize(200).jpeg({ mozjpeg: true }).toBuffer()
// param 버킷을 대상 버킷으로 변경
s3Params.Bucket = bucketTg
const result = await s3.putObject({
...s3Params,
ContentType: 'image/jpeg',
Body: data,
ACL: 'public-read'
}).promise()
const snsParams = {
TopicArn: process.env.SNS_TOPIC_ARN,
Subject: 'Thumbnail have been extracted from the image.',
Message: `Thumbnail URL: https://${bucketTg}.s3.${region}.amazonaws.com/${key}`
}
// SNS 메시지 게시
await sns.publish(snsParams).promise()
}
5. 테스트
이메일 전송 확인

🔥 S3의 Pre-signed URL 기능을 이용하여, 업로드 전용 URL을 획득하고, 이를 통해 이미지를 S3 업로드할 수 있게 만들어봅시다.
1. Lambda 소스 코드 수정
const aws = require('aws-sdk')
const region = process.env.AWS_REGION
aws.config.update(region);
const s3 = new aws.S3({ apiVersion: '2006-03-01' })
const sns = new aws.SNS({apiVersion: '2010-03-31'})
const sharp = require('sharp')
const axios = require('axios')
exports.helloFromLambdaHandler = async (event, context) => {
const body = JSON.parse(event.Records[0].body)
const s3Event = body.Records[0].s3
const bucketSrc = s3Event.bucket.name
const bucketTg = process.env.BUCKET_TARGET
const key = decodeURIComponent(s3Event.object.key.replace(/+/g, ' '))
const s3Params = {
Bucket: bucketSrc,
Key: key,
}
// 원본 버킷으로부터 파일 읽기
const s3Object = await s3.getObject(s3Params).promise()
// 이미지 리사이즈, sharp 라이브러리가 필요합니다.
const data = await sharp(s3Object.Body).resize(200).jpeg({ mozjpeg: true }).toBuffer()
// get presignedURL
const presignedURL = s3.getSignedUrl('putObject', {
Bucket: bucketTg,
ContentType: 'image/jpeg',
ACL: 'public-read',
Key: key,
Expires: 60
})
// 대상 버킷으로 파일 쓰기
await axios.put(presignedURL, data, {
headers: {
'Content-Type': 'image/jpeg',
'x-amz-acl': 'public-read'
}
})
// SNS 메시지 게시
const snsParams = {
TopicArn: process.env.SNS_TOPIC_ARN,
Subject: 'Thumbnail have been extracted from the image.',
Message: `Thumbnail URL: https://${bucketTg}.s3.${region}.amazonaws.com/${key}`
}
await sns.publish(snsParams).promise()
}
2. 테스트
변환된 이미지 업로드 확인

# TROUBLE SHOOTING LOG
💡 S3 이벤트 → SNS 구성시 원하는 응답을 받지 못함
원인
S3에 생성되는 객체에 대한 이벤트만 SNS로 알림을 하기 때문에 사진 URL 응답을 받을 수 없음
해결 방안

위와 같은 방향으로 다시 코드를 작성하여 해결
💡 SQS를 트리거로 하는 Lambda가 지속 호출되는 이슈
원인
Lambda 함수 코드 오류 발생으로 인해 호출이 재시도 됨.
해결 방안
메시지 보존 기간을 조정하여 SQS 대기열에서 처리되지 않은 메시지 삭제 추후 데드 레터 큐(DLQ) 의 사용을 고려해 볼 필요가 있음.
[레퍼런스]
Amazon SQS에서 Lambda 사용 – 실패한 호출에 대한 백오프 전략
💡 Pre-signed URL을 사용하여 S3 업로드를 하였을 때, 해당 객체 URL에 접속되지 않는 이슈
원인
Pre-signed URL 생성 및 S3 업로드 시 ACL을 지정하지 않음.
해결 방안
public-read ACL 지정
// get presignedURL
const presignedURL = s3.getSignedUrl('putObject', {
Bucket: bucketTg,
ContentType: 'image/jpeg',
ACL: 'public-read',
Key: key,
Expires: 60
})
// S3 putObject
await axios.put(presignedURL, data, {
headers: {
'Content-Type': 'image/jpeg',
'x-amz-acl': 'public-read'
}
})
#References
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/API/API_PutObject.html