버전 비교

  • 이 줄이 추가되었습니다.
  • 이 줄이 삭제되었습니다.
  • 서식이 변경되었습니다.

...

  1. Cloud9 IDE 환경 생성

    1. AWS 콘솔 환경에서 Cloud9 서비스로 이동합니다.


    2. Create environment 버튼을 클릭하여 Cloud9 환경을 생성합니다.


    3. Cloud9 이름을 NewsWebApp 이라고 생성합니다. 설명(News Web Application using Serverless Service)은 옵션이기 때문에 넣지 않아도 됩니다. 


    4. Cloud9의 환경설정을 합니다. 개발환경은 EC2 인스턴스로 선택하고 인스턴스 타입은 t2.micro를 선택합니다. 프리티어로 사용할 수 있습니다. Cloud9 IDE를 30분간 사용하지 않으면, 자동으로 EC2 인스턴스가 Stop 되어 비용을 절감할 수 있는 옵션을 기본적으로 제공합니다.


    5. Cloud9 최종 점검을 합니다. 모든 검수가 완료되면 Create environment 버튼을 클릭합니다.


    6. Cloud9 IDE가 준비중인 것을 확인할 수 있습니다. 몇 분이 지나면 IDE가 활성화 됩니다.


    7. Cloud9에서 IDE에 대해서 환경 설정을 할 수 있습니다. 화면과 같이 서비스 배포를 위한 리전을 별도로 지정할 수 있습니다. 여기서는 Singapore 리전을 그대로 사용합니다.


    8. 개발 환경이 설치되고 서버리스 애플리케이션 개발을 할 준비가 완료되었습니다.

  2. Application 및 "PostNews" Lambda 함수 생성

    1. 새로운 애플리케이션 및 람다 함수를 생성합니다. Cloud9 IDE 우측 네비게이션의 AWS Resources 탭을 클릭하고, Lambda 아이콘을 클릭하면 새로운 함수를 생성할 수 있습니다. 앞으로 추가할 나머지 3개의 함수도 동일한 방법으로 생성합니다.


    2. 화면 중앙에 팝업 창이 표시됩니다. 하단의 Application name에는 WebApp을 상단의 Function name에는 PostNews를 입력하고, Next 버튼을 클릭합니다. 앞으로 추가할 나머지 3개의 함수도 Application name은 동일하게 WebApp을 사용합니다.


    3. Runtime은 Python 2.7을 선택하고, 하단의 Select blueprint는 hello-world-python을 선택하고, Next 버튼을 클릭합니다.


    4. PostNews 함수를 호출하는 주체는 API Gateway입니다. 따라서, Function trigger는 API Gateway를 선택합니다. API 호출에 사용할 리소스 경로는 /news 를 입력합니다. API 호출을 위한 보안 메커니즘은 NONE으로 선택하고, Next 버튼을 클릭합니다.


    5. PostNews 함수를 실행하는 Memory는 128MB로 지정합니다. Role은 Automatically generate role을 선택하고, Next 버튼을 클릭합니다. Role에 들어갈 정책(Policy)는 SAM 템플릿에서 직접 설정할 예정입니다. 


    6. PostNews 함수 구성을 확인합니다. 별도의 이상이 없을 경우, Finish 버튼을 클릭 합니다.
      Image Modified

    7. 다음과 같이 Application과 Function이 세팅되는 것을 확인할 수 있습니다.
      Image Modified
      1. 좌측 Environment 탭에 WebApp Application 폴더를 생성합니다. 그리고 PostNews 함수에서 사용하는 폴더를 생성합니다.
      2. SAM 템플릿 파일인 template.yaml 파일이 생성되는것을 확인할 수 있습니다.
      3. Lambda 함수가 blueprint에 의해서 자동으로 만들어집니다. 해당 코드는 아래에서 제공하는 PostNews 함수 코드로 대체합니다.
      4. 우측 AWS Resources 탭에 Local Functions에 현재 작업중인 WebApp 폴더와 Application과 PostNews 함수를 생성하는 것을 확인할 수 있습니다.
      5. 자동으로 template을 기반으로 Lambda 서비스에 배포할 수 있습니다. 배포할 경우, 이름은 Cloud9- 접두사와 Application 이름인 WebApp-이 접두사로 붙는 것을 확인할 수 있습니다.

    8. PostNews 함수는 아래에서 제공하는 코드로 대체하고 Ctrl+S를 눌러서 저장합니다. 코드 설명은 주석을 기반으로 생략합니다.
      Image Modified

      코드 블럭
      languagepy
      themeRDark
      titlePostNews
      linenumberstrue
      # -*- coding: utf-8 -*-
      from __future__ import print_function
      
      import boto3
      import os
      import json
      import uuid
      import datetime
      
      def lambda_handler(event, context):
          if "body" in event:
              event = json.loads(event['body'])
          print (event)
          
          recordId = str(uuid.uuid4())
          voice = event["voice"]
          originText = event["text"]
          timbre = event["timbre"]
          pitch = event["pitch"]
          updateDate = datetime.datetime.now().strftime("%Y%m%d")
          print('Generating new DynamoDB record, with ID: ' + recordId)
      
          # Create the item in DynamoDB table
          dynamodb = boto3.resource('dynamodb')
          table = dynamodb.Table(os.environ['DB_TABLE_NAME'])
          table.put_item(
              Item={
                  'id' : recordId,
                  'originText': originText,
                  'postDate': int(updateDate),
                  'pollyVoice' : voice,
                  'pollyStatus' : "PROCESSING",
                  'pollyTimbre': timbre,
                  'pollyPitch': pitch
              }
          )
          
          # Sending notification about new post to SNS
          client = boto3.client('sns')
          client.publish(
              TopicArn = os.environ['SNS_TOPIC'],
              Message = recordId
          )
          
          response = {
              'statusCode': 200,
              'body': json.dumps({'recordId': recordId}),
              'headers': {
                  'Content-Type': 'application/json',
                  'Access-Control-Allow-Origin': '*'
              }
          }
          
          return response


    9. 이번에는 SAM Template을 변경합니다. 먼저 좌측 소스코드 영역에서 template.yaml 파일을 더블클릭하여 편집 창을 열고, 아래 Template 코드로 대체하고, Ctrl+S를 눌러서 저장합니다.
      Image Modified

      코드 블럭
      languageyml
      themeRDark
      titlePostNewsTemplate_01
      linenumberstrue
      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
            Events:
              PostNewsApi:
                Type: Api
                Properties:
                  Path: /news
                  Method: POST
            Policies:
              - Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - 'logs:PutLogEvents'
                      - 'logs:CreateLogStream'
                      - 'dynamodb:PutItem'
                      - 'sns:Publish'
                    Resource: '*'


    10. SAM 템플릿 구성은 다음과 같습니다.
      Image Modified

      1. Globals는 전역으로 사용합니다. 함수(Function)이나 API에서 반복해서 사용하는 것을 선언할 수 있습니다.

      2. Resources는 AWS 서비스를 정의합니다. 각 서비스에 대해서 이름을 선언하고, Type을 정할 수 있습니다.
      3. Runtime은 Function에서 사용합니다. 4개의 Lambda 함수가 모두 Python 2.7 기반이기 때문에 python2.7을 선언합니다.
      4. Type은 서비스 타입을 의미합니다. Lambda 함수, API Gateway, DynamoDB, SNS, S3 등을 표기할 수 있습니다.
        CodeUri는 Lambda 함수가 위치한 폴더를 의미합니다. template.yaml 파일 기준으로 Lambda 함수가 있는 폴더의 이름을 지정합니다.
      5. Handler는 Lambda 함수가 시작하는 함수의 파일명.함수명 에 대한 구성을 의미 합니다. 모두 lambda_function.py 파일에 선언되어 있는 lambda_handler 함수가 시작 함수가 됩니다. 각각의 함수는 폴더가 서로 다릅니다.
      6. MemorySize128MB로 모두 구성되어 있으며, Lambda 함수의 최대 구동 시간은 Timeout 값을 사용하고 60초로 설정합니다.
      7. Events는 Lambda 함수를 트리거하는 이벤트를 의미합니다. API Gateway일 경우 TypeApi가 됩니다. SNS에 의해서 트리거 될 경우, Type은 Sns가 됩니다.
      8. Policies는 해당 함수가 접근할 수 있는 서비스에 대한 권한을 줄 수 있습니다. 각각의 함수는 최소 권한의 원칙에 의거하여 필요한 정책만을 연결합니다.
        PostNews 함수는 동작 로그를 남기기 위해서 logs:PutLogEvents logs:CreateLogStream을, DynamoDB에 항목을 추가하기 위해서 dynamodb:PutItem을 SNS Topic에 게시하기 위해서 sns:Publish를 설정합니다.

    11. SAM

    기반 서버리스 리소스 추가
  3. "ConvertAudio" Lambda 함수 생성

  4. "GetNews" Lambda 함수 생성

  5. "DeleteNews" Lambda 함수 생성

  6. SAM에서의 Outputs 설정

  7. SAM에서의 CORS 설정

  8. 정적 웹 호스팅을 위한 파일 업로드하기

  9. 서비스 동작 테스트

  10. SAM을 CloudFormation 스택에 직접 반영하기

