간단하게(?) WebSocket API, Lambda, DynamoDB를 사용해 서버리스 채팅 앱을 구축해보자..

https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/websocket-api-chat-app.html

해당 링크에서 cloudformation 템플릿으로 작업 후에 하나씩 살펴보자..


  • CloudFormation 생성

CloudFormation 템플릿

 

  • WebSocket API 생성

라우팅 선택 표현식 값으로 표현된 속성은 클라이언트가 API 호출할 때마다 전송되는 모든 메시지에 포함되어야 한다.

$request.body.action 의 경우, 클라이언트 요청의 body 내에 "action" 키에 mapping 된 값으로 route 한다는 의미!

 

다음으로 넘어가 $connect / $disconnect / $default 다 추가하기.

(요청과 일치하는 다른 경로가 없을 때 $default 경로를 호출)

통합 연결에서 Lambda 선택 후 각 각 맞는 함수를 넣어준다.

 

  • Test the chat API

wscat 유틸을 사용해 테스트 해보자

 

터미널 환경을 위해 cloud9을 열어주자... 2개를 만들 것!(메시지를 던지는 서버 1 / 메시지를 받는 서버 2)

wscat을 사용하기 위해선 npm이 필요!

npm -v
npm install -g wscat

publish 된 API endpoint에 연결

wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/{STAGE}

api gateway의 대시보드를 보면 websocket url 알 수 있음..

위의 명령어 입력 시 connected 가 뜨면 연결 된 것..

DynamoDB에 connect Id 가 들어간 것을 볼 수 있음

sendMessage 함수 테스트를 위해 아래와 같은 JSON 메시지 전달 (cloud9 -1)

던진 메시지를 보기 위한 것..(cloud9 -2)

두 개의 터미널(cloud9 -1,2)에서 연결을 종료 시킨 뒤 다시 dynamoDB를 확인해보면...

 

이제 하나씩 다시 살펴보자!


일단 Lambda 함수들 먼저 보면

Connect / Disconnect / Default / sendMessage 이렇게 4개의 함수가 생성되어 있다.

(물론 모든 함수들은 트리거로 위에서 생성했던 API Gateway 잡혀있음!!)

  • ConnectHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      exports.handler = async function (event, context) {
        try {
          await ddb
            .put({
              TableName: process.env.table,
              Item: {
                connectionId: event.requestContext.connectionId,
              },
            })
            .promise();
        } catch (err) {
          return {
            statusCode: 500,
          };
        }
        return {
          statusCode: 200,
        };
      };

DynamoDB 테이블에 connectId 값을 넣는 작업

 

  • DisconnectHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      
      exports.handler = async function (event, context) {
        await ddb
          .delete({
            TableName: process.env.table,
            Key: {
              connectionId: event.requestContext.connectionId,
            },
          })
          .promise();
        return {
          statusCode: 200,
        };
      };

DynamoDB 테이블에 connectId 값에 해당되는 record 삭제

(따라서 위 두 함수의 역할에는 기본적인 Lambda + DynamoDB 에 접근할 수 있는 권한이 필요!)

 

  • DefaultHandler
const AWS = require('aws-sdk');

      exports.handler = async function (event, context) {
        let connectionInfo;
        let connectionId = event.requestContext.connectionId;
      
        const callbackAPI = new AWS.ApiGatewayManagementApi({
          apiVersion: '2018-11-29',
          endpoint:
            event.requestContext.domainName + '/' + event.requestContext.stage,
        });
      
        try {
          connectionInfo = await callbackAPI
            .getConnection({ ConnectionId: event.requestContext.connectionId })
            .promise();
        } catch (e) {
          console.log(e);
        }
      
        connectionInfo.connectionID = connectionId;
      
        await callbackAPI
          .postToConnection({
            ConnectionId: event.requestContext.connectionId,
            Data:
              'Use the sendmessage route to send a message. Your info:' +
              JSON.stringify(connectionInfo),
          })
          .promise();
      
        return {
          statusCode: 200,
        };
      };

잘못된 경로(?)로 들어오면... 들어오는 방법 설명!

 

  • sendMessageHandler
const AWS = require('aws-sdk');
      const ddb = new AWS.DynamoDB.DocumentClient();
      
      exports.handler = async function (event, context) {
        let connections;
        try {
          connections = await ddb.scan({ TableName: process.env.table }).promise();
        } catch (err) {
          return {
            statusCode: 500,
          };
        }
        const callbackAPI = new AWS.ApiGatewayManagementApi({
          apiVersion: '2018-11-29',
          endpoint:
            event.requestContext.domainName + '/' + event.requestContext.stage,
        });
      
        const message = JSON.parse(event.body).message;
      
        const sendMessages = connections.Items.map(async ({ connectionId }) => {
          if (connectionId !== event.requestContext.connectionId) {
            try {
              await callbackAPI
                .postToConnection({ ConnectionId: connectionId, Data: message })
                .promise();
            } catch (e) {
              console.log(e);
            }
          }
        });
      
        try {
          await Promise.all(sendMessages);
        } catch (e) {
          console.log(e);
          return {
            statusCode: 500,
          };
        }
      
        return { statusCode: 200 };
      };

