AWS 서버리스 애플리케이션 배포 실습

cch_chan·2022년 4월 12일
0

AWS

목록 보기
3/5
post-thumbnail

기본 이론

AWS Lambda란?
Lambda는 AWS가 제공하는 서버리스 FaaS 솔루션으로, 함수의 인스턴스를 실행하여 이벤트를 처리합니다.

사용해야할것 API 게이트웨이, S3, SQS
추가적으로 Application LoadBalancer, cloudWatch 모니터링 부분에서 추가 사용
cloudWatch lamda 사용에 필수적 -로그확인

API Gateway를 사용하는 이유?
마이크로서비스의 사용 증가로 인해 API 게이트웨이가 더 많이 사용되고 있습니다. 각 마이크로서비스는 자체 기능을 필요로 하기 때문에 애플리케이션을 느슨하게 결합된 여러 서비스로 분해될 수 있습니다. 한편, 마이크로서비스를 사용하면 애플리케이션의 다양한 기능을 더 쉽게 개발, 배포 및 유지 관리할 수 있지만 고객이 애플리케이션에 빠르고 안전하게 액세스하기가 더 어려워질 수도 있습니다. 이 때 API 게이트웨이가 이 문제를 해결할 수 있습니다. 고객이 각 마이크로서비스에 대한 액세스를 개별적으로 요청하는 대신 게이트웨이는 요청에 대한 단일 진입점(entrypoint)으로, 해당 요청을 적절한 서비스에 연결하고 결과를 수집하여 요청자에게 다시 전달하게 도와줍니다. 이 기능을 라우팅이라고 하며, API Gateway의 주요 기능 중 하나입니다.

API Gateway 기능 :

  • 다른 기능의 람다를 각각 라우팅 가능
  • api키, 권한 부여자 기능을 추가해서 보안, 인증강화가능 (포스트맨 인증키 추가랑 비슷함)
  • 새로운 릴리즈/ 다양한 Stage API를 배포할 수있음

API Gateway 장점 :

  • 트래픽 측정 (API에 대한 트래픽을 측정하기 때문에 각 API 키에 대한 사용률 데이터를 추출할 수 있다.)
  • 실시간 양방향 통신 (서버를 실행하거나 관리할 필요 없이 채팅 앱, 스트리밍 대시보드 및 알림 같은 실시간의 양방향 통신 애플리케이션을 구축가능)

HTTP API, REST API 차이점 파악

SAM(serverless application model)
대표적인 기능
SAM이 제공하는 대표적인 기능으로는 다음과 같습니다.

  • 한번에 배포
    Lambda 함수, API Gateway 등의 리소스를 CLI 명령어 한번으로 배포가 가능하게 만들 수 있습니다.
  • 로컬에서의 테스트
    꼭 배포하지 않아도 로컬 환경에서 테스트가 가능합니다.
    AWS CloudFormation 기능을 이용한 단일 작업을 통한 리소스(인프라) 관리
    AWS CloudFormation은 이후에 배우게 될 Terraform과 같은 Infrastructure as Code 도구입니다

SAM 대안
serverless 범용성이 좀 더 좋다 멀티 클라우드에
terraform 사용할 수 있지만 서버리스위한 툴은아님

실행 속도 문제
cold start/warm start

언제 Cold start가 발생하는가?

  • 구성이 변경되거나, 코드가 새롭게 배포 되었을때
  • 한동안 함수가 호출되지 않을때

어떻게 하면 Warm 상태를 유지하는가?

  • cloundWatch EventBridge를 트리거 삼아 주기적 호출
  • 로드밸런서의 타겟을 람다로 연결해 health check를 하게 만듦
  • Provisioned Concurrency 기능 사용

sam으로 기본적인 Hello World 애플리케이션 배포

SAM 설치(Mac 기준)

SAM 설치 전 사전 조건

  • AWS 계정 생성
  • IAM 권한 및 AWS 자격 증명
    터미널에 AWS configure를 친후 IAM에서 발급받은 aws 인증키를 입력해줘서 인증해줘야함
  • docker 설치
  • homebrew 설치
  • aws sam cli 설치
brew tap aws/tap
brew install aws-sam-cli

설치 후 잘 깔려있는지 확인

sam --version

HelloWorld 출력하는 애플리케이션 배포
참고
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html#serverless-getting-started-hello-world-test-locally

sam local start-api 
curl http://127.0.0.1:3000/hello 

서버 실행 후 hello api 실행결과


Sprint1) API Gateway를 활용한 서버리스 애플리케이션 배포

API Gateway - Lambda - DyanmoDB 구조를 가지는 애플리케이션 배포 해보기

step 1: API Gateway - Lambda 배포 Instruction

  • git clone 하기
git clone https://github.com/aws-samples/serverless-patterns/ 
cd serverless-patterns/lambda-dynamodb
  • 현재 람다가 런타임 node 14.x를 지원하므로 template.yaml 파일 수정