Lab2. Code* 서비스를 이용한 서버리스 CI/CD 배포 프로세스 구축

  1. 소스 리포지토리를 위해서 CodeCommit 생성

  2. CodeBuiild를 위한  buildspec.yaml 파일 생성

  3. CodePipeline 구축하기

  4. 소스 리포티토리에 코드 체크인하기

  5. 배포 결과 확인 (S3 정적 웹 페이지는 다루지 않습니다.)

  6. API를 이용해서 결과 확인

  7. Canary 배포를 위한 설정하기

  8. CloudFormation에서 CodeDeploy 할 수 있도록 IAM 정책 설정 적용

  9. 코드를 변경하여 배포하고 CodeDeploy 중에 API를 호출하여 적용되는지 확인하기

  10. 기존 배포 버전으로 롤백하기

Lab3. X-ray를 이용한 서버리스 서비스 모니터링 및 디버깅 (9월 예정)

Lab4. LocalStack을 이용한 로컬 테스트 환경 구축 및 테스트 (미정)

Lab5. ElasticSearch를 이용한 검색 서비스 구축 (미정)

Application 및 "PostNews" Lambda 함수 생성

  1. 배포하기
    Image Removed

필수 리소스를 SAM에 추가하고 리소스 확인

    1. 설정이 완료되면 해당 template.yaml 파일을 Cloud9에서 바로 배포할 수 있습니다. 
      Image Added

      1. 우측의 해당 Lambda 함수에서 마우스 우측 버튼을 클릭하고 Deploy 명령을 수행합니다. 해당 SAM template이 CloudFormation Stack에 업데이트 되는 것을 확인할 수 있습니다.


  1. SAM 기반 서버리스 리소스 추가

    1. SAM 템플릿을 이용해서 S3 Bucket, SNS, DynamoDB 테이블을 배포할 수 있습니다. 다음과 같이 template.yaml 파일에 리소스에 추가합니다.
      Image Added

      1. Environment: 환경 변수를 선언합니다. 각각의 Lambda 함수에는 DynamoDB, SNS, S3 버킷에 접근하기 위해서 ARN이 필요합니다. SAM에서 자동으로 만들어준 각각의 리소스 ARN을 참조할 수 있도록 설정합니다.

      2. NewsTable: SAM에서 SimpleTable은 DynamoDB를 의미합니다. 여기서 DynamoDB를 기본 세팅인 WCU 5, RCU 5 값으로 생성합니다.

      3. NewsTopic: PostNews에 들어온 텍스트 값은 비동기적으로 처리를 위하여 ConvertAudio 함수의 처리를 필요로 합니다. ConvertAudio의 처리를 위해서 사용할 SNS Topic을 하나 생성합니다.

      4. PollyMp3Bucket: Amazon Polly를 이용해서 생성한 MP3 파일을 저장할 S3 버킷을 생성합니다.

      5. StaticWebBucket: S3는 객체 스토리지이면서 정적 웹 호스팅을 위한 서비스로 활용할 수 있습니다. 여기서는 정적 웹 호스팅을 위한 설정까지 함께 구성하여 SAM에 서비스를 추가합니다.

      6. 변경된 SAM 템플릿을 반영하기 위해서 우측 Lambda 함수에서 마우스 우측 버튼을 클릭하고 Deploy 명령을 수행합니다.

    가나다
    Image Removed가나다
      1. 코드 블럭
        languageyml
        themeRDark
        titlePostNewsTemplate_02
        linenumberstrue
        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
              Events:
                PostNewsApi:
                  Type: Api
                  Properties:
                    Path: /news
                    Method: POST
              Policies:
                - Version: '2012-10-17'
                  Statement:
                    - Effect: Allow
                      Action:
                        - 'logs:PutLogEvents'
                        - 'logs:CreateLogStream'
                        - 'dynamodb:PutItem'
                        - 'sns:Publish'
                      Resource: '*'
  2. 가나다
    Image Removed
    Image Removed

    1. AWS 관리 콘솔에서 CloudFormation 서비스로 이동하면 다음과 같이 Stack을 확인할 수 있습니다.
      Image Added
      1. Cloud9 환경을 배포했기 때문에 해당 Stack(aws-cloud9-NewsWebApp-xxxxxxxxxx)이 등록되어 있는 것을 확인할 수 있습니다.
      2. Cloud9 환경에서 만든 WebApp이 배포된 Cloud9-WebApp Stack을 확인할 수 있습니다.

    2. 아래와 같이 Cloud9에서 SAM template을 변경하고 Delpoy 하면, 해당 Stack이 업데이트 되는 것을 확인할 수 있습니다.
      Image Added

    3. Cloud9-WebApp을 클릭하고 들어가면, Stack에 대해서 자세히 확인할 수 있습니다. 현재 추가로 배포한 리소스가 배포 완료된 것을 확인할 수 있습니다.
    1. Image Modified

  3. "ConvertAudio" Lambda 함수 생성

  1. 가나다
    Image Removed
  2. 가나다
    Image Removed
  3. 가나다
    Image Removed
  4. 가나다
    Image Removed
  5. 가나다
    Image Removed
  6. 가나다
    Image Removed
  7. 가나다
    Image Removed

