서버리스, 람다, SQS를 이용한 프로젝트

문한성·2023년 5월 24일
1

부트캠프

목록 보기
94/123
post-thumbnail

프로젝트 개요

AWS 클라우드 환경을 기반으로 하는  느슨하게 연결된 (loosely coupled) 어플리케이션 아키텍처에 대한 이해

최소 요구 사항

Serverless 를 이용한 메시지 대기열 활용 이해 및 구현

요구사항에 따른 어플리케이션과 인프라 구현

문제사항 해결을 위한 추가 리소스 생성 -> DLQ, Legacy 시스템 성능문제 해결, SES

아키텍처 다이어그램 제작

Advanced

인프라 관리와 재사용성을 위한 IaC 활용 -> Terraform을 통한 리소스생성


프로젝트 요구사항 및 시나리오

프로젝트 명 : <자동 재고 확보 시스템> 을 위한 MSA 구성


시나리오

<도넛-스테이츠>는 온라인으로 도너츠를 판매합니다.

웹사이트 통해서 주문 버튼을 누르는 것으로 구매(Sales API)가 가능합니다.

창고에 재고가 있다면 재고가 감소하고 구매가 완료됩니다.

한 유튜버가 도넛-스테이츠의 도너츠가 맛있다고 영상을 올려 주문이 급등하였습니다.

창고에 재고가 없어 구매가 불가능한 경우 제조 공장에 알려 다시 창고를 채우는 시스템을 구축해야합니다.

제조 공장인 <팩토리>에 주문을 요청 (Lagacy Factory API)할 수 있습니다.

주문이 요청되면 일정 시간이 지난 후 창고에 재고가 증가합니다.

구성요소

  1. Sales API

2. Factory API

3. 프론트엔드 (웹사이트) : cURL / Postman / k6 등을 통한 API 호출로만 구현

- Sales API를 통해 백엔드에 요청

4. 백엔드 (서버) : 구매 시 창고에서 재고 확인 후 재고 감소 로직 구현

- 재고가 부족할 경우 Factory API를 통해 재고 확보 요청

5. 데이터베이스 (창고) : RDS에 mysql db 구성

- 요청에 따른 재고 상태 변경


Day 1 (Tutorial)

목표

Serverless 를 이용한 AWS 리소스 생성

메시지 Queue가 사용되는 구조 이해


Step 1 : Serverless 를 이용한 Lambda 생성

(Serverless framework 를 이용하여 Lambda 함수 생성 및 배포)

#1. Serverless 튜토리얼을 통한 프로젝트 생성

  • 서버리스 프레임워크 설치 및 새 서비스 생성
% npm install -g serverless
// node와 npm이 설치된 상태에서 Serverless Framework 를 글로벌 모듈로 설치하는 것을 권장함

% serverless
// AWS - Node.js - Starter       // 생성 템플릿 선택
// Project name 은 원하는대로 작성
// Do you want to deploy now?  N   // 답변 no로 하기 - yes로 하면 기본 리전으로 바로 배포됨
  • AWS 자격증명 원하는 방식대로 진행 (필자는 로컬 AWS 자격 증명 사용함)
  • 가끔 aws configure list 로 aws 설정 확인

#2. Serverless.yaml 레퍼런스를 참고하여 리전 변경

  • serverless.yaml 파일 수정
service: aws-node-project
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs18.x
  region: ap-northeast-2    // 리전 서울로 변경

functions:
  function1:
    handler: index.hello
# The `events` block defines how to trigger the handler.helloWorld code
    events:
      - http:
          path: hello       // index.js 함수 연결
          method: post      // post 메소드를 확인 예정
          cors: true

#3. handler.js 편집 (서버구현)

module.exports.hello = async (event) => {
  let inputValue, outputValue
  console.log(event.body)

  if (event.body) {

    let body = JSON.parse(event.body)

// 추가 도전과제: body가 { input: 숫자 } 가 맞는지 검증하고, 검증에 실패하면 응답코드 400 및 에러 메시지를 반환하는 코드를 넣어봅시다.

    inputValue = parseInt(body.input)
    outputValue = inputValue + 1
  }

  const message = `메시지를 받았습니다. 입력값: ${inputValue}, 결과: ${outputValue}`

  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message
      },
      null,
      2
    ),
  };
};