Runtime: node.js14.x
  • 변경사항 sam build

  • sam 으로 배포

sam deploy --guided

잘 동작 하는지 확인

aws lambda invoke --function-name arn:aws:lambda:ap-northeast-2:652217438494:function:sam-app-LambdaPutDynamoDB-gJozNWaUjUS5 --invocation-type Event \
--payload '{ "Metadata": "Hello" }' \ response.json --cli-binary-format raw-in-base64-out

202 state가 도착하면 성공!

step 2. lambda 추가 트리거 api 게이트웨이 생성

  • api 게이트웨이 선택
  • 새 api 생성
  • 보안은 열기

생성 확인

Step 3 : API 게이트웨이에 제한 추가

  • Post 전용으로만 작동하게 만들기
  1. apigateway로 가서 Post메소드 추가
  2. lambda함수 입력 추가
  3. 작업 -> API배포
  4. Postman으로 Post 엔드포인트 입력후 바디 입력 후 Send
  5. 잘 작동하는지 DynamoDB 항목탐색에서 확인

순서
1. apigateway에서 apikey 자동 생성
2. 사용량 계획 생성
3. 리소스 메소드실행에서 apikey필요함 ture로 변경
4. postman에 헤더 x-api-key 벨류 값으로 생성된 apikey 입력 후 테스트
5. OK가 나오면 성공

순서
1. 함수생성
2. 함수값 입력 (aws공식문서)


// A simple token-based authorizer example to demonstrate how to use an authorization token 
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke 
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke 
// the request if the token value is 'deny'. If the token value is 'unauthorized' or an empty
// string, the authorizer function returns an HTTP 401 status code. For any other token value, 
// the authorizer returns an HTTP 500 status code. 
// Note that token values are case-sensitive.

exports.handler =  function(event, context, callback) {
    var token = event.authorizationToken;
    switch (token) {
        case 'allow':
            callback(null, generatePolicy('user', 'Allow', event.methodArn));
            break;
        case 'deny':
            callback(null, generatePolicy('user', 'Deny', event.methodArn));
            break;
        case 'unauthorized':
            callback("Unauthorized");   // Return a 401 Unauthorized response
            break;
        default:
            callback("Error: Invalid token"); // Return a 500 Invalid token response
    }
};

// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
    var authResponse = {};
    
    authResponse.principalId = principalId;
    if (effect && resource) {
        var policyDocument = {};
        policyDocument.Version = '2012-10-17'; 
        policyDocument.Statement = [];
        var statementOne = {};
        statementOne.Action = 'execute-api:Invoke'; 
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    
    // Optional output with custom properties of the String, Number or Boolean type.
    authResponse.context = {
        "stringKey": "stringval",
        "numberKey": 123,
        "booleanKey": true
    };
    return authResponse;
}
  1. 권한 부여자 탭으로 이동하여 새로운 권한 부여자 생성
  2. 토큰 형식에 토큰 소스로 'authorizationToken'입력 후 생성
  3. test값에 allow 입력하여 200이 나오면 테스트성공
  4. 리소스 메소드실행에서 승인에 이번에 만든 토큰 권한 입력 후 배포
  5. postman에 'authorizationToken' 헤더 값으로 입력 후 벨류로 allow 입력 후 포스트
  6. OK나오면 성공

Sprint2) 서버리스 사진첩 서비스

주요 기능

우리가 만드려는 서버리스 사진첩 서비스는, 여느 클라우드 사진 저장 서비스들처럼 단순히 사진을 업로드하는 것 외에도, 인증 기능과 썸네일 생성 기능을 제공합니다.
S3->lambda->sns

Bare Minimum Requirements

  • 이미지가 업로드되면, 원본과 별도로 썸네일을 생성하고, 이를 별도의 버킷에 저장해야 합니다.

    썸네일 이미지는 가로 200px의 크기를 가집니다.
    썸네일을 저장할 별도의 버킷은 람다 함수의 환경 설정으로 구성되어야 합니다.

  • 썸네일 생성이 완료되면, 메일로 해당 썸네일 URL과 함께 전송이 되어야 합니다.

    Amazon SNS를 활용합니다

Setting
터미널에서 sam init 로 샘플 받아온 후 람다 함수 변경하고 빌드 후 배포

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

    console.log(context)

    return 'Hello from Lambda!';
}
  • 좀더 자세히 보고싶으면 JSON.stringify(event)

이후 s3 생성 후 트리거 추가로 연결 후(접미사: .jpeg추가) 람다함수 테스트 (s3.put)로 실행되는 함수 로그 확인하여 완성시키기


참고: https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/with-s3-tutorial.html

로컬에서 테스트
JSON.stringify(event) 테스트 실행 로그를 가져와 json파일로 만들어 아래 명령어 실행

sam local invoke -e ./s3-event.json