"GetNews" Lambda 함수 생성

  1. 가나다
    Image Removed
  2. 가나다
    Image Removed
  3. 가나다
    Image Removed

"DeleteNews" Lambda 함수 생성

  1. 가나다
    Image Removed
  2. 가나다
    Image Removed
  3. 가나다
    Image Removed

S3 정적 컨텐츠 업로드

...

정적 웹 호스팅 파일 다운로드 받기

코드 블럭
languagebash
themeRDark
linenumberstrue
wget https://s3.ap-northeast-2.amazonaws.com/polly.awsdemokr.com/301_static_web.zip

축 풀고 폴더 이동

코드 블럭
languagebash
themeRDark
linenumberstrue
unzip 301_static_web.zip
cd 301_static_web

Cloud9에서 scripts.js 파일 열어서 CloudFormation Stack에 배포된 Output의 APIEndpointURL 값을 소스코드에 반영 (WebsiteURL이 아니므로 주의)

코드 블럭
languagejs
themeRDark
linenumberstrue
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 값을 아래에 대체)

코드 블럭
languagebash
themeRDark
linenumberstrue
aws s3 sync . s3://cloud9-webapp-staticwebbucket-xxxxxxxxxxxx --acl public-read

...

  1. https://cloud9-webapp-staticwebbucket-xxxxxxxxxxxx.s3.amazonaws.com

