Zero To One

S3, Lambda, SQS 본문

마이크로서비스

S3, Lambda, SQS

Zero_To_One 2022. 4. 15. 00:03

목표

- S3 이벤트가 SQS로 전송되게 만들고, SQS로부터 이벤트를 받아 람다가 실행시키게 한다

 

메세지큐를 쓰는 이유

- 소스에서 처리속도가 빠르고, 타켓에서 처리속도가 느린경우 타켓에서의 처리속도를 따라가게 하기 위해 큐를 둬서 이벤트 들을 하나씩 쌓아놓고 하나씩 처리하게 하기 위해서- 예시) 컴퓨터와 프린터

 

아키텍처

s3 두개를 만들어야 한다!

시작

1. 

> sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
	1 - Hello World Example
	2 - Multi-step workflow
	3 - Serverless API
	4 - Scheduled task
	5 - Standalone function
	6 - Data processing
	7 - Infrastructure event management
	8 - Machine Learning
Template: 5

Which runtime would you like to use?
	1 - dotnetcore3.1
	2 - nodejs14.x
	3 - nodejs12.x
Runtime: 2

Based on your selections, the only Package type available is Zip.
We will proceed to selecting the Package type as Zip.

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.

Project name [sam-app]: image-resize-sqs

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: image-resize-sqs
    Runtime: nodejs14.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: quick-start-from-scratch
    Output Directory: .
    
    Next steps can be found in the README file at ./image-resize-sqs/README.md
        

    Commands you can use next
    =========================
    [*] Create pipeline: cd image-resize-sqs && sam pipeline init --bootstrap
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch

 

2. src -> handlers -> hello-from-lambda.js

다음 코드의 출생은 https://mtou.tistory.com/111 에서 확인할 수 있다.

//기본 code
const AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-2'});
const s3 = new AWS.S3({apiVersion: '2006-03-01'});
const sharp = require('sharp')

exports.helloFromLambdaHandler = async (event, context) => {
    console.log(JSON.stringify(event)) //JSON.stringify로 모든 로그가 찍힐 수 있게끔 한다

    const sourceBucktName = event.Records[0].s3.bucket.name
    const uploadFileName = event.Records[0].s3.object.key
    const destinationBucktName = '타겟버킷' 

    console.log(sourceBucktName)
    console.log(uploadFileName)
    // 원본 버킷으로부터 파일 읽기
    const s3Object = await s3.getObject({
        Bucket: sourceBucktName,
        Key: uploadFileName
    }).promise()
  
  // 이미지 리사이즈, sharp 라이브러리가 필요합니다.
    const data = await sharp(s3Object.Body)
        .resize(200)
        .jpeg({ mozjpeg: true })
        .toBuffer()
  
  // 대상 버킷으로 파일 쓰기
    const result = await s3.putObject({
        Bucket: destinationBucktName, 
        Key: uploadFileName,
        ContentType: 'image/jpeg',
        Body: data,
        ACL: 'public-read'
    }).promise()

    return 'Hello from Lambda!';
}

 

3. 

sam build
sam deploy --guided

 

4. AWS SQS에 대기열 생성, 표시제한시간은 100초 이상으로 설정

5. S3에 버킷 생성 (ACL 활성화 및 모든 퍼블릭 엑세스 차단)

 

6. 만든 s3버켓 클릭 -> 이벤트알림 -> 이벤트알림생성

다음과 같이 생성하려면 오류가 뜬다.

 

6. 일단 다시 SQS로 들어와서 SQS와 람다를 연결한다

다음과 같은 오류가 뜬다....

함수의 실행권한이 없는 것이다.

 

7. 만든 람다 -> 구성 -> 권한 -> 실행역할 -> 역할이름(파란색 링크 클릭)

다음과 같이 추가해주자

 

그리고 다시 함수 트리거를 설정해주면 다음과 같이 잘 붙는다.

 

8. SQS -> 엑세스 정책 수정

정책을 다음과 같이 수정한다.

"Service" : "s3.amazonaws.com"만 추가해주면 된다.

{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__owner_statement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::485879423137:root",
        "Service" : "s3.amazonaws.com"
      },
      "Action": "SQS:*",
      "Resource": "arn:aws:sqs:ap-northeast-2:485879423137:s3-event-queue"
    }
  ]
}

위 정책을 해석하면,

" s3-event-queue의 정책은 SQS의 모든 액션을 실행할 수 있다.

 그 주체는 485879423137이다. s3.amazonaws.com 서비스가 SQS에서 실행할 수 있게 한다."

 

9. S3에 다시 돌아와서 변경사항 저장을 하면, 이벤트가 붙은 것을 볼 수 있다.

 

10. S3에 업로드를 하면, 타켓 버킷에 리사이즈 된 이미지가 안온다. 클라우드 워치를 이용해서 로그 기록을 확인해보자.

