AWS 기술 블로그
AWS Application Composer를 활용한 서버리스 서비스 구현 방법
서비스에서 신규 기능으로 빈번하게 등장하는 기능 중 하나는 알람 기능입니다. 예를 들어 쇼핑몰 업체는 고객에게 배송 안내 알람을 전달하고 공장 운영 환경에서는 장비 장애 알람을 보냅니다. 문제는 이런 알람 서비스를 위해서 별도의 인스턴스를 만들고 웹 서버를 유지하는 건 새로운 관리 포인트를 만드는 일이라는 점입니다. 또한 알람 서비스의 특성상 대규모 고객에게 알람을 보내야 하는 일도 발생할 수 있는데 알람만을 위해서 별도 시스템을 구축하지 않으면 전체 시스템 장애로 이어질 수 있습니다. 그래서 저희는 고객에게 대안으로 서버리스 서비스를 안내해 드리고 있습니다. 서버리스 서비스를 선택하는 경우 고객은 별도의 인프라를 관리할 필요가 없고 비즈니스 로직 코드만 작성하면 됩니다. 확장성이나 안정성 면에서도 AWS가 관리하기 때문에 직접 인프라를 관리하는 것에 비해 공수를 줄일 수 있습니다.
하지만 서버리스 서비스를 선택한다고 해서 모든 문제가 해결되는 것은 아닙니다. 대표적으로 서버리스 서비스가 문제 되는 부분은 서비스 아키텍처를 팀원들과 공유하기 어렵다는 점입니다. 기존 서비스에서는 UML 등 도식도로 아키텍처를 공유했지만, 서비스가 잘게 분리되고 복잡해지는 이 시점에서 기존 방식이 항상 정답은 아닙니다.
이번 게시글에서는 AWS Application Composer 서비스를 통해서 알람 서비스를 서버리스 방식으로 만드는 방법을 소개합니다. 나아가서 실제 메일, Slack 등으로 알람을 전달하는 기능을 직접 구축해 보면서 어떻게 응용 가능한지도 알아봅니다. 사용자는 이 시스템을 이용해서 자신의 서비스에 쉽게 알람 서비스를 추가할 수 있습니다.
솔루션 개요
이번 게시글에서 소개하는 솔루션은 AWS Application Composer를 활용한 알람 서비스입니다. 최종적으로 고객에게 메일과 Slack 메시지를 전송하고 알람 데이터를 Amazon DynamoDB에 저장합니다. 각 과정을 구성하기 위해 사용한 AWS 서비스를 소개합니다.
AWS Application Composer : AWS Application Composer는 시각적으로 서버리스 아키텍처를 디자인하는 도구입니다. 즉시 배포할 수 있도록 IaC(Infrastructure as code) 문서를 생성하고 팀원 간 만들어진 IaC 문서를 공유하면서 아키텍처 모델을 유지 관리할 수 있습니다. 또 다른 장점으로는 AWS 보안 설정을 쉽게 할 수 있다는 점이 있습니다. AWS Application Composer를 사용하지 않는 경우에는 하나의 리소스에서 다른 리소스를 호출할 때 AWS Identity and Access Management(IAM) 설정을 수행해야 합니다. 하지만 Application Composer를 사용하면 각 화면을 보면서 구성요소를 연결하는 것만으로 보안 설정이 만들어집니다. 따라서 설계에만 집중하고 싶은 고객의 경우 유용하게 사용하실 수 있습니다.
AWS Serverless Application Model(SAM) : AWS Serverless Application Model은 서버리스 애플리케이션을 빌드하는데 사용하는 오픈소스 프레임워크입니다. AWS SAM 템플릿을 사용하여 서버리스 애플리케이션을 정의하고 구성하는 함수, API, 권한, 이벤트를 간단하게 만들 수 있습니다.
아키텍처 소개
이번 게시글에서는 소개하는 아키텍처는 실제 서비스 환경처럼 API를 호출해서 알람을 보내는 구조입니다. 알람 시나리오는 아래 순서로 진행합니다.
- Amazon API Gateway에서 API를 생성하고 해당 API로 요청을 받으면 AWS Lambda를 호출합니다.
- AWS Lambda에서는 Amazon Simple Notification Service(SNS)로 데이터를 전달합니다.
- Amazon Simple Notification Service(SNS)에서는 각 기능을 수행할 Lambda와 Amazon Simple Queue Service(SQS)로 데이터를 전달합니다.
- 첫번째 Lambda에서는 Amazon Simple Email Service(SES) API를 호출해 이메일을 전송합니다.
- 두번째 Lambda는 Slack API를 통해 Slack 메시지를 전달합니다.
- Amazon SQS로 받은 데이터는 Lambda를 통해 Amazon DynamoDB에 저장합니다.
사전 준비 사항
솔루션을 배포하기 위해서는 아래 항목을 준비해야 합니다.
- AWS 계정
- AWS CLI
- AWS SAM CLI
- E-mail 계정 및 Slack 계정
- Slack Workspace
단계 요약
- 단계 1 : AWS Application Composer를 통한 서버리스 서비스 구성 연습
- 단계 2 : 샘플 코드 다운로드
- 단계 3 : AWS Serverless Application Model 구성
- 단계 4 : 동작 확인
단계 1 : AWS Application Composer를 통한 서버리스 서비스 구성 연습
단계 1에서는 프로젝트를 새롭게 생성 해보고 서버리스 구성 요소들을 변경하는 작업을 진행하겠습니다. 그리고 단계 2에서 샘플 코드를 다운받아 실제 동작까지 수행하는 방식으로 핸즈온을 진행합니다.
1-1. AWS 계정에 로그인 후 콘솔에서 AWS Application Composer에 접속한 후 프로젝트 생성 버튼을 누릅니다. 그리고 아래와 같이 New Blank Project(새 빈 프로젝트) 버튼을 눌러 신규 프로젝트를 생성합니다.
1-2. 왼쪽에 있는 아이콘들을 클릭 후 캔버스에 이동합니다. 그리고 구성요소 박스 내부에 원을 클릭 후 다른 구성요소까지 이으면 두 가지 구성요소가 연결됩니다.
1-3 생성한 구성 요소를 더블 클릭하면 속성을 변경할 수 있습니다. Amazon DynamoDB에서는 아래와 같이 ID, 파티션키, 정렬키 등을 설정할 수 있습니다.
1-4 캔버스로 만든 아키텍처는 왼쪽 상단 메뉴 버튼을 클릭 후 변경 사항 저장을 통해 로컬로 내려받을 수 있습니다.
단계 2 : 샘플 코드 다운로드
단계 2는 미리 만들어진 샘플 코드로 진행합니다.
2-1. Github에서 샘플 코드를 git clone하거나 압축파일로 내려받습니다.
2-2 다운로드한 코드를 AWS Application Composer에서 확인해보겠습니다. 단계1과 같이 프로젝트를 생성하지만 이번에는 새 빈 프로젝트가 아니라 기존 프로젝트 로드를 선택하고 다운로드 받은 폴더의 template.yaml 파일을 클릭합니다.
2-3 샘플 코드의 구조를 확인합니다. 아키텍처 소개 섹션에 소개된 것처럼 Amazon API Gateway로 데이터가 입력되고 다른 구성 요소들과 연결되는 구조입니다.
2-2 소스 코드 항목 중 개인 환경에 맞게 수정해야되는 부분이 있습니다. VSCode, Vim등 IDE를 통해 코드 내용을 확인합니다.
2-3 첫번째 수정파일은 template.yaml 파일 입니다. 아래 항목 중 자신이 전달받을 이메일 도메인과 이메일 주소를 변경합니다.
Transform: AWS::Serverless-2016-10-31
Resources:
SendItemInfo:
Type: AWS::Serverless::Function
Properties:
Description: !Sub
- Stack ${AWS::StackName} Function ${ResourceName}
- ResourceName: SendItemInfo
CodeUri: src/SendItem
Handler: index.handler
Runtime: nodejs16.x
MemorySize: 3008
Timeout: 30
Tracing: Active
Environment:
Variables:
TOPIC_NAME: !GetAtt ItemTopic.TopicName
TOPIC_ARN: !Ref ItemTopic
Policies:
- SNSPublishMessagePolicy:
TopicName: !GetAtt ItemTopic.TopicName
Events:
ApiPOSTitem:
Type: Api
Properties:
Path: /item
Method: POST
RestApiId: !Ref Api
SendItemInfoLogGroup:
Type: AWS::Logs::LogGroup
DeletionPolicy: Retain
Properties:
LogGroupName: !Sub /aws/lambda/${SendItemInfo}
ItemTopic:
Type: AWS::SNS::Topic
Properties:
Subscription:
- Endpoint: !GetAtt ItemQueue.Arn
Protocol: sqs
ItemQueue:
Type: AWS::SQS::Queue
Properties:
MessageRetentionPeriod: 345600
ItemTopicToItemQueuePermission:
Type: AWS::SQS::QueuePolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: sns.amazonaws.com
Action: sqs:SendMessage
Resource: !GetAtt ItemQueue.Arn
Condition:
ArnEquals:
aws:SourceArn: !Ref ItemTopic
Queues:
- !Ref ItemQueue
StoreItemInfo:
Type: AWS::Serverless::Function
Properties:
Description: !Sub
- Stack ${AWS::StackName} Function ${ResourceName}
- ResourceName: StoreItemInfo
CodeUri: src/StoreInfo
Handler: index.handler
Runtime: nodejs16.x
MemorySize: 3008
Timeout: 30
Tracing: Active
Events:
ItemQueue:
Type: SQS
Properties:
Queue: !GetAtt ItemQueue.Arn
BatchSize: 1
Environment:
Variables:
TABLE_NAME: !Ref ItemHistoryTable
TABLE_ARN: !GetAtt ItemHistoryTable.Arn
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref ItemHistoryTable
StoreItemInfoLogGroup:
Type: AWS::Logs::LogGroup
DeletionPolicy: Retain
Properties:
LogGroupName: !Sub /aws/lambda/${StoreItemInfo}
ItemHistoryTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: isolate
AttributeType: S
BillingMode: PAY_PER_REQUEST
KeySchema:
- AttributeName: id
KeyType: HASH
- AttributeName: isolate
KeyType: RANGE
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
TimeToLiveSpecification:
AttributeName: expiration
Enabled: true
Api:
Type: AWS::Serverless::Api
Properties:
Name: !Sub
- ${ResourceName} From Stack ${AWS::StackName}
- ResourceName: Api
StageName: Prod
DefinitionBody:
openapi: '3.0'
info: {}
paths:
/item:
post:
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri: !Sub arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SendItemInfo.Arn}/invocations
responses: {}
EndpointConfiguration: REGIONAL
TracingEnabled: true
SendMail:
Type: AWS::Serverless::Function
Properties:
Description: !Sub
- Stack ${AWS::StackName} Function ${ResourceName}
- ResourceName: SendMail
CodeUri: src/SendMail
Handler: index.handler
Runtime: nodejs16.x
MemorySize: 3008
Timeout: 30
Tracing: Active
Events:
ItemTopic:
Type: SNS
Properties:
Topic: !Ref ItemTopic
Region: !Select
- 3
- !Split
- ':'
- !Ref ItemTopic
Policies:
- SESCrudPolicy:
IdentityName: amazon.com // 개인 이메일 도메인으로 변경
- SESCrudPolicy:
IdentityName: amazon@amazon.com // 개인 이메일 주소로 변경
SendMailLogGroup:
Type: AWS::Logs::LogGroup
DeletionPolicy: Retain
Properties:
LogGroupName: !Sub /aws/lambda/${SendMail}
SendSlack:
Type: AWS::Serverless::Function
Properties:
Description: !Sub
- Stack ${AWS::StackName} Function ${ResourceName}
- ResourceName: SendSlack
CodeUri: src/SendSlack
Handler: index.handler
Runtime: nodejs16.x
MemorySize: 3008
Timeout: 30
Tracing: Active
Events:
ItemTopic:
Type: SNS
Properties:
Topic: !Ref ItemTopic
Region: !Select
- 3
- !Split
- ':'
- !Ref ItemTopic
SendSlackLogGroup:
Type: AWS::Logs::LogGroup
DeletionPolicy: Retain
Properties:
LogGroupName: !Sub /aws/lambda/${SendSlack}
2-4 Amazon SES 페이지에 접속한 후 개인 메일 주소를 등록합니다. 아쉽지만 아직 Amazon SES는 ApplicationComposer에서 사용할 수 없어 별도로 등록하는 과정이 필요합니다.
2-5 다운로드한 프로젝트 경로 SendMail → index.js 파일을 수정합니다. 코드 중 메일 주소 변경이라고 되어 있는 항목에 자신의 개인 이메일 주소를 입력합니다.
// Load the AWS SDK for Node.js
const AWS = require('aws-sdk');
AWS.config.update({region: 'us-east-1'});
const ses = new AWS.SES({ apiVersion: "2010-12-01" });
exports.handler = async (event) => {
let message = event.Records[0].Sns.Message
try {
const params = {
Destination: {
ToAddresses: ["amazon@amazon.com"], // 메일 주소 변경
},
Message: {
Body: {
Html: {
Charset: "UTF-8",
Data: message,
},
},
Subject: {
Charset: "UTF-8",
Data: "Special Price",
},
},
Source: "amazon@amazon.com", // 메일 주소 변경
};
const { MessageId } = await ses.sendEmail(params).promise();
return {
statusCode: 200,
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ MessageId }),
};
} catch (error) {
console.log(error.message);
return {
statusCode: 500,
body: JSON.stringify({ message: "Error send email" })
}
}
}
2-6 다운로드한 프로젝트 경로 SendSlack → index.js 파일을 수정합니다. 개인의 Slack Webhook URL과 Workspace를 생성 후 개인 정보로 코드를 수정합니다. Slack Webhook URL을 생성 방법은 링크에서 확인하실 수 있습니다.
const https = require('https');
const doPost = (message) => {
console.log(message)
// 본인 슬랙 정보로 변경 필요
const data = {
"channel": "#anycompany-slack",
"username": "SpecialPrice_BOT",
"text": message,
"icon_emoji": ":sos:"
};
return new Promise((resolve, reject) => {
const options = {
host: 'hooks.slack.com',
path: '/services/AWSAWSAWS', // 본인 슬랙 경로로 변경 필요
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
};
const req = https.request(options, (res) => {
resolve(JSON.stringify(res.statusCode));
});
req.on('error', (e) => {
reject(e.message);
});
req.write(JSON.stringify(data));
req.end();
});
};
exports.handler = async (event) => {
let message = event.Records[0].Sns.Message
await doPost(message)
.then(result => console.log(`Status code: ${result}`))
.catch(err => console.error(`Error doing the request for the event: ${JSON.stringify(event)} => ${err}`));
};
단계 3 : AWS Serverless Application Model 구성
3-1. AWS SAM CLI를 설치합니다.
3-2 터미널 창을 켜서 샘플 코드가 있는 경로로 이동 후 sam build 명령어 입력해서 빌드를 시작합니다.
$ sam build
3-3 빌드가 완료되면 아래와 같이 출력됩니다.
Running NodejsNpmBuilder:CleanUpNpmrc
Running NodejsNpmBuilder:LockfileCleanUp
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
yeonuk@bcd0743a application_composer %
3-4 빌드가 완료되면 sam deploy —guided 명령어를 입력해서 배포를 시작합니다. 배포 중간에 나오는 Stack Name, AWS Region 등 설정은 아래 명령어와 동일하게 진행합니다. 특히 리전 정보는 코드에 이미 설정되어 있는 부분들이 있어 us-east-1로 설정하셔야 합니다.
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: price-alert
AWS Region [ap-northeast-2]: us-east-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: n
SendItemInfo may not have authorization defined, Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
Looking for resources needed for deployment:
3-5 설정을 완료하면 어떤 리소스들이 추가될지 보여주는 화면이 나옵니다. Deploy this changeset? 항목에서 y 를 입력합니다.
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------
+ Add ApiDeployment9ecf704443 AWS::ApiGateway::Deployment N/A
+ Add ApiProdStage AWS::ApiGateway::Stage N/A
+ Add Api AWS::ApiGateway::RestApi N/A
+ Add ItemHistoryTable AWS::DynamoDB::Table N/A
+ Add ItemQueue AWS::SQS::Queue N/A
+ Add ItemTopicToItemQueuePermission AWS::SQS::QueuePolicy N/A
+ Add ItemTopic AWS::SNS::Topic N/A
+ Add SendItemInfoApiPOSTitemPermissionPr AWS::Lambda::Permission N/A
od
+ Add SendItemInfoLogGroup AWS::Logs::LogGroup N/A
+ Add SendItemInfoRole AWS::IAM::Role N/A
+ Add SendItemInfo AWS::Lambda::Function N/A
+ Add SendMailItemTopicPermission AWS::Lambda::Permission N/A
+ Add SendMailItemTopic AWS::SNS::Subscription N/A
+ Add SendMailLogGroup AWS::Logs::LogGroup N/A
+ Add SendMailRole AWS::IAM::Role N/A
+ Add SendMail AWS::Lambda::Function N/A
+ Add SendSlackItemTopicPermission AWS::Lambda::Permission N/A
+ Add SendSlackItemTopic AWS::SNS::Subscription N/A
+ Add SendSlackLogGroup AWS::Logs::LogGroup N/A
+ Add SendSlackRole AWS::IAM::Role N/A
+ Add SendSlack AWS::Lambda::Function N/A
+ Add StoreItemInfoItemQueue AWS::Lambda::EventSourceMapping N/A
+ Add StoreItemInfoLogGroup AWS::Logs::LogGroup N/A
+ Add StoreItemInfoRole AWS::IAM::Role N/A
+ Add StoreItemInfo AWS::Lambda::Function N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:us-east-1:018023422026:changeSet/samcli-deploy1682091194/4c4ec0bb-15b8-4c57-a618-6bc622c9e819
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
3-6 5분 정도 대기하면 리소스 배포가 완료되고 Successfully created/updated stack – price-alert in us-east-1 이라는 문구를 확인하실 수 있습니다. 만약 실패한다면 단계2부터 다시 진행 부탁드립니다.
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ApiDeployment9ecf704443 Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Deployment ApiDeployment9ecf704443 -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission SendItemInfoApiPOSTitemPermissionPr -
od
CREATE_COMPLETE AWS::CloudFormation::Stack price-alert -
-----------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - price-alert in us-east-1
단계 4 : 동작 확인
4-1 Amazon API Gateway 콘솔 페이지에 접속해서 만들어진 API를 확인합니다.
4-2 생성된 API를 클릭하고 화면 왼쪽 스테이지 버튼을 클릭합니다. 그리고 Prod → / → /item → POST 를 차례로 클릭해서 /item의 URL 경로를 확인합니다.
4-3 Postman이나 Curl등 API 호출 도구를 이용해서 /item 경로 URL을 Post 방식으로 호출합니다.
Body에 들어가는 항목은 아래와 동일하게 입력합니다. 타임 세일! 치킨 1마리 9900원!은 원하는 대로 변경할 수 있고 message는 수정하실 경우 코드도 같이 수정해야합니다.
{
"message": "타임 세일! 치킨 1마리 9900원!"
}
4-4 /item 경로 호출 후 각 기능을 확인합니다. 가장 먼저 Slack으로 알림 메세지가 오는지 확인합니다.
4-5 이메일로 알람 메시지가 오는지 확인합니다.
4-6 Amazon DynamoDB 페이지에서 알람 정보가 제대로 들어있는지 확인합니다.
리소스 삭제
리소스 삭제의 경우 생성 방법과 동일하게 터미널 창에서 프로젝트에 경로로 이동 후 sam delete —region us-east-1 명령어를 입력합니다. 뒤이어 나오는 항목들도 아래와 동일하게 입력합니다. Deleted successfully 문구가 나오면 정상적으로 삭제 완료된 상황입니다.
$ sam delete --region us-east-1
Are you sure you want to delete the stack price-alert in the region us-east-1 ? [y/N]: y
Are you sure you want to delete the folder price-alert in S3 which contains the artifacts? [y/N]: y
- Deleting S3 object with key price-alert/74e34d23b3d0a443150fc2808f577b94
- Deleting S3 object with key price-alert/7ff274a4dda558f31e31783d09aaa79c
- Deleting S3 object with key price-alert/36a64026e685303d7f04f42931aaa41f
- Deleting S3 object with key price-alert/cb016c36084a296ce60e00b1e31c891e
- Deleting S3 object with key price-alert/4711b84605d088b51b97c1bf7df12f0a.template
- Deleting Cloudformation stack price-alert
Deleted successfully
결론
이 글에서는 AWS Application Composer와 AWS SAM을 사용하여 간단한 서버리스 알람 서비스를 구성해 봤습니다. 그리고 실제 구성한 서비스의 동작을 확인해 보면서 실무에 적용할 수 있는 방법을 알아보았습니다. 서비스에 새로운 기능을 개발 중이시라면 AWS Application Composer를 도입을 추천합니다. 시각적으로 아키텍처를 디자인할 수 있고 팀원간 공유가 간편합니다. 또한 AWS Application Composer로 만들 수 있는 다른 아키텍처들이 궁금하시다면 AWS의 여러 서버리스 관련 정보를 모아놓은 서버리스 랜드에서 확인 가능합니다.
참고자료
- https://aws.amazon.com/blogs/compute/visualize-and-create-your-serverless-workloads-with-aws-application-composer/
- https://serverlessland.com/