AWS 클라우드 환경을 기반으로 하는 느슨하게 연결된 (loosely coupled) 어플리케이션 아키텍처에 대한 이해
Serverless 를 이용한 메시지 대기열 활용 이해 및 구현
요구사항에 따른 어플리케이션과 인프라 구현
문제사항 해결을 위한 추가 리소스 생성 -> DLQ, Legacy 시스템 성능문제 해결, SES
아키텍처 다이어그램 제작
인프라 관리와 재사용성을 위한 IaC 활용 -> Terraform을 통한 리소스생성
<도넛-스테이츠>는 온라인으로 도너츠를 판매합니다.
웹사이트 통해서 주문 버튼을 누르는 것으로 구매(Sales API)가 가능합니다.
창고에 재고가 있다면 재고가 감소하고 구매가 완료됩니다.
한 유튜버가 도넛-스테이츠의 도너츠가 맛있다고 영상을 올려 주문이 급등하였습니다.
창고에 재고가 없어 구매가 불가능한 경우 제조 공장에 알려 다시 창고를 채우는 시스템을 구축해야합니다.
제조 공장인 <팩토리>에 주문을 요청 (Lagacy Factory API)할 수 있습니다.
주문이 요청되면 일정 시간이 지난 후 창고에 재고가 증가합니다.
- Sales API
2. Factory API
3. 프론트엔드 (웹사이트) : cURL / Postman / k6 등을 통한 API 호출로만 구현
- Sales API를 통해 백엔드에 요청
4. 백엔드 (서버) : 구매 시 창고에서 재고 확인 후 재고 감소 로직 구현
- 재고가 부족할 경우 Factory API를 통해 재고 확보 요청
5. 데이터베이스 (창고) : RDS에 mysql db 구성
- 요청에 따른 재고 상태 변경
Serverless 를 이용한 AWS 리소스 생성
메시지 Queue가 사용되는 구조 이해
(Serverless framework 를 이용하여 Lambda 함수 생성 및 배포)
% npm install -g serverless
// node와 npm이 설치된 상태에서 Serverless Framework 를 글로벌 모듈로 설치하는 것을 권장함
% serverless
// AWS - Node.js - Starter // 생성 템플릿 선택
// Project name 은 원하는대로 작성
// Do you want to deploy now? N // 답변 no로 하기 - yes로 하면 기본 리전으로 바로 배포됨
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
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
),
};
};
%cd <serverless 프로젝트 템플릿 폴더>
// serverless 프로젝트 진입 후 deploy 진행
% serverless deploy
// deploy 완료 후 AWS console 내 lambda, cloudWatch, s3 등 생성 확인
!https://blog.kakaocdn.net/dn/bhMqXc/btrZNutNhPo/gIqfWD7a4tSEqg2nxKxv2K/img.png
% 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" }
% serverless remove
// deploy로 배포된 것들 삭제됨
{"message":"Missing Authentication Token"}
오류 출력 시 오타 또는 URL 뒷부분 /dev/hello 경로 확인
Serverless 튜토리얼 참고 링크
https://www.serverless.com/framework/docs/tutorial
% sls --version
% serverless // sls => 프로젝트 생성// AWS - Node.js - SQS Worker// Project name 은 적당하게 설정// Do you want to deploy now? N // no 로 선택하기 - yes로 하면 기본값으로 deploy % cd <생성 폴더 이름>// sls 프로젝트 진입% code . // visual code 로 작업할 경우
provider:
name: aws
runtime: nodejs18.x
region: ap-northeast-2
% serverless deploy // jobsWorker (컨슈머) 와 producer 두 함수가 생성된다
if (!event.body) {
return {
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,
};
% 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 경로 확인
#!/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/
DLQ 란?
K6 란?
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);
// 생략
% sls deploy// step 2 단계를 다시 deploy 한다. // 프로듀서와 컨슈머 생성 확인 (AWS Lambda)
=> 타임아웃에 걸려 람다가 제대로 실행되지 않았단 뜻
=> 람다함수는 최소 15초 이상의 시간이 필요함 (delay를 15초를 걸었으므로)
=> 지금 이 람다 함수의 실행 제한 시간이 6초 이므로 타임아웃이 걸림
=> 현재 6초로 설정되어 있음 (최대 15분)
=> 기본 표시 제한시간 Visibility Timeout (36s)
=> 이전 테스트에서 3회 시도 후 dlq 로 넘어갔던것 확인 했었음
=> 이전에 작성했던 메시지 확인 가능
% brew install k6
// 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, // 기대한 응답인지 확인합니다.
});
}
% bash run.sh // k6 테스트 실행
k6 설치 참고 링크
https://k6.io/docs/get-started/installation/
=> 시간이 지나면 메시지를 소비하고 이동 중인 메시지 숫자가 사라짐
=> 람다 함수가 실행완료 되기 전에 표시 제한 시간이 끝나기 때문