사진을 하나만 올렸는데 큐를 계속 보낸다. 오류를 해결하기 전에 잠깐 조취를 취해보자.

 

11. 대기열을 추가 생성 ->SQS에서 배달 못한 편지 대기열에서 편집을 눌러주자

활성화 후 s3-event-queue를 선택.

 

12. 소스S3에 다시 사진을 올려서 로그를 확인해보자

에러는 다음과 같이 2개가 나온다.

 

처음 나오는 에러

{
    "Records": [
        {
            "messageId": "baea43ea-c55c-4030-8918-c9a35b3ae594",
            "receiptHandle": "AQEBW/TlbE+Wpfpzc7i2slHqk1WKZ2KyZm0kfqxSZAJN94W7yHzRs/b/OLN6RwNjRCN+vSSuzpe7PFspEbWfTbxvSnHuKeyxVRgD0V5aDlmGLz5GR6biIghezRHwVcAKgUIuoBCus14wBNJ8Y7+FoaUt/sBg82HYfktWJtuHvTvbPquTmpNMzpat9wM6T0W3oMc9HGunbFAYxzknhV1GQSJkgz9oD+lU2E/jPiEnqkQZEhrgh7ZTd4RQD8BB+oxDd1ojwcg0HYjcAlEikGi/27UHGz5FJe5sfFU5rL5QyttLbagS5uXZpmsZB8j2PgkOyRm2O9jjD5WYjSKYhjg78bxhFH9g033FDp48Ek8aCkcEDehZ9AC4VDfGrvEn97nQ/S+Y56Hb1aOG9v4FNEhFaJnMqA==",
            "body": "{\"Service\":\"Amazon S3\",\"Event\":\"s3:TestEvent\",\"Time\":\"2022-04-14T12:52:02.681Z\",\"Bucket\":\"s3-image-resize-sqs\",\"RequestId\":\"QAYPBCNNHF3QB2QQ\",\"HostId\":\"4t75X5MDNJ1rultDtRFxEDBF6ycBHZnhtgXcHLml8uLsSTDqKFNimcFLgKOGxWtopMWIVbi5ovk=\"}",
            "attributes": {
                "ApproximateReceiveCount": "12",
                "SentTimestamp": "1649940722709",
                "SenderId": "AIDAJCPXNKW2CHJMM46DC",
                "ApproximateFirstReceiveTimestamp": "1649940722709"
            },
            "messageAttributes": {},
            "md5OfBody": "2c1c9268490e1eab30c0fdd217c00af3",
            "eventSource": "aws:sqs",
            "eventSourceARN": "arn:aws:sqs:ap-northeast-2:485879423137:s3-event-queue",
            "awsRegion": "ap-northeast-2"
        }
    ]
}

두번째 나오는 에러

{
    "errorType": "TypeError",
    "errorMessage": "Cannot read property 'bucket' of undefined",
    "stack": [
        "TypeError: Cannot read property 'bucket' of undefined",
        "    at Runtime.exports.helloFromLambdaHandler [as handler] (/var/task/src/handlers/hello-from-lambda.js:9:49)",
        "    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)"
    ]
}

세번째 나오는 에러

{
    "Records": [
        {
            "messageId": "0b79d2bd-8db2-46fa-abaa-d47983b4d1ca",
            "receiptHandle": "AQEBB+tDbKKc2XitdoDildFCXzWyEwNPurO1MV7o5z5nB94oAd01bo6n5LPtgTugZl0hqptuFeOtl+34fRpgVvsZFBhgh7o0LfapMAeWjN3HLoERhmSBiPUh/rdc2UwcGxFHcC1T5r8ZZh2+/4Y0qGoUhUMpLjvgJNLH7G+xuu/xzPywW0PE21IxHpzP27ogp6Uiy5MN+xTXTvL9fREnEr/5HHJPP4BdVncw82GdkzMG2dDVeg+op5E3udrXsR9nRIHhHmKroQ+sK0WT2GBP+/5kTuqXvMLQm21r4Fxekcn7qKQcjFF43QvSEa/Gb1x1ozJcYoGZQvB4/VQ3PKWPvYr2oENQg25rvgR4xnaNqJnkk+7iTMpmZUomdYvX9OJD2Q/4K/PkRIcVsLLrguSxVq3QPg==",
            "body": "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"ap-northeast-2\",\"eventTime\":\"2022-04-14T13:10:27.969Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"AMAW5L9ADIVTO\"},\"requestParameters\":{\"sourceIPAddress\":\"116.125.64.98\"},\"responseElements\":{\"x-amz-request-id\":\"299J91MTX95D2Z6B\",\"x-amz-id-2\":\"C8QrQSEnjLAkQvIbGgistRxC0KRqxhfGDYsbcc56/z/hTBZWmYQgZ5cS5ISvHVmXZ8iMXuYs7IB5iRhFodmFxO8jn8eNjcwF\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"image-event-sqs\",\"bucket\":{\"name\":\"s3-image-resize-sqs\",\"ownerIdentity\":{\"principalId\":\"AMAW5L9ADIVTO\"},\"arn\":\"arn:aws:s3:::s3-image-resize-sqs\"},\"object\":{\"key\":\"summer.jpg\",\"size\":2878872,\"eTag\":\"0fedc4356e5364746d677ae083b13299\",\"sequencer\":\"0062581D43D2E3929F\"}}}]}",
            "attributes": {
                "ApproximateReceiveCount": "1",
                "SentTimestamp": "1649941829495",
                "SenderId": "AIDAJCPXNKW2CHJMM46DC",
                "ApproximateFirstReceiveTimestamp": "1649941829500"
            },
            "messageAttributes": {},
            "md5OfBody": "57128a9d39f420543631bb9a49cf7f42",
            "eventSource": "aws:sqs",
            "eventSourceARN": "arn:aws:sqs:ap-northeast-2:485879423137:s3-event-queue",
            "awsRegion": "ap-northeast-2"
        }
    ]
}

