Code States/TIL

[0524] 섹션 3. 프로젝트 - 마이크로서비스 Day 1 - Tutorial

ki1111m2 2023. 5. 24. 14:35

Step 1. Serverless를 이용한 Lambda 생성

serverless 설치 후 템플릿 생성

serverless.yml 파일에서 지역을 수정해준다

service: tutorial-step-1
frameworkVersion: '3'

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

functions:
  api:
    handler: index.handler
    events:
      - httpApi:
          path: /
          method: post

배포를 진행한다

터미널에 나타난 api 주소로 get 요청을 보내면 정상적으로 응답하는 것을 볼 수 있다

hello 모듈로 접근할 수 있도록 yml 파일을 수정한다

service: tutorial-step-1
frameworkVersion: '3'

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

functions:
  api:
    handler: index.hello
    events:
      - httpApi:
          path: /
          method: post

index.js 파일에 hello 모듈을 추가한다

module.exports.handler = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        message: "Go Serverless v3.0! Your function executed successfully!",
        input: event,
      },
      null,
      2
    ),
  };
};

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
    ),
  };
};

배포 후 GET 요청을 보내 작동을 확인한다


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

서버리스 템플릿을 생성한다

yml 파일에서 지역을 바꾼 후 배포를 진행한다

람다 함수에 프로듀서와 컨슈머가 생성된 것을 볼 수 있다

프로듀서의 경우엔 트리거로 API 게이트웨이가, 컨슈머의 경우엔 SQS가 연결된 것을 볼 수 있다

테스트 이벤트를 구성한다

 

계속 실패가 떠서 이것 저것 시도한 모습들 ..

처음엔 인풋으로 넣었다가 400 에러가 떴다

바디로 넣었더니 500 에러로 바꼈다

작성한 소스 코드를 생각했을 때, 바디 안에 인풋 안에 값이 있는것이니 그 구조를 따라야하지 않을까? 라고 생각했다

그러나 여전히 500 오류

클라우드 워치를 통해 로그를 살펴보니

메시지 바디로 들어는 가는데 인풋 값으로 인식을 못하고 있었다

소스 코드를 body.input이 아닌 body로 변경했다

그러나 여전히 같은 상황

메시지 바디를 출력하는 부분에서는 record.body여서 두시 부분도 그렇게 바꿔줬다

그러나 여전히 같은 상황

이프문 안의 콘솔로그도 못찍길래 이프문으로 접근을 못한다고 판단했다

이프문을 제거한 후 테스트 해보았다

상태코드는 여전히 500으로 오류가 뜨지만, 값은 제대로 들어가는 것이 확인됐다

혹시 하는 마음에 테스트 이벤트의 값을 1 -> "1" 로 따옴표로 감싸주었다

상태코드도 200으로 바뀌고 정상 작동 되는 모습..

코드 뒷부분에서 어차피 parseInt를 통해 정수로 변환해주니 뒷부분에서는 오류가 안나고, 앞부분은 정수를 바디로 인식을 못했다보다

이프문은 왜 인식을 못하는지 고민하다가 for문의 조건때문에 그런가 라는 생각이 들었다

for (const record of event.Records) 이 부분에서 event.Records의 값을 record에 넣어줬기 때문에 event.body가 아닌 record.body로 접근해야 할 것 같다는 생각을 했다

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) {
    console.log("Message Body: ", record.body);

    let inputValue, outputValue

    // TODO: Step 1을 참고하여, +1 를 하는 코드를 넣으세요
    if(record.body){
      inputValue = parseInt(record.body)
      console.log(inputValue);
      outputValue = inputValue + 1
    }
    
    const message = `메시지를 받았습니다. 입력값: ${inputValue}, 결과: ${outputValue}`
    console.log(message)

  }
};

module.exports = {
  producer,
  consumer,
};

변경한 결과 람다함수의 상태코드도 200으로 잘 뜨고, if문 안의 코드들도 인식을 해서 결과 또한 정상적으로 뜨는 것을 확인할 수 있었다


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

기존 js 파일에 딜레이를 걸어준다

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,
    }),
  };
};

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);

    let inputValue, outputValue

    // TODO: Step 1을 참고하여, +1 를 하는 코드를 넣으세요
    if(record.body){
      inputValue = parseInt(record.body)
      console.log(inputValue);
      outputValue = inputValue + 1
    }
    
    const message = `메시지를 받았습니다. 입력값: ${inputValue}, 결과: ${outputValue}`
    console.log(message)

  }
};

module.exports = {
  producer,
  consumer,
};

성능테스트 도구인 k6를 사용하기 위해 설치를 진행한다

k6 파일을 실행한 결과, 100번의 테스트 중 89번은 통과하고 11번은 실패한 것을 볼 수 있었다