Lab2. CodePipeline을 생성하여 CI/CD 프로세스 만들기

  1. CodeCommit 생성
  2. CoudBuild를 위한 nuildspec.yml 만들기
  3. CodePipeline 생성하기
  4. CodeDeploy 배포 (카나리 배포)
  5. 롤백하기 (Lambda - API Gateway)

...

languagepy
themeRDark
titleConverAudio
linenumberstrue

...

    1. 우측 AWS Resources 탭에 있는 Lambda 아이콘을 클릭하여 두 번째 함수인 ConvertAudio 함수를 생성합니다. Application name은 WebApp이고, Function name은 ConvertAudio를 입력하고 우측 하단의 Next 버튼을 클릭합니다.
      Image Added

    2. Runtime은 Python2.7을 선택하고 blueprint는 hello-world-python을 선택하고, Next 버튼을 클릭합니다.
      Image Added
    3. ConvertAudio 함수는 trigger가 SNS에 의하여 됩니다. PostNews 설정과 동일하게 API Gateway로 설정했지만, SAM의 Events에서 SNS를 사용하도록 변경할 예정입니다. 다음 함수 설정을 위해서 API Gateway를 Function trigger로 선택하고, 리소스 경로는 /news , 보안은 NONE 을 동일하게 선택합니다.
      Image Added

    4. ConvertAudio 함수를 실행하는 Memory는 128MB로 지정합니다. Role은 Automatically generate role을 선택하고, Next 버튼을 클릭합니다. Role에 들어갈 정책(Policy)는 SAM 템플릿에서 직접 설정할 예정입니다. 
      Image Added

    5. ConvertAudio 함수 구성을 확인합니다. 별도의 이상이 없을 경우, Finish 버튼을 클릭 합니다.
      Image Added

    6. ConvertAudio 함수 코드를 다음과 같이 변경하고 저장합니다. 소스 코드의 설명은 주석으로 대체합니다.

      코드 블럭
      languagepy
      themeRDark
      titleConverAudio
      linenumberstrue
      # -*- coding: utf-8 -*-
      from __future__ import print_function
      
      import boto3
      import os
      from contextlib import closing
      from boto3.dynamodb.conditions import Key, Attr
      import re
      
      def lambda_handler(event, context):
          postId = event["Records"][0]["Sns"]["Message"]
          print ("Text to Speech function. Post ID in DynamoDB: ", postId)
          
          # Retrieving information about the post from DynamoDB table
          dynamodb = boto3.resource('dynamodb')
          table = dynamodb.Table(os.environ['DB_TABLE_NAME'])
          postItem = table.query(
              KeyConditionExpression=Key('id').eq(postId)
          )
          
          text = postItem["Items"][0]["originText"]
          voice = postItem["Items"][0]["pollyVoice"]
          timbre = postItem["Items"][0]["pollyTimbre"]
          pitch = postItem["Items"][0]["pollyPitch"]
          rest = text
          
          # Because 