처음 나오는 에러는 테스트이벤트 에러이고, 두번째 나오는 에러는 버켓을 찾을 수 없다고 뜨고, 세번째는 우리가 원하는 Event값이 이 들어가 있다.

 

개발자도구 -> 콘솔로그에서 테스트 이벤트 body값을 뽑아보자

테스트이벤트값은 필요없으므로 코드상으로 처리를 해주어야 한다.

Event : "s3:TestEvent"를 잡아주어야 한다.

 

    // event는 sqs로부터 오는 이벤트
    event = JSON.parse(event.Records[0].body)
    console.log(JSON.stringify(event)) //JSON.stringify로 모든 로그가 찍힐 수 있게끔 한다

    //테스트이벤트값 처리
    if(event.Event === 's3:TestEvent'){
        console.log('test event 입니다')
        return;
    }

그럼 완성된 코드는 다음과 같다.

 

Full Code

const AWS = require('aws-sdk');
AWS.config.update({region: 'ap-northeast-2'});
const s3 = new AWS.S3({apiVersion: '2006-03-01'});
const sharp = require('sharp')

exports.helloFromLambdaHandler = async (event, context) => {

    // event는 sqs로부터 오는 이벤트
    event = JSON.parse(event.Records[0].body)
    console.log(JSON.stringify(event)) //JSON.stringify로 모든 로그가 찍힐 수 있게끔 한다

    //테스트이벤트값 처리
    if(event.Event === 's3:TestEvent'){
        console.log('test event 입니다')
        return;
    }

    //event는 s3로 오는 이벤트로 재설정
    const sourceBucktName = event.Records[0].s3.bucket.name
    const uploadFileName = event.Records[0].s3.object.key
    const destinationBucktName = 'photo-destination-birdnine'

    console.log(sourceBucktName)
    console.log(uploadFileName)
    // 원본 버킷으로부터 파일 읽기
    const s3Object = await s3.getObject({
        Bucket: sourceBucktName,
        Key: uploadFileName
    }).promise()
  
  // 이미지 리사이즈, sharp 라이브러리가 필요합니다.
    const data = await sharp(s3Object.Body)
        .resize(200)
        .jpeg({ mozjpeg: true })
        .toBuffer()
  
  // 대상 버킷으로 파일 쓰기
    const result = await s3.putObject({
        Bucket: destinationBucktName, 
        Key: uploadFileName,
        ContentType: 'image/jpeg',
        Body: data,
        ACL: 'public-read'
    }).promise()

    return 'Hello from Lambda!';
}

로컬로 테스트를 해보자.

test-event.json 에는 테스트이벤트 로그를, real-event.json 에는 s3주소가 적혀있는 로그값을 넣는다.

 

sam build
sam local invoke -e ./test-event.json

테스트가 잘 됬다면 deploy를 하자

 

sam deploy

그리고 s3에 다시 사진을 넣어본다

 

그리고 다시 나는 에러

 

거의 다왔다...

 

구성 -> 권한 -> 실행역할 -> 역할이름(파란색 링크 클릭)

 

S3FullAccess권한을 넣는다

 

잘 들어가진다!

 

타겟 버킷에도 잘 들어가있다.

 

추가로....

람다가 잘 실행되면 SNS를 통해 이메일을 받아보자.

1. SNS -> 주제 생성

2. 표준으로 설정

3. 구독 -> 구독 생성

4. 이메일 선택

5. 이메일 인증

6. 람다로 돌아가서 대상추가

7. 성공하면 발송되게끔 설정해본다. (원래는 실패시 전송이 일반적이다)

8. 다시 소스 s3에 파일을 올리면 타겟s3에 resize된 사진이 올라오고, 메일에도 오는 것을 볼 수 있다.