사전 모듈 설치
aws-sdk: s3버킷을 불러오는데 필요
sharp: 썸네일을 만드는데 필요

npm install aws-sdk
npm install sharp

해결하는데 힘들었던 점

1) not find sharp : m1의 환경에서만 npm i sharp를 했음에도 모듈을 못찾는다고 나오는 현상
-> 패키지에서 sharp를 제거하니 해결

"dependencies": {
        "aws-sdk" : "^2.1111.0",
        "sharp": "^0.30.3" //<-제거
    }

2) "errorType": "AccessDenied"
s3 정책에서 허용하지 못한다고 나오는 오류

  • iam에서 s3에 필요한 정책권한 역할생성 후 람다함수 - 권한 - 실행역할편집에서 생성한 역할 등록 후 적용 하여 해결
  • s3 권한 엑세스 허용
  • s3 acl활성화

3) 대상 버킷 환경변수 설정
구성 -> 환경 변수 -> 편집 -> 키, 값 입력
람다 함수에서 process.env.key값 입력

4) 람다함수 완성 후 sns서비스와 연결
sns주제 일반으로 생성 후 프로토콜 이메일 엔드포인트 : 이메일 주소

5)sns에 s3 url 주소 담아서보내기

const message = {
    Message: "URL : "+`https://${process.env.s3resized}.s3.${region}.amazonaws.com/${uploadFileName}`,
    Subject: "썸네일 도착",
    TopicArn : process.env.topicArn
  }

TopicArn : AWS-SNS:ARN
6) sns 메일 url이랑 빈메일 오는거 해결하기
람다와 sns를 추가 할 때 대상추가에서 조건을 성공시에서 실패시로 변경

Bare Mininum Requirements 완성!

s3에 jpeg업로드 했을때

/**
 * A Lambda function that returns a static string
 */
 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) => {
 const bucketName = event.Records[0].s3.bucket.name
 const uploadFileName = event.Records[0].s3.object.key
 const region = event.Records[0].awsRegion
 console.log("*******"+JSON.stringify(event))
 console.log("-------"+JSON.stringify(context))

 // 원본 버킷으로부터 파일 읽기
 const s3Object = await s3.getObject({
   Bucket: bucketName,
   Key: uploadFileName
 }).promise()
 
 // 이미지 리사이즈, sharp 라이브러리가 필요
 const data = await sharp(s3Object.Body)
     .resize(200)
     .jpeg({ mozjpeg: true })
     .toBuffer()
 
 // 대상 버킷으로 파일 쓰기 (*일단 대상버킷 똑같이)
 const result = await s3.putObject({
   Bucket: process.env.s3resized, 
   Key: uploadFileName,
   ContentType: 'image/jpeg',
   Body: data,
   ACL: 'public-read'
 }).promise()
  
  const sns = new AWS.SNS(region);
  const massage = await sns.publish({
    Message : "URL : "+`https://${process.env.s3resized}.s3.${region}.amazonaws.com/${uploadFileName}`,
    Subject: "썸네일 도착",
    TopicArn : process.env.topicArn
}).promise()
 }

Advanced Challenge
과제를 달성하면, S3 이벤트가 SQS로 전송되게 만들고, SQS로부터 이벤트를 받아 람다가 실행하게 만들어봅시다.
S3의 Pre-signed URL 기능을 이용하여, 업로드 전용 URL을 획득하고, 이를 통해 이미지를 S3 업로드할 수 있게 만들어봅시다.

7) sqs 람다 트리거 연결
람다에서 sqs풀엑세스 역할 지정후 트리거 추가로 sqs연결

8) s3에서 sqs 대기열 이벤트 생성불가
Error: Unable to validate the following destination configurations
sqs 엑세스 정책 변경

      "Principal": {
        "AWS": "arn:aws:iam::652217438494:root",
        "Service" : "s3.amazonaws.com"
      }

Service 추가

9) 실패하는 메시지 처리
sqs에서 보내는 실패하는 메시지는 발신자를 찾지못해 무한 루프하게됨
(처리해주지 않으면 aws 과금발생)
해결법
sqs에서 실패하는 메시지 담을 대기열 생성 후 배달 못한 편지 대기열 활성화 후 대기열 추가

10) 에러메시지 : "Cannot read property 'bucket' of undefined"
sqs에서 오는 이벤트값이 json형태가 아니여서 bucket값을 찾지 못하는 상태 콘솔에서 에러메시지 확인 후 바디에 값있는걸 확인 후
event값을 json형으로 변경

event = JSON.parse(event.Records[0].body)

11) "errorMessage": "SyntaxError: Identifier 'event' has already been declared"
event를 상수로 2번 지정해서 난 오류
event값을 json으로 변경해주는 과정에서 event를 const event로 한번 더 지정해서 난 오류 였음.. (내 실수ㅜ)

profile
꾸준히 새로운 기술을 배워나가는중입니다.

0개의 댓글