#4. serverless deploy 를 통한 배포

  • serverless deploy
%cd <serverless 프로젝트 템플릿 폴더>
// serverless 프로젝트 진입 후 deploy 진행

% serverless deploy
// deploy 완료 후 AWS console 내 lambda, cloudWatch, s3 등 생성 확인

!https://blog.kakaocdn.net/dn/bhMqXc/btrZNutNhPo/gIqfWD7a4tSEqg2nxKxv2K/img.png

#5. cURL 을 통한 테스트 : 입력값의 +1 반환 확인

% curl -X POST https://API_GATEWAY_ID.execute-api.ap-northeast-2.amazonaws.com/dev/hello --header 'Content-type: application/json' --data-raw '{ "input": 1 }'

응답

{ "message": "메시지를 받았습니다. 입력값: 1, 결과: 2" }

#6. 튜토리얼 완료 후 서버리스 삭제

% serverless remove

// deploy로 배포된 것들 삭제됨

오류

{"message":"Missing Authentication Token"}

오류 출력 시 오타 또는 URL 뒷부분 /dev/hello 경로 확인

Serverless 튜토리얼 참고 링크

https://www.serverless.com/framework/docs/tutorial


Tutorial: Your First Serverless Framework Project
The Serverless Framework documentation for AWS Lambda, API Gateway, EventBridge, DynamoDB and much more.
www.serverless.com


Step 2 : Serverless를 이용한 Lambda -SQS - Lambda 구조 생성

#1. serverless 프레임워크 설치 확인

% sls --version

#2. serverless 프로젝트 생성

% serverless       // sls  => 프로젝트 생성// AWS - Node.js - SQS Worker// Project name 은 적당하게 설정// Do you want to deploy now?  N     // no 로 선택하기 - yes로 하면 기본값으로 deploy % cd <생성 폴더 이름>// sls 프로젝트 진입% code .    // visual code 로 작업할 경우

#3. region 추가 (serverless.yaml)

provider:
  name: aws
  runtime: nodejs18.x
  region: ap-northeast-2

#4. 배포 진행 및 클라우드 포메이션 확인

% serverless deploy // jobsWorker (컨슈머) 와 producer 두 함수가 생성된다

#5. 생성된 함수 별 트리거 확인

  • Producer 트리거 -> API Gateway 생성 확인

  • JobsWorker 트리거 -> SQS 생성 확인