메시지 주고 받게...

(위 두 함수들은 연결된 클라이언트들 호출을 위한 "execute-api:ManageConnections" 권한이 필요하다.)

 

❗ 근데.. 위는 정말 간단하게 메시지 주고 받는 거고... 실제로 사용할 땐 어떤 식으로 사용하는지 궁금하다... ❗

그리고 웹소켓 연결 후 주고받는 메시지들은 어떻게 관리가 되는지, 유실은 없는지 등의 궁금증이 생기네...?

'Cloud > AWS' 카테고리의 다른 글

[AWS] Serverless Service - Lambda 편 (2)  (0) 2022.11.05
[AWS] Serverless Service - Lambda 편 (1)  (0) 2022.11.05
[AWS] Lambda Warm Start / Cold Start  (0) 2022.10.28
[AWS] Fan-Out 시나리오 -1(easy)  (0) 2022.10.11
[AWS] Event-Driven Architecture  (0) 2022.10.11

https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/http-api-private-integration.html#http-api-private-integration-create-api

"VPC에서 실행되는 ECS 서비스에 연결하는 서버리스 API 생성"

아무래도 api gateway의 경우 서버리스(lambda, ecs와 같은) 서비스와 함께 사용된다.. 

api gateway + lambda는 저번에 해봤으니까..

이번엔 api gateway + ecs다!(http api 구축이랑 rest api 구축이랑 콘솔 디자인이 다르다.. rest 꾸짐)

VPC link

(그림 아래의 사이트에 들어가 cloudformation 템플릿으로 한번에 올려서 테스트가 가능함..)

나는 차례대로 한 번 따라가 볼 예정!


  • ECS 생성(ALB를 사용하는 ECS 서비스 생성)

문서에서 제공하는 템플릿을 cloudformation에 스택을 올린다.(template.yaml)

관련된 서비스들이 어떻게 올라갔는지 확인해보자.

1. VPC

vpc 안에 public subnet 2개 / private subnet 2개 생성

ngw의 경우 public subnet에 2개 생성(private rtb과 각각 연결) - 이중화를 기본으로 하는 듯

 

2. ECS

클러스터 생성

태스크 정의 생성

태스크 역할: https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/task-iam-roles.html

태스크 실행 역할: https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/task_execution_IAM_role.html

 

태스크 실행 후에 서비스 배포(태스크 정의 카테고리에서 작업 → 태스크 실행 / 서비스 생성 해야 바로 ELB 붙일 수 있음..)

Task: Task definition 대로 배포된 container set(cluster에 속한 container instance에 배포됨)

Service: Task들의 라이프 사이클 관리(Task를 cluster에 몇 개 배포할 건지, ELB 관리, Auto Scaling 관리)

 

3. ALB

alb 생성(스키마: 내부 / 가용영역: private /보안그룹: 인바운드 80포트 - all ipv4)

listener(80포트 - 대상 그룹)

대상 그룹: ecs의 태스크 ip 등록

(얘는 서비스 생성 전에 만들어야 위의 서비스 생성 과정에서 ALB 붙일 수 있을듯?)

❗ 위의 내용은 나중에 따로 혼자서 해보는걸로... 현재는 그냥 cloudformation 템플릿 사용해서 올렸을 뿐 ❗

 

  • VPC link

api gateway → vpc link 클릭

서브넷의 경우 private들 선택

보안그룹 없어도 됨

 

  • HTTP API

api gateway → http api build

경로는 나중에 생성할 예정

  • 경로 생성

  • 통합 생성

경로 선택 → 통합 대상(프라이빗 리소스) → 수동 선택 → ALB/NLB(ALB 선택) → 리스너(HTTP 80) → VPC link 선택

 

  • API Test

api url에 들어가면 아래와 같은 화면을 얻을 수 있음.

새로 고침 여러번 누르고 ecs 서비스 상태를 확인해보면

다음과 같이 잘 동작하고 있는 것을 확인할 수 있다...

 

 

❗ 아직 ecs의 개념이 확실치 않으니 다음에는 ecs 정리편으로 돌아오겠다.. ❗

'Cloud > AWS' 카테고리의 다른 글

[AWS] ECS 실습 -2(ECS 구축)  (0) 2022.09.15
[AWS] ECS 개념 + 실습 -1(Docker image push)  (1) 2022.09.14
[AWS] Mobile Backend 구성  (1) 2022.09.08
[AWS] AWS Amplify -1(Amplify Studio)  (0) 2022.09.07
[AWS] Lambda와 RDS 연동(+ API Gateway)  (0) 2022.09.06

+ Recent posts