...

    1. single 

...

    1. invocation 

...

    1. of the 

...

    1. polly 

...

    1. synthesize_speech 

...

    1. api 

...

    1. can
          # 

...

    1. transform text 

...

    1. with about 3,000 characters, we are dividing the
          # post into blocks of approximately 2,900 characters.
          textBlocks = []
        

...

    1.   while (len(rest) > 3000):
          

...

    1.     

...

    1. begin = 

...

    1. 0

...

    1. 
          

...

    1.     end = 

...

    1. rest.find(".", 2900)
          

...

    1.     if (end == -1):
            

...

    1.  

...

    1.  

...

    1.     

...

    1. end = 

...

    1. rest.find(" ", 2900)
          

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1. textBlock 

...

    1. = rest[begin:end]
          

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1. rest 

...

    1. = rest[end:]
          

...

    1.  

...

    1.  

...

    1.  

...

    1.  textBlocks.append(textBlock)
          textBlocks

...

    1. .append(rest)
          

...

    1.  

...

    1. 
       

...

    1.  

...

    1.   #For each block, invoke Polly API, 

...

    1. which 

...

    1. will 

...

    1. transform text into audio
          polly 

...

    1. = 

...

    1. boto3.

...

    1. client('polly')
          for textBlock 

...

    1. in textBlocks:
              

...

    1. removeBrackets 

...

    1. = 

...

    1. re.sub(r'\([^)]*\)', '', textBlock)
              

...

    1. repTextBlock = 

...

    1. re.sub('[·…]', '<break time="100ms"/>', removeBrackets)
              

...

    1. #repTextBlock = 

...

    1. re.sub('["·\'…]', '<break time="100ms"/>', removeBrackets)
          

...

    1.     ssmlBlock 

...

    1. = "<speak><amazon:effect vocal-tract-length=\"" + 

...

    1. timbre 

...

    1. + "\"><prosody pitch=\"" + pitch + "\">" + repTextBlock + "</prosody></amazon:effect></speak>"
             

...

    1.  

...

    1. #print 

...

    1. (ssmlBlock)
          

...

    1.  

...

    1.  

...

    1.  

...

    1.  response = polly.synthesize_speech(OutputFormat='mp3', Text = ssmlBlock, 

...

    1. VoiceId = 

...

    1. voice, TextType = 'ssml')
          
            

...

    1.  

...

    1.  #Save the audio stream returned by Amazon Polly on Lambda's temp
              

...

    1. # directory. If there are multiple text blocks, the audio stream
              

...

    1. # 

...

    1. will be combined into a single file.
              if "AudioStream" in response:
              

...

    1.     with 

...

    1. closing(response["AudioStream"]) as stream:
                

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1. output = 

...

    1. os.path.join("/tmp/", postId)
          

...

    1.         

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1. with open(output, "a") as file:
                

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  file.write(stream.read())
              

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.      
         

...

    1.  

...

    1. s3 

...

    1. = boto3.client('s3')
          s3.upload_file('/tmp/' + postId,
            

...

    1.   os.environ['BUCKET_NAME'],
              postId + ".mp3")
          