#6. Producer Test

  • 소스코드를 확인해보니 body 라는 키-값을 필요로함
  if (!event.body) {
    return {
  • Producer Lambda 내 Test Event 생성 -> 테스트 실행

  • 실행결과 확인

  • Jobs Worker Lambda의 View CloudWatch logs (최근 생성된 로그 스트림 내 hello-world)

#7. handler.js (index.js)편집 - 컨슈머 구현

  • 이후 serverless deploy
const { SQSClient, SendMessageCommand } = require("@aws-sdk/client-sqs");
const sqs = new SQSClient();

const producer = async (event) => {
  let statusCode = 200;
  let message;

  if (!event.body) {
    return {
      statusCode: 400,
      body: JSON.stringify({
        message: "No body was found",
      }),
    };
  }

  try {
    await sqs.send(new SendMessageCommand({
      QueueUrl: process.env.QUEUE_URL,
      MessageBody: event.body,
      MessageAttributes: {
        AttributeName: {
          StringValue: "Attribute Value",
          DataType: "String",
        },
      },
    }));

    message = "Message accepted!";
  } catch (error) {
    console.log(error);
    message = error;
    statusCode = 500;
  }

  return {
    statusCode,
    body: JSON.stringify({
      message,
    }),
  };
};

const consumer = async (event) => {
  /*
  for (const record of event.Records) {
    const messageAttributes = record.messageAttributes;
    console.log(
      "Message Attribute: ",
      messageAttributes.AttributeName.stringValue
    );
    console.log("Message Body: ", record.body);
  }
  */
  for (const record of event.Records) {
    console.log("Message Body: ", record.body);

    let inputValue, outputValue
    // TODO: Step 1을 참고하여, +1 를 하는 코드를 넣으세요
    if (record.body) {

      let body = JSON.parse(record.body)

      // 추가 도전과제: body가 { input: 숫자 } 가 맞는지 검증하고, 검증에 실패하면 응답코드 400 및 에러 메시지를 반환하는 코드를 넣어봅시다.

      inputValue = parseInt(body.input)
      outputValue = inputValue + 1
    }

    const message = `메시지를 받았습니다. 입력값: ${inputValue}, 결과: ${outputValue}`
    console.log(message)

  }
};

module.exports = {
  producer,
  consumer,
};

#8. cURL을 통한 테스트

% curl -X POST https://API_GATEWAY_ID.execute-api.ap-northeast-2.amazonaws.com/produce --header 'Content-type: application/json' --data-raw '{ "input": 1 }'

응답

{"message":"Message accepted!"}

오류

{"message":"Not Found"}

오류 출력 시 URL 뒷부분 serverless.yaml 의 fucntions path인  /produce 경로 확인

#9. 쉘 스크립트의 반복문을 이용한 반복 실행 확인

#!/bin/bash

for i in {1..5}

do

echo curl -X POST https://API_GATEWAY_ID.execute-api.ap-northeast-2.amazonaws.com/ --header 'Content-type: application/json' --data-raw '{ "input": 1 }'

done

쉘스크립트 반복문 참고 링크

https://www.cyberciti.biz/faq/bash-for-loop/

#10. CloudWatch를 통해 컨슈머가 메시지를 소비하는 것 확인


Step 3 : DLQ 연결 및 K6 성능 테스트

DLQ 란?

  • Dead Letter Queue
  • 메시징 시스템에서 메시지를 처리하지 못한 경우, 메시지를 보관하는 대기열을 의미함
  • DLQ는 메시지 처리 실패 시 수동으로 재처리하거나, 다른 서비스나 시스템으로 전달하여 처리함
  • 메시지 처리 실패에 대한 안정성과 신뢰성을 향상시킬 수 있음

K6 란?

  • AWS에서 제공하는 로드 테스트 도구 중 하나인 K6를 실행하는 데 사용되는 AWS Lambda Layer

#1. deplay 함수 추가 및 소스코드 수정

  • 기존 index.js 내 소스코드 수정
  • await delay(5000) 부분을 수정하며 테스트 진행 예정
function delay(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

const consumer = async (event) => {
  await delay(5000)
  for (const record of event.Records) {
    console.log("Message Body: ", record.body);

// 생략

#2. 재배포 후 람다 및 SQS, SQS DLQ 생성 확인 (queue 2개 생성 확인 - 프로듀서, 컨슈머)

  • 추가로 CloudWatch 도 생성확인

% sls deploy// step 2 단계를 다시 deploy 한다.  // 프로듀서와 컨슈머 생성 확인 (AWS Lambda)

#3. Lambda -> Application -> 리소스 에서 생성된 리소스 확인

#4. Producer 테스트

  • JSON 형식으로 -> body에 메시지를 작성하여 테스트 이벤트 실행

  • 컨슈머 (jobs worker) 에서 body 정상 수신 확인

#5. 함수 실행 지연함수 delay 수정 후 재 테스트

  • index.js 내 await delay(5000)을 15000으로 수정 (15초) ==> 재배포 sls deploy
  • Producer 테스트 이벤트 재실행

  • CloudWatch 컨슈머 로그 확인

=> 타임아웃에 걸려 람다가 제대로 실행되지 않았단 뜻

=> 람다함수는 최소 15초 이상의 시간이 필요함 (delay를 15초를 걸었으므로)

=> 지금 이 람다 함수의 실행 제한 시간이 6초 이므로 타임아웃이 걸림

  • SQS 에 연결된 소비자는 주기적으로 폴링을 하기 때문에 같은오류가 반복적으로 출력됨

  • Producer 람다 -> 구성 -> 일반구성 -> 제한시간

=> 현재 6초로 설정되어 있음 (최대 15분)

#6. SQS 확인

  • 메시지 소비 실패 시 jobs의 '이동중인 메시지'에 숫자가 카운팅 되며
  • 반복적 메시지 소비 최종 실패 시 jobs-dlq 의 사용 가능한 메시지 숫자로 카운팅 된다

  • sqs내 jobs -> 클릭 -> 자세히 -> 내용 확인

=> 기본 표시 제한시간 Visibility Timeout (36s)

  • sqs 내 jobs -> 배달 못한 편지 대기열 -> 최대 수신 수 (3)

=> 이전 테스트에서 3회 시도 후 dlq 로 넘어갔던것 확인 했었음

#7. SQS DLQ 확인

  • SQS 내 jobs-dlq -> 클릭 -> 메시지 전송 및 수신

  • 사용 가능한 메시지 확인 후 우측 메시지 폴링 클릭 (콘솔에서도 메시지 폴링이 가능하다)

  • 하단부에서 미시지 확인

=> 이전에 작성했던 메시지 확인 가능

#8. SQS DLQ 원리

  • 일반 큐에서 메시지를 수신 했으나 최종적으로 소비되지 못하여 (재시도 회수 까지도) 지워지지 않은 메시지가 DLQ로 넘어간것

#9. K6 성능 테스트 도구 활용 (프로듀서 반복 실행 및 테스트)

  • k6 설치
% brew install k6
  • 제공받은 run.sh , single-request.k6.js
// run.sh
#!/bin/bash
k6 run -u 1 -i 100 ./single-request.k6.js

// single-request.k6.js
import http from 'k6/http';
import { sleep, check } from 'k6';

// you can specify stages of your test (ramp up/down patterns) through the options object
// target is the number of VUs you are aiming for

export const options = {
  stages: [
    { target: 20, duration: '20s' },
    { target: 15, duration: '20s' },
    { target: 0, duration: '20s' },
  ],
  thresholds: {
    http_reqs: ['count <= 100'],
  },
};

export let input = 1

export default function () {
  // our HTTP request, note that we are saving the response to res, which can be accessed later
  const payload = { input: input++ };
  const headers = {
    'Content-Type': 'application/json',
    'dataType': 'json'
  };
  const res = http.request('POST', 'https://API_GATEWAY_ID.execute-api.ap-northeast-2.amazonaws.com/produce',
  JSON.stringify(payload),  {
    headers: headers,
  });
  console.log(JSON.stringify(payload))
  sleep(0.1);

  const checkRes = check(res, {
    'status is 200': (r) => r.status === 200, // 기대한 HTTP 응답코드인지 확인합니다.
    'response body': (r) => r.body.indexOf('{"message":"Message accepted!"}') !== -1,  // 기대한 응답인지 확인합니다.
  });
}
  • k6 실행
% bash run.sh   // k6 테스트 실행

오류

  • indexOf 오류 => k6 테스트를 위한 소스 내 API_GATEWAY_ID 를 실제 값으로 바꾸지 않고 진행해서 생긴 오류

k6 설치 참고 링크

https://k6.io/docs/get-started/installation/

#10. 표시 제한 시간에 따른 DLQ 전송 실습

  • jobsWorker 람다 내 실행 제한 시간 증가

  • 프로듀서 테스트 이벤트 재 실행

  • SQS jobs 내 이동중인 메시지 확인 (함수 실행이 완료되지 않았으므로 jobs의 이동중인 메시지에 떠있음)

=> 시간이 지나면 메시지를 소비하고 이동 중인 메시지 숫자가 사라짐

  • CloudWatch 결과 확인

  • SQS jobs 의 기본 표시 제한 시간을 람다 함수 실행 완료 시간 보다 낮은 시간으로 설정 (15>n)

  • 같은 이벤트 테스트 진행 시 DQL로 메시지가 전송됨

=> 람다 함수가 실행완료 되기 전에 표시 제한 시간이 끝나기 때문

  • SQS내 DLQ 메시지 폴링 확인
  • 표시 제한 시간이 람다 함수 실행 시간 보다 낮으면 DQL로 메시지가 전송됨 (메시지를 소비하지 못했기 때문)
profile
기록하고 공유하려고 노력하는 DevOps 엔지니어

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN