목차
Version Update v 1.3 - 2019.02.13
Version Update v 1.2 - 2018.11.12
Version Update v 1.1 - 2018.10.23
|
서버리스 서비스를 이용하면 서버를 설치 운용하거나 관리할 필요 없이 비지니스 로직을 코드로 구현하는 것만으로도 애플리케이션 구축이 가능합니다 .
201 레벨: 지난 "Amazon Polly를 통한 음성 읽기 서버리스 앱 개발하기" 블로그에서, AWS 관리 콘솔을 이용하여 서버리스 웹 애플리케이션을 구축하는 방법을 살펴 보았습니다.
301 레벨: 해당 실습은 301 레벨로, Cloud9을 이용해서 아래의 Lab1을 진행하고, CodeStar를 이용해서 Lab2를 진행합니다. 도전과제를 통해서 Lab2의 환경에 Lab1의 개발 코드를 배포하여 CICD 배포 파이프라인을 구축할 수 있습니다.
해당 실습은 2시간 기준으로 비용을 예측합니다. 프리티어 기준으로 비용이 발생하지 않는 범위내에서 실습을 진행할 수 있습니다.
Service | Sub Service | Unit | Pricing | Use | free tier | etc |
---|---|---|---|---|---|---|
Cloud9 | EC2 - t2.micro | 1 h | $0.0116/Hour | t2.micro 2hour | 750hour/month | |
EBS - 8GB | 1 m | $0.1/GB | 8GB 2hour | 30GB/month | ||
Lambda | - | 128MB | $0.000000208/100ms | 1,000번 이하 | 100만번 요청 400GB-초 | 1년 후 지속 제공 |
API Gateway | - | 1백만 API 호출당 $3.50 처음 10TB에 대해 $0.09/GB | 1,000번 이하 | 호출 100만건 | ||
DynamoDB | - | WCU당 최소 $0.47 RCU당 최소 $0.09 GB당 최소 $0.25 | WCU 5 RCU 5 스토리지 100M | 매달 2억건 요청 | 1년 후 지속 제공 | |
SNS | - | 처음 1GB/월 $0.000 GB당 최대 10TB/월 $0.090 GB당 | 100M 이하 | 매달 15GB의 데이터 전송 | ||
Polly | - | 1백만 문자당 $4 | 1,000 문자 이하 | 매달 문자 500만개 | ||
CodeStar | CodePipeline | 월별 활성 파이프라인*당 $1 | 1개 | 매월 활성 파이프라인 1개 | ||
CodeCommit | 최초 5명 초과시 월별 $1 GB당 $0.06/월 (10GB/월 초과) Git 요청당 $0.001 (1000회/월 초과) | 1개 | 최초 5명까지 매달 50GB의 스토리지 매달 10,000건의 Git 요청 | 1년 후 지속 제공 | ||
CodeBuild | build.general1.small $0.005/분 | 30분 | 매월 100 빌드 분의 build.general1.small | 1년 후 지속 제공 | ||
CodeDeploy | EC2 또는 Lambda에 배포 무료 | 30회 | - | |||
CloudFormation | - | - | - | |||
s3 | - | PUT, COPY, POST 또는 LIST 요청 $0.005/1,000건 GET, SELECT 및 기타 모든 요청 $0.0004/1,000건 처음 50TB/월 $0.023/GB | Put 100번 내외 GET 2,000번 내외 | 매달 5GB 스토리지 Get 요청 20,000건 Put 요청 2,000건 |
실습이 종료되고 나면 리소스를 반드시 삭제해야 합니다. 해당 실습은 CloudFormation 기반으로 진행됩니다. 즉 실습에 사용한 Stack을 삭제하면, 배포되어 있는 리소스도 함께 삭제할 수 있습니다. 반드시 실습에 사용한 리소스가 삭제되었는지 확인 후 실습을 종료합니다. 만약 S3 버킷에 객체가 남아 있을 경우 해당 버킷이 삭제가 되지 않을 수 있으므로, 해당 S3 버킷을 직접 삭제하고 확인 합니다.
아래 다이어그램은 Lab1에서 구축할 서버리스 웹 애플리케이션입니다. 서버리스 서비스를 이용하기 때문에, 프로비저닝, 패치, 확장에 대해 고민할 필요가 없으며, 사용한 만큼만 비용을 지불합니다. 또한, 서버리스 서비스는 완전 관리형 서비스이기 때문에, 개발자는 애플리케이션 개발 자체에만 집중할 수 있습니다. 해당 애플리케이션은 텍스트를 전송하면 해당 텍스트를 읽어주는 기능을 서버리스 서비스 기반으로 개발합니다. Lab2에서는 아래의 RESTful API에 대해서 CI/CD 배포 프로세스를 구축하는 방법에 대해서 자세히 살펴봅니다.
v1.0 Polly과 async를 지원하기 전의 아키텍처 다이어그램 |
이 애플리케이션은 다섯 가지 영역으로 구분할 수 있습니다.
실습은 Lab1과 Lab2로 구성되어져 있습니다.
|
Lab1은 Cloud9 IDE를 이용하여 서버리스 웹 애플리케이션을 구축하는 방법을 살펴봅니다. 이 실습은 Cloud9이 존재하는 Singapore 리전에서 English 언어로 진행합니다. Cloud9 통합 개발 환경(IDE)은 싱가포르 리전을 사용하지만, 서비스 배포는 다른 리전을 선택할 수 있습니다. 이 실습에서는 Singapore 리전을 그대로 사용하겠습니다.
새로운 애플리케이션 및 람다 함수를 생성합니다. Cloud9 IDE 우측 네비게이션의 AWS Resources 탭을 클릭하고, Lambda 아이콘 을 클릭하면 새로운 함수를 생성할 수 있습니다. 이 실습에서는 총 4개의 Lambda 함수를 만들 것이기 때문에, 앞으로 추가할 나머지 3개의 함수도 동일한 방법으로 생성할 예정입니다.
PostNews 함수는 아래에서 제공하는 코드로 대체하고 Ctrl+S를 눌러서 저장합니다. 코드 설명은 주석을 기반으로 생략합니다.
# -*- coding: utf-8 -*- from __future__ import print_function import boto3 import os import json import uuid import datetime import re def lambda_handler(event, context): if "body" in event: parmas = json.loads(event['body']) voice = parmas["voice"] originText = parmas["text"] timbre = parmas["timbre"] pitch = parmas["pitch"] updateDate = datetime.datetime.now().strftime("%Y%m%d") polly = boto3.client('polly') removeBrackets = re.sub(r'\([^)]*\)', '', originText) repTextBlock = re.sub('[·…]', '<break time="100ms"/>', removeBrackets) ssmlBlock = "<speak><amazon:effect vocal-tract-length=\"" + timbre + "\"><prosody pitch=\"" + pitch + "\">" + repTextBlock + "</prosody></amazon:effect></speak>" ssmlBlock = ssmlBlock.replace('철수', '<amazon:effect vocal-tract-length="+80%"><prosody pitch="-70%">안녕하세요? 저는 서연 친구 철수에요.</prosody></amazon:effect>') ssmlBlock = ssmlBlock.replace('귀신', '<amazon:effect name="whispered"><amazon:effect vocal-tract-length="-30%"><prosody volume="loud">나 꿍꼬또, 기싱꿍꼬또</prosody></amazon:effect></amazon:effect>') print (ssmlBlock) response = polly.start_speech_synthesis_task( OutputFormat= 'mp3', OutputS3BucketName= os.environ['BUCKET_NAME'], # OutputS3KeyPrefix='polly/', SampleRate='22050', SnsTopicArn=os.environ['SNS_TOPIC'], Text=ssmlBlock, TextType='ssml', VoiceId=voice ) print (response) data = response['SynthesisTask'] # Creating new record in DynamoDB table dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) table.put_item( Item = { 'id' : data['TaskId'], 'updateDate': data['CreationTime'].strftime("%Y-%m-%d %H:%M:%S"), 'voice' : voice, 'originText': originText, 'pollyStatus' : data['TaskStatus'], 'timbre': timbre, 'pitch': pitch, 'mp3Url': data['OutputUri'], 'RequestCharacters': data['RequestCharacters'] } ) result = { 'statusCode': 200, 'body': json.dumps({'recordId': data['TaskId']}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return result |
이번에는 SAM Template을 변경합니다. 먼저 좌측 소스코드 영역에서 template.yaml 파일을 더블클릭하여 편집 창을 열어 줍니다. 아래와 같이 자동으로 생성되어 있는 SAM 템플릿 코드를 볼 수 있습니다. 이 실습에서는 자동으로 생성된 SAM 템플릿을 사용하지 않고 SAM 템플릿을 교체합니다.
아래의 SAM 템플릿 코드로 교체하고, Ctrl+S를 눌러서 저장 합니다.
AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: >- Building Serverless development environment and CI/CD process for DevOps based on Cloud9 Globals: Function: Runtime: python2.7 Handler: lambda_function.lambda_handler MemorySize: 128 Timeout: 60 Resources: PostNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: PostNews Description: Post news text to convert from text to speech Tracing: Active Events: PostNewsApi: Type: Api Properties: Path: /news Method: POST Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 's3:PutObject' - 'sns:Publish' - 'dynamodb:PutItem' - 'polly:StartSpeechSynthesisTask' Resource: '*' |
위에 기술되어진 SAM 템플릿에 대한 설명은 다음과 같습니다. 추후 이어지는 SAM도 유사하게 해석할 수 있습니다.
해당 템플릿은 SAM(Serverless Architecture Model)을 기반으로 한 템플릿입니다. Globals를 통해서 리소스에서 공통으로 사용하는 값을 사전에 정의할 수 있습니다. 리소스를 정의하는 단계에서는 먼저 리소스에 대한 이름을 쓰고 리소스가 가진 서비스 타입을 명시합니다. 해당 Lambda 함수를 동작시키는 이벤트 트리거의 이름은 PostNewsApi 입니다. 해당 Lambda 함수가 실행하는 동안 접근해야할 리소스에 대한 권한을 설정할 수 있습니다. 이를 정책으로 정의할 수 있습니다. |
변경하여 수정한 SAM 템플릿 구성은 다음과 같습니다. 각 구성에 대해서 다시 한 번 자세히 살펴보면 아래와 같습니다.
Globals 는 전역으로 사용합니다. 함수(Function)나 API에서 반복해서 사용하는 것을 선언할 수 있습니다.
SAM 설정이 완료되면 해당 template.yaml 파일을 Cloud9에서 바로 배포할 수 있습니다.
우측의 해당 Lambda 함수에서 마우스 우측 버튼을 클릭하고 Deploy 명령을 수행 합니다. 해당 SAM 템플릿이 CloudFormation Stack에 업데이트 되는 것을 확인할 수 있습니다. 서비스에서 CloudFormation 서비스로 이동하면 cloud9-WebApp 이 추가되어 있는 것을 확인할 수 있습니다. 해당 Stack을 클릭하면 배포된 리소스를 알 수 있습니다. 다음 서버리스 리소스 추가 부분에서 CloudFormation에 추가된 템플릿을 확인하겠습니다.
SAM 템플릿을 이용해서 S3 Bucket, SNS, DynamoDB 테이블을 배포할 수 있습니다. 다음과 같이 template.yaml 파일에 리소스에 추가합니다.
Environment : 환경 변수를 선언합니다. 각각의 Lambda 함수에는 DynamoDB, SNS, S3 버킷에 접근하기 위해서 ARN이 필요합니다. SAM에서 자동으로 만들어준 각각의 리소스 ARN을 참조할 수 있도록 설정합니다.
NewsTable : SAM에서 SimpleTable은 DynamoDB를 의미합니다. 여기서 DynamoDB를 기본 세팅인 WCU 5, RCU 5 값으로 생성합니다.
NewsTopic : PostNews에 들어온 텍스트 값은 비동기적으로 처리를 위하여 ConvertAudio 함수의 처리를 필요로 합니다. ConvertAudio의 처리를 위해서 사용할 SNS Topic을 하나 생성합니다.
PollyMp3Bucket : Amazon Polly를 이용해서 생성한 MP3 파일을 저장할 S3 버킷을 생성합니다.
StaticWebBucket: S3는 객체 스토리지이면서 정적 웹 호스팅을 위한 서비스로 활용할 수 있습니다. 여기서는 정적 웹 호스팅을 위한 설정까지 함께 구성하여 SAM에 서비스를 추가합니다.
변경된 SAM 템플릿을 반영하기 위해서 우측 Lambda 함수에서 마우스 우측 버튼을 클릭하고 Deploy 명령을 수행합니다.
AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: >- Building Serverless development environment and CI/CD process for DevOps based on Cloud9 Globals: Function: Runtime: python2.7 Handler: lambda_function.lambda_handler MemorySize: 128 Timeout: 60 Environment: Variables: DB_TABLE_NAME: Ref: NewsTable SNS_TOPIC: Ref: NewsTopic BUCKET_NAME: Ref: PollyMp3Bucket Resources: NewsTable: Type: 'AWS::Serverless::SimpleTable' Properties: PrimaryKey: Name: id Type: String ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 NewsTopic: Type: 'AWS::SNS::Topic' Properties: DisplayName: NewsTopic PollyMp3Bucket: Type: 'AWS::S3::Bucket' StaticWebBucket: Type: 'AWS::S3::Bucket' Properties: AccessControl: PublicRead WebsiteConfiguration: IndexDocument: index.html ErrorDocument: error.html PostNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: PostNews Description: Post news text to convert from text to speech Tracing: Active Events: PostNewsApi: Type: Api Properties: Path: /news Method: POST Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 's3:PutObject' - 'sns:Publish' - 'dynamodb:PutItem' - 'polly:StartSpeechSynthesisTask' Resource: '*' |
UpdateNews 함수 코드를 다음과 같이 변경하고 저장합니다. Amazon Polly로부터 작업이 완료되면, DynamoDB에 해당 작업을 업데이트 하고, Polly가 S3에 업로드한 MP3에 접근할 수 있도록 Public-read 권한을 부여합니다.
from __future__ import print_function import boto3 import os import json from contextlib import closing from boto3.dynamodb.conditions import Key, Attr import re def lambda_handler(event, context): polly_message = event["Records"][0]["Sns"]["Message"] print (polly_message) polly_response = json.loads(polly_message) # Updating the item in DynamoDB dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) response = table.update_item( Key={ 'id': polly_response["taskId"] }, UpdateExpression="set pollyStatus = :s", ExpressionAttributeValues={ ':s': polly_response['taskStatus'] } ) print(response) s3 = boto3.client('s3') s3.put_object_acl( ACL = 'public-read', Bucket = os.environ['BUCKET_NAME'], Key = polly_response["taskId"] + ".mp3" ) return |
사전에 닫힌 template.yaml 파일을 클릭하여 열고, UpdateNews 함수 설정이 추가되는 것을 확인합니다. 그리고, 아래의 SAM 코드로 대체하고 Deploy를 진행합니다.
Resources: ... UpdateNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: UpdateNews Description: Update News via Amazon SNS Tracing: Active Events: ConvertResource: Type: SNS Properties: Topic: Ref: NewsTopic Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:UpdateItem' - 's3:PutObjectAcl' Resource: '*' |
코드는 다음을 참고합니다. 해당 함수는 DynamoDB에서 데이터를 가져오는 로직이 들어 있습니다. 코드 12번째 라인에 x자 표시가 나타나지만 무시하셔도 됩니다.
from __future__ import print_function import boto3 import os import json import decimal from boto3.dynamodb.conditions import Key, Attr # https://docs.aws.amazon.com/ko_kr/amazondynamodb/latest/developerguide/GettingStarted.Python.04.html # Helper class to convert a DynamoDB item to JSON. class DecimalEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, decimal.Decimal): if o % 1 > 0: return float(o) else: return int(o) return super(DecimalEncoder, self).default(o) def lambda_handler(event, context): if "queryStringParameters" in event: parmas = event['queryStringParameters'] print (parmas) postId = parmas["postId"] dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) if postId == "*": items = table.scan() else: items = table.query(KeyConditionExpression=Key('id').eq(postId)) response = { 'statusCode': 200, 'body': json.dumps(items["Items"], cls=DecimalEncoder), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response |
SAM 템플릿을 GetNews 함수에 맞게 기존 설정을 지우고 아래와 같이 추가하고 저장한 후, Deploy를 수행합니다.
Resources: ... GetNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: GetNews Description: Gather information from Ajax calls from web pages Tracing: Active Events: GetNewsApi: Type: Api Properties: Path: /news Method: GET Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:Query' - 'dynamodb:Scan' Resource: '*' |
코드는 다음을 참고합니다. 해당 함수는 postId 값을 확인한 후에, dynamoDB에 등록된 정보와 생성된 MP3를 삭제하는 로직이 들어가 있습니다.
from __future__ import print_function import boto3 import os import json from boto3.dynamodb.conditions import Key, Attr def lambda_handler(event, context): if "body" in event: params = json.loads(event['body']) print (params) # Bad Request if params["postId"] is None or params["postId"] == "": response = { 'statusCode': 400, 'body': json.dumps({'message': "An unknown error has occurred. Missing required parameters."}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response postId = params["postId"] dynamodb = boto3.resource('dynamodb') table = dynamodb.Table(os.environ['DB_TABLE_NAME']) table.delete_item(Key={"id":postId}) s3 = boto3.client('s3') s3.delete_object(Bucket=os.environ['BUCKET_NAME'], Key= postId + ".mp3") response = { 'statusCode': 200, 'body': json.dumps({'message': "item is deleted : " + postId}), 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response |
DeleteNews 를 위해서 SAM 템플릿을 아래와 같이 추가합니다. SAM 템플릿을 수정 저장한 후에는 반드시 Deploy를 해서 정상적으로 템플릿이 배포가 되었는지 확인을 합니다.
Resources: ... DeleteNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: DeleteNews Description: Delete news item in DynamoDB Table and mp3 file in S3 bucket. Tracing: Active Events: DeleteNewsApi: Type: Api Properties: Path: /news Method: DELETE Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:DeleteItem' - 's3:DeleteObject' Resource: '*' |
삭제 이벤트에 대해서 DynamoDB의 항목을 삭제하기 위해서 dynamodb:DeleteItem 정책과 S3에 생성된 MP3 파일을 삭제하기 위해서 s3:DeleteObject 정책을 추가합니다.
SAM 템플릿에서 생성된 리소스를 확인하기 위해서 CloudFormation의 Stack 상세에서 Outputs으로 출력을 만들 수 있습니다. 여기서는 3개의 출력물을 생성합니다.
Outputs: S3WebBucket: Description: S3 Bucket Name for web hosting Value: Ref: StaticWebBucket WebsiteURL: Description: Name of S3 bucket to hold website content Value: 'Fn::Join': - '' - - 'https://' - 'Fn::GetAtt': - StaticWebBucket - DomainName - '/index.html' APIEndpointURL: Description: URL of your API endpoint Value: 'Fn::Sub': >- https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/news |
S3에서 제공하는 정적 웹 호스팅을 하는 버킷에서 제공하는 URL의 도메인과 API Gateway 에서 배포한 API Endpoint URL의 도메인은 서로 상이합니다. 따라서 브라우저에서 발생하는 보안 이슈를 해결하기 위해서는 CORS 설정을 해야합니다. 이 설정 역시 SAM에 모든 API Gateway에 추가할 수 있습니다. 따라서 Globals에 API 영역에 다음과 같이 추가합니다. SAM 템플릿을 수정 저장한 후에는 반드시 Deploy를 해서 정상적으로 템플릿이 배포가 되었는지 확인을 합니다.
Globals: ... Api: Cors: AllowMethods: "'*'" AllowHeaders: "'*'" AllowOrigin: "'*'" |
지금까지의 SAM을 정리하면 다음과 같습니다.
AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: >- Building Serverless development environment and CI/CD process for DevOps based on Cloud9 Globals: Function: Runtime: python2.7 Handler: lambda_function.lambda_handler MemorySize: 128 Timeout: 60 Environment: Variables: DB_TABLE_NAME: Ref: NewsTable SNS_TOPIC: Ref: NewsTopic BUCKET_NAME: Ref: PollyMp3Bucket Api: Cors: AllowMethods: "'*'" AllowHeaders: "'*'" AllowOrigin: "'*'" Resources: NewsTable: Type: 'AWS::Serverless::SimpleTable' Properties: PrimaryKey: Name: id Type: String ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 NewsTopic: Type: 'AWS::SNS::Topic' Properties: DisplayName: NewsTopic PollyMp3Bucket: Type: 'AWS::S3::Bucket' StaticWebBucket: Type: 'AWS::S3::Bucket' Properties: AccessControl: PublicRead WebsiteConfiguration: IndexDocument: index.html ErrorDocument: error.html PostNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: PostNews Description: Post news text to convert from text to speech Tracing: Active Events: PostNewsApi: Type: Api Properties: Path: /news Method: POST Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 's3:PutObject' - 'sns:Publish' - 'dynamodb:PutItem' - 'polly:StartSpeechSynthesisTask' Resource: '*' UpdateNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: UpdateNews Description: Update News via Amazon SNS Tracing: Active Events: ConvertResource: Type: SNS Properties: Topic: Ref: NewsTopic Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:UpdateItem' - 's3:PutObjectAcl' Resource: '*' GetNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: GetNews Description: Gather information from Ajax calls from web pages Tracing: Active Events: GetNewsApi: Type: Api Properties: Path: /news Method: GET Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:Query' - 'dynamodb:Scan' Resource: '*' DeleteNews: Type: 'AWS::Serverless::Function' Properties: CodeUri: DeleteNews Description: Delete news item in DynamoDB Table and mp3 file in S3 bucket. Tracing: Active Events: DeleteNewsApi: Type: Api Properties: Path: /news Method: DELETE Policies: - Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:CreateLogStream' - 'dynamodb:DeleteItem' - 's3:DeleteObject' Resource: '*' Outputs: S3WebBucket: Description: S3 Bucket Name for web hosting Value: Ref: StaticWebBucket WebsiteURL: Description: Name of S3 bucket to hold website content Value: 'Fn::Join': - '' - - 'https://' - 'Fn::GetAtt': - StaticWebBucket - DomainName - '/index.html' APIEndpointURL: Description: URL of your API endpoint Value: 'Fn::Sub': >- https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/news |
정적 웹 호스팅 파일 다운로드 받기 (Command Line Interface)
wget http://polly.awsdemokr.com/301_static_web.zip |
압축 풀고 폴더 이동 (Command Line Interface)
unzip 301_static_web.zip cd 301_static_web |
Cloud9에서 scripts.js 파일을 열고, CloudFormation Stack에 배포된 Output의 APIEndpointURL 값을 scripts.js 소스코드 첫 줄의 API_ENDPOINT 에 반영 (WebsiteURL이 아니므로 주의)
CloudFormation에 접속해서 Cloud9-WebApp 스택을 클릭합니다.
스택 상세 하단에 Outputs에 있는 리소스 2개(APIEndPointURL, S3WebBucket)를 확인합니다.
Cloud9에서 다운 받아서 압축을 해제한 scripts.js 파일을 열고 첫 줄을 수정하고 저장합니다.
var API_ENDPOINT = "https://xxxxxxxxxx.execute-api.ap-southeast-1.amazonaws.com/Prod/news/"; if (API_ENDPOINT === "") { alert("scripts.js 파일의 상단에 API Gateway에 배포한 URL을 등록하고 실행하세요."); } |
정적 웹 포스팅하고자 하는 S3 버킷에 public-read 권한으로 파일을 업로드 (CloudFormation Stack에 배포된 Output의 S3WebBucket 값을 아래에 대체)
aws s3 sync . s3://cloud9-webapp-staticwebbucket-xxxxxxxxxxxx --acl public-read |
PostNews를 호출하기 위해서 음성으로 변환하고자 하는 텍스트를 입력합니다.
안녕하세요. 서연입니다. 철수. 귀신. |
먼저 SAM 템플릿 배포를 위해서 S3 버킷을 하나 생성(예: template-deploy-301 이름을 변경 )합니다. 해당 S3 버킷에 패키지를 업로드 합니다.
aws s3 mb s3://template-deploy-301 |
Cloud9 IDE 하단의 ~/ environment 디렉토리에서 하단의 작업을 진행할 수 있습니다.
sam package --template-file WebApp/template.yaml --output-template-file webapp-output.yaml --s3-bucket template-deploy-301 |
template-depoly-hyouk 버킷에는 배포에 필요한 아티팩트가 들어가 있습니다. 각 람다 함수별로 패키징된 임의의 파일들이 생성되는 것을 확인할 수 있습니다.
각각의 파일은 ZIP 파일로 바로 Lambda 함수에 배포할 수 있는 코드의 묶음입니다. 직접 다운로드 해서 unzip을 하면 확인할 수 있습니다.
다음과 같이 WebAppTest 라는 새로운 스택으로 배포를 진행할 수 있습니다. webapp-outpuy.yaml 파일에는 Function의 CodeUri가 S3로 변경되어져 있는 것을 확인할 수 있습니다.
sam deploy --template-file /home/ec2-user/environment/webapp-output.yaml --stack-name WebAppTest --capabilities CAPABILITY_IAM |
Lab1에서 SAM을 구성하는 방법을 배웠습니다. Lab2에서는 서버리스 서비스의 CICD 배포 프로세스를 이해하는 실습입니다. CodeStar를 이용하여 서버리스 서비스를 하나 배포합니다. CodeStar는 소스 리포지토리인 CodeCommit, 소스를 빌드하고 단위테스트를 지원하는 CodeBuild, 서버리스 서비스를 배포하기 위한 CloudFormation을 지원하고 있으며, SAM에서 CodeDeploy 를 지원하기 위해서 SAM 안에 간단한 설정만으로 Canary 배포 환경을 구축할 수 있습니다. CodeStar는 위에서 언급한 Code 시리즈 서비스를 CodePipeline에 의해서 하나의 CI/CD 배포 프로세스를 자동으로 구성합니다. IDE는 Cloud9을 선택할 수 있으며, Cloud9이 배포가 완료되면 자동으로 CodeCommit에 배포된 blueprint가 clone 되는 것을 스크립트로 확인할 수 있습니다.
Lab2에서는 서버리스 서비스의 CICD 배포 프로세스 이해를 위해서 단위 테스트를 적용해서 CodeBuild에서 테스틑 하는 방법과 Canary 배포하는 방법, 그리고 Lambda 함수를 이전에 배포한 버전으로 Rollback 하는 방법을 살펴 봅니다.
참고: Lab2는 Change a Serverless Project to Shift Traffic Between AWS Lambda Function Versions을 참고하여 작성되었으며, 추가 작업을 포함했습니다. 다양하게 응용하여 테스트 할 수 있습니다.
일정 시간이 지나면 Cloud9이 기동되는 것을 확인할 수 있습니다. 또한 CICD에 의해서 최초 배포가 종료되면 API Gateway로 배포한 Endpoint URL이 나오는 것을 알 수 있습니다. 이 URL을 이용해서 추후 Canary 배포에 의해서 Traffic Shift가 잘 이루어지는지, Rollback은 잘 동작하는지를 확인합니다. Start coding 버튼을 클릭하여 Cloud9으로 접속합니다.
Cloud9에 접속하게 되면 자동으로 CodeCommit에 연결하고 소스코드를 Cloud9 환경에 설치합니다. 동작 구조가 궁금할 경우 쉘 스크립트 파일을 확인할 수 있습니다.
Lab1의 구조와 다른 점은 Unit 테스트를 하기 위한 test_handler.py 파일이 포함되어져 있습니다.
그리고 CodeBuild를 통해서 SAM 및 코드를 실행 및 테스트 하기 위한 단계를 기술한 buildspec.yml 파일이 있는것을 확인할 수 있습니다.
buildspec.yml 파일은 다음과 같은 형태로 구성되어져 있습니다.
CodeBuild가 시작되면, 자동으로 buildspec.yml 파일을 찾아서 스크립트를 실행합니다.
install: 환경을 구성하기 위해서 최초에 해야할 명령을 기술 합니다.
pre_build: 패키징 작업을 하기 전에 단위테스트를 수행할 수 있습니다.
build: 패키징 작업을 하고 해당 아티팩트를 S3 버킷에 업로드 합니다.
artifacts: 최종 산출물(template-export.yml)을 Zip 파일 형태로 작성합니다.
CodeStarWoker-webappcodestar-CloudFormation Role을 선택하고 Permissions 탭에서 CodeStarWorkerCloudFormationRolePolicy 를 선택합니다. 그리고 해당 Policy에 CodeDeploy를 이용해서 Lambda에서 Canary 배포를 할 수 있도록 추가 권한을 추가합니다. 아래 코드에서 region 은 ap-southeast-1(5개) 로 수정하고, id 는 본인 AWS 계정 넘버 12자리(6개) 를 입력합니다.
(코드를 추가할 때에는, 기존 코드가 끝나는 부분에 ,(컴마)를 찍고, 해당 Policy를 추가해야 합니다.)
{ "Action": [ "s3:GetObject", "s3:GetObjectVersion", "s3:GetBucketVersioning" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:PutObject" ], "Resource": [ "arn:aws:s3:::codepipeline*" ], "Effect": "Allow" }, { "Action": [ "lambda:*" ], "Resource": [ "arn:aws:lambda:region:id:function:*" ], "Effect": "Allow" }, { "Action": [ "apigateway:*" ], "Resource": [ "arn:aws:apigateway:region::*" ], "Effect": "Allow" }, { "Action": [ "iam:GetRole", "iam:CreateRole", "iam:DeleteRole", "iam:PutRolePolicy" ], "Resource": [ "arn:aws:iam::id:role/*" ], "Effect": "Allow" }, { "Action": [ "iam:AttachRolePolicy", "iam:DeleteRolePolicy", "iam:DetachRolePolicy" ], "Resource": [ "arn:aws:iam::id:role/*" ], "Effect": "Allow" }, { "Action": [ "iam:PassRole" ], "Resource": [ "*" ], "Effect": "Allow" }, { "Action": [ "codedeploy:CreateApplication", "codedeploy:DeleteApplication", "codedeploy:RegisterApplicationRevision" ], "Resource": [ "arn:aws:codedeploy:region:id:application:*" ], "Effect": "Allow" }, { "Action": [ "codedeploy:CreateDeploymentGroup", "codedeploy:CreateDeployment", "codedeploy:DeleteDeploymentGroup", "codedeploy:GetDeployment" ], "Resource": [ "arn:aws:codedeploy:region:id:deploymentgroup:*" ], "Effect": "Allow" }, { "Action": [ "codedeploy:GetDeploymentConfig" ], "Resource": [ "arn:aws:codedeploy:region:id:deploymentconfig:*" ], "Effect": "Allow" } |
SAM 템플릿 파일이 변경되었으므로 Code를 CodeCommit에 check-in 해 줍니다. 아래와 같이 폴더를 이동한 다음 git push르 합니다.
cd webappcodestar git add . git commit -m 'Change SAM template' git push origin master |