...

    1. s3.put_object_acl(ACL='public-read',
              Bucket=os.environ['BUCKET_NAME'],
              Key= 

...

    1. postId + "

...

    1. .mp3")

...

    1. 
                  
          location = 

...

    1. s3.get_bucket_location(Bucket=os.environ['BUCKET_NAME'])
          region = location['LocationConstraint']
          
          if region is None:
         

...

    1.      

...

    1. url_begining = 

...

    1. "https://s3.amazonaws.com/"
          else:
            

...

    1.   url_begining = "https://s3-" + str(region) + ".amazonaws.com/" \
              
          url = url_begining \
              + str(os.environ['BUCKET_NAME']

...

    1. ) \
      

...

    1.        

...

    1.  + "

...

    1. /"

...

    1.  \
         

...

    1.      + 

...

    1. str(postId) \
              

...

    1. + ".mp3"

...

    1. 
      
          #Updating the item in 

...

    1. DynamoDB
          

...

    1. response = 

...

    1. table.

...

    1. update_

...

    1. item(
          

...

    1.     Key=

...

    1. {'

...

    1. id':postId},
          

...

    1.      

...

    1.  

...

    1.  

...

    1.  

...

    1. UpdateExpression=
              

...

    1.     

...

    1.     "SET #statusAtt = 

...

    1. :statusValue, #urlAtt = 

...

    1. :urlValue",
                  

...

    1. ExpressionAttributeValues=
          

...

    1.  

...

    1.  

...

    1.  

...

    1.         

...

    1.  {':statusValue': 'UPDATED', ':urlValue': url},
            

...

    1.  

...

    1.  

...

    1.     ExpressionAttributeNames=
          

...

    1.  

...

    1.  

...

    1.         

...

    1.  

...

    1.  {'#statusAtt': 'pollyStatus', '#urlAtt': 'mp3Url'},
          

...

    1. )
              

...

    1. 
          return


    2. 아래와 같이 자동으로 template.yaml 파일에 ConvertAudio 함수 설정이 추가되는 것을 확인할 수 있습니다. 하지만, 해당 SAM 설정 대신 아래의 SAM 코드로 대체합니다.
      Image Added

      코드 블럭
      languageyml
      themeRDark
      titleCoverAudio Template
      linenumberstrue
      Resources:
      ...
        ConvertAudio:
          Type: 

...

    1. 'AWS::Serverless::Function'
          Properties:
            CodeUri: ConvertAudio
            Description: Convert Audio using Amazon 

...

    1. Polly
       

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  Policies:

...

    1. 
              - Version: '2012-10-17'
          

...

    1.       Statement:
                

...

    1.  

...

    1.  - Effect: Allow
                  

...

    1.   Action:
                      

...

    1. - '

...

    1. logs:PutLogEvents'
          

...

    1.         

...

    1.     

...

languageyml
themeRDark
titleCoverAudio Template
linenumberstrue

...

    1. - 'logs:CreateLogStream'
            

...

    1.  

...

    1.     

...

    1.      - 

...

    1. 'dynamodb:Query'
            

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.  

...

    1.      - 

...

    1. 'dynamodb:UpdateItem'
             

...

    1.          - 

...

    1. 's3:GetBucketLocation'
                  

...

    1.  

...

    1.  

...

    1.   

...

    1. - 's3:PutObject'
                      - '

...

    1. s3:

...

    1. PutObjectAcl'
                      - '

...

    1. polly:

...

    1. SynthesizeSpeech'
                    

...

    1. Resource: '

...

    1. *'
            

...

    1. Events:
              

...

    1. ConvertResource:
                

...

    1. Type: SNS
                

...

    1. Properties:
                  

...

    1. Topic:
            

...

    1.         

...

    1. Ref:

...

    1.  NewsTopic
      


    2. 다음과 같이 SAM template을 수정합니다. 이미 중복되는 설정은 Globals에 선언되어 있기 때문에 간단하게 선언할 수 있습니다.
      Image Added
      1. Policies 에서, DynamoDB에 등록된 News 정보를 읽기 위해서 dynamoDb:Query 를 추가하고, S3에 업로드한 MP3 URL 정보를 업데이트 하기 위해서 dynamodb:UpdateItem 정책을 추가합니다.
      2. S3 버킷에 MP3를 업로드 하기 전, 리전 정보를 검색하기 위해서 s3:GetBucketLocation 을 추가하고, S3에 객체를 추가하기 위해서 s3:PutObject 를 추가하고, 퍼블릭하게 접근할 수 있는 권한 설정을 위해서 s3:PutObjectAcl 정책을 추가합니다.
      3. 텍스트를 MP3로 TTS 변환하기 위해서는 Aamzon Polly 서비스를 사용할 수 있습니다. 해당 Lambda 함수에서 접근할 수 있도록 polly:SynthesizeSpeech 정책을 추가합니다.
      4. ConvertAudio 함수는 SNS의 Topic에 의해서 트리거 됩니다. 따라서 리소스에서 생성한 NewsTopic 에 의해서 트리거 되도록 설정합니다.

  1. "GetNews" Lambda 함수 생성

    1. PostNews와 ConvertAudio 함수처럼 GetNews 함수를 생성합니다.
      Image Added

    2. 코드는 다음을 참고합니다. 함수에 대한 설명은 주석으로 대체합니다.
      Image Added

      코드 블럭
      language
    1. py
      themeRDark
      titleGetNews
      linenumberstrue
      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:
              event = event['queryStringParameters']
          print (event)
          
          postId = event["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


    2. SAM 템플릿을 GetNews 함수에 맞게 기존 설정을 지우고 아래와 같이 추가합니다.

      코드 블럭
      languageyml
      themeRDark
      titleGetNews Template
      linenumberstrue
      Resources:
      ...
        GetNews:
          Type: 'AWS::Serverless::Function'
          Properties:
            CodeUri: GetNews
            Description: Gather information from Ajax calls from web pages
            Policies:
              - Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - 'logs:PutLogEvents'
                      - 'logs:CreateLogStream'
                      - 'dynamodb:Query'
                      - 'dynamodb:Scan'
                    Resource: '*'
            Events:
              GetNewsApi:
                Type: Api
                Properties:
                  Path: /news
                  Method: GET


  1. "DeleteNews" Lambda 함수 생성

    1. 마지막으로 DeleteNews 함수를 생성합니다.
      Image Added

    2. 코드는 다음을 참고합니다. 함수에 대한 설명은 주석으로 대체합니다.
      Image Added

      코드 블럭
      languagepy
      themeRDark
      titleDeleteNews
      linenumberstrue
      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:
              event = json.loads(event['body'])
          print (event)
          
          # Bad Request
          if event["postId"] is None or event["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 = event["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


    3. DeleteNews를 위해서 SAM 템플릿을 아래와 같이 추가합니다.

      코드 블럭
      languageyml
      themeRDark
      titleDeleteNews Template
      linenumberstrue
      Resources:
      ...
        DeleteNews:
          Type: 'AWS::Serverless::Function'
          Properties:
            CodeUri: DeleteNews
            Description: Delete news item in DynamoDB Table and mp3 file in S3 bucket.
            Policies:
              - Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - 'logs:PutLogEvents'
                      - 'logs:CreateLogStream'
                      - 'dynamodb:DeleteItem'
                      - 's3:DeleteObject'
                    Resource: '*'
            Events:
              DeleteNewsApi:
                Type: Api
       

...

    1.          Properties:
                  Path: /news
                  Method: DELETE
      1. 삭제 이벤트에 대해서 DynamoDB의 항목을 삭제하기 위해서 dynamodb:DeleteItem 정책과 S3에 생성된 MP3 파일을 삭제하기 위해서 s3:DeleteObject 정책을 추가합니다.

  1. SAM에서의 Outputs 설정

    1. SAM 템플릿에서 생성된 리소스를 확인하기 위해서 CloudFormation의 Stack 상세에서 Outputs으로 출력을 만들 수 있습니다. 여기서는 3개의 출력물을 생성합니다.

      코드 블럭
      languageyml
      themeRDark
      titleTemplate_Outpuit
      linenumberstrue
      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
      1. S3WebBucket : 정적 웹 호스팅을 하는 S3 버킷 이름
      2. WebsiteURL : 정적 웹 호스팅에 접속하기 위한 URL 정보, 정적 웹 호스팅을 하기 위한 정적 컨텐츠 파일은 추후 업로드 합니다.(html, css, js)
      3. APIEndpointURL : 동적 API를 활용하기 위한 API Gateway가 배포한 Endpoint URL 정보

  2. SAM에서의 CORS 설정

    1. S3에서 제공하는 정적 웹 호스팅을 하는 버킷에서 제공하는 URL의 도메인과 API Gateway 에서 배포한 API Endpoint URL의 도메인은 서로 상이합니다. 따라서 브라우저에서 발생하는 보안 이슈를 해결하기 위해서는 CORS 설정을 해야합니다. 이 설정 역시 SAM에 모든 Lambda 함수에 추가할 수 있습니다. 따라서 Globals에 API 영역에 다음과 같이 추가합니다.


  3. 정적 웹 호스팅을 위한 파일 업로드하기

  4. 서비스 동작 테스트

  5. SAM을 CloudFormation 스택에 직접 반영하기

Lab2. Code* 서비스를 이용한 서버리스 CI/CD 배포 프로세스 구축

  1. 소스 리포지토리를 위해서 CodeCommit 생성

  2. CodeBuiild를 위한  buildspec.yaml 파일 생성

  3. CodePipeline 구축하기

  4. 소스 리포티토리에 코드 체크인하기

  5. 배포 결과 확인 (S3 정적 웹 페이지는 다루지 않습니다.)

  6. API를 이용해서 결과 확인

  7. Canary 배포를 위한 설정하기

  8. CloudFormation에서 CodeDeploy 할 수 있도록 IAM 정책 설정 적용

  9. 코드를 변경하여 배포하고 CodeDeploy 중에 API를 호출하여 적용되는지 확인하기

  10. 기존 배포 버전으로 롤백하기

Lab3. X-ray를 이용한 서버리스 서비스 모니터링 및 디버깅 (9월 예정)

Lab4. LocalStack을 이용한 로컬 테스트 환경 구축 및 테스트 (미정)

Lab5. ElasticSearch를 이용한 검색 서비스 구축 (미정)


Application 및 "PostNews" Lambda 함수 생성

  1. 배포하기

필수 리소스를 SAM에 추가하고 리소스 확인

  1. 가나다


"ConvertAudio" Lambda 함수 생성

"GetNews" Lambda 함수 생성

  1. 가나다

  2. 가나다

  3. 가나다


"DeleteNews" Lambda 함수 생성

  1. 가나다

  2. 가나다

  3. 가나다
    Image Added

S3 정적 컨텐츠 업로드

  1. 가나다
    Image Added
  2. 정적 웹 호스팅 파일 다운로드 받기

    코드 블럭
    languagebash
    themeRDark
    linenumberstrue
    wget https://s3.ap-northeast-2.amazonaws.com/polly.awsdemokr.com/301_static_web.zip


  3. 축 풀고 폴더 이동

    코드 블럭
    languagebash
    themeRDark
    linenumberstrue
    unzip 301_static_web.zip
    cd 301_static_web


  4. Cloud9에서 scripts.js 파일 열어서 CloudFormation Stack에 배포된 Output의 APIEndpointURL 값을 소스코드에 반영 (WebsiteURL이 아니므로 주의)

    코드 블럭
    languagejs
    themeRDark
    linenumberstrue
    var API_ENDPOINT = "https://xxxxxxxxxx.execute-api.ap-southeast-1.amazonaws.com/Prod/news/";
    if (API_ENDPOINT === "")
    {
    

...

languageyml
themeRDark
titleTemplate_Outpuit
linenumberstrue

...

  1.         alert("scripts.js 

...

  1. 파일의 상단에 API Gateway에 배포한 URL을 등록하고 실행하세요.");
    }


  2. 정적 웹 포스팅하고자 하는 S3 버킷에 public-read 권한으로 파일을 업로드 (CloudFormation Stack에 배포된 Output의 S3WebBucket 값을 아래에 대체)

    코드 블럭
    languagebash
    themeRDark
    linenumberstrue
    aws s3 sync . s3://cloud9-webapp-staticwebbucket-xxxxxxxxxxxx --acl public-read


  3. 웹 브라우저로 정적 웹 페이지에 접속 (CloudFormation Stack에 배포된 Output의 WebsiteURL 값을 웹 브라우저 주소창에 입력)
    1. https://cloud9-webapp-staticwebbucket-xxxxxxxxxxxx.s3.amazonaws.com



Lab2. CodePipeline을 생성하여 CI/CD 프로세스 만들기

  1. CodeCommit 생성
  2. CoudBuild를 위한 nuildspec.yml 만들기
  3. CodePipeline 생성하기
  4. CodeDeploy 배포 (카나리 배포)
  5. 롤백하기 (Lambda - API Gateway)










코드 블럭
languageyml
themeRDark
titleTemplate_API_CORS
linenumberstrue
Globals:
...
  Api:
    # enable CORS; to make more specific, change the origin wildcard
    # to a particular domain name, e.g. "'www.example.com'"
    Cors:
      AllowMethods: "'*'"
      AllowHeaders: "'*'"
      AllowOrigin: "'*'"

...

이 글은 Tomasz Stachlewski가 작성한 Build Your Own Text-to-Speech Applications with Amazon Polly 의 블로그를 한국어 콘솔에 맞게 번역 및 편집 하였습니다.

- 김현수 솔루션스 아키텍트는 회사가 디지털 변환을 가속화 할 수 있게 해주는 서버리스(serverless) 아키텍처와 같은 혁신적인 기술과 인공지능(AI/ML)에 관심이 많습니다.


기타 자료






  1. 가나다

dynamoDb: