AWS SAM으로 Serverless Rest API 만들기

AWS SAM으로 Serverless Rest API 만들기에 대해 알아봅니다.

작성자

홍세민

홍세민

MarcusHong

AWS SAM으로 Serverless Rest API 만들기

유지보수 없는 Serverless 아키텍처는 매력적인 분야다. AWS에서는 API Gateway - Lambda를 사용한다면 Rest API 를 만들 수 있다. 하지만 이 모든 작업(Api 설정, Lambda 함수 관리, Role 관리)을 하는 것이 간단하지만은 않다.

AWS Serverless Application Model (AWS SAM)

AWS에서는 Cloudformation을 통해 문서로 서버 아키텍처를 생성, 수정, 삭제를 할 수 있게 한다. (참고)

Cloudformation의 컨셉은 AWS Console에서 할 수 있는 모든 기능을 문서로 만드는 것이기 때문에 많은 기능을 사용할 수 있긴 하지만, 해야할 것들이 많다는 뜻이기도 하다.

SAM을 사용하게 되면 Rest API를 만드는데 필요한 기능들, Api gateway, lambda와 리소스를 만들고 연결하는 과정이 한결 단순해 지는데, 단순화되면서 몇가지 제한 되는 기능이 있다.

만들기전 중요한 사항은 다음과 같다.

  • Nested Stack이 불가능하다. 한 파일로만 만들어야 된다는 의미다.
  • Lambda 버전 별 저장이 되지 않는다.
  • SAM이 제공하는 리소스가 아니더라도 Cloudformation의 문법을 사용하면 리소스 추가가 가능하다.
  • API Gateway는 Swagger2.0 문법을 지원한다. Swagger로 좀 더 간소화할 수 있다는 의미다.
  • aws-cli를 사용해서 버킷이름만 지정해주면 lambda 함수를 직접 s3로 업로드할 필요가 없다. aws-cli가 폴더를 압축해서 업로드 후 local경로를 s3경로로 치환한 파일을 생성해준다.

Sample

node.js로 DynamoDB CRUD하는 Lambda 함수 만들기
'use strict';


const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();

const tableName = process.env.TABLE_NAME;

const createResponse = (statusCode, body) => {
    return {
        statusCode: statusCode,
        body: body
    }
};

exports.get = (event, context, callback) => {
    let params = {
        TableName: tableName,
        Key: {
            id: event.pathParameters.resourceId
        }
    };

    let dbGet = (params) => { return dynamo.get(params).promise() };
    dbGet(params).then( (data) => {
        if (!data.Item) {
            callback(null, createResponse(404, "ITEM NOT FOUND"));
            return;
        }
        console.log(`RETRIEVED ITEM SUCCESSFULLY WITH doc = ${data.Item.doc}`);
        callback(null, createResponse(200, data.Item.doc));
    }).catch( (err) => { 
        console.log(`GET ITEM FAILED FOR doc = ${params.Key.id}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err));
    });
};

exports.put = (event, context, callback) => {
    let item = {
        id: event.pathParameters.resourceId,
        doc: event.body
    };

    let params = {
        TableName: tableName,
        Item: item
    };

    let dbPut = (params) => { return dynamo.put(params).promise() };
    dbPut(params).then( (data) => {
        console.log(`PUT ITEM SUCCEEDED WITH doc = ${item.doc}`);
        callback(null, createResponse(200, null));
    }).catch( (err) => { 
        console.log(`PUT ITEM FAILED FOR doc = ${item.doc}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err)); 
    });
};

exports.delete = (event, context, callback) => {

    let params = {
        TableName: tableName,
        Key: {
            id: event.pathParameters.resourceId
        },
        ReturnValues: 'ALL_OLD'
    };

    let dbDelete = (params) => { return dynamo.delete(params).promise() };

    dbDelete(params).then( (data) => {
        if (!data.Attributes) {
            callback(null, createResponse(404, "ITEM NOT FOUND FOR DELETION"));
            return;
        }
        console.log(`DELETED ITEM SUCCESSFULLY WITH id = ${event.pathParameters.resourceId}`);
        callback(null, createResponse(200, null));
    }).catch( (err) => { 
        console.log(`DELETE ITEM FAILED FOR id = ${event.pathParameters.resourceId}, WITH ERROR: ${err}`);
        callback(null, createResponse(500, err));
    });
};

아래는 SAM에서 환경변수를 선언한 것을 가져오는 부분이다.

SAM에서 리소스를 선언할 때 변수로 같은 SAM파일 안에서 생성한 리소스의 이름, Arn등을 가져올 수 있다.

const tableName = process.env.TABLE_NAME;

SAM 파일로 리소스 만들기

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple CRUD
Resources:
  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs6.10
      CodeUri: ./src
      Policies: AmazonDynamoDBReadOnlyAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: get

  PutFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.put
      Runtime: nodejs6.10
      CodeUri: ./src
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        PutResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

  DeleteFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.delete
      Runtime: nodejs6.10
      CodeUri: ./src
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        DeleteResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: delete

  Table:
    Type: AWS::Serverless::SimpleTable

Cloudformation과 다른 점은 아래의 한 줄 밖에 없다.

Transform: AWS::Serverless-2016-10-31

AWS에 배포

앞서 언급한 바와 같이 aws-cli를 사용한다면 배포를 조금 더 쉽게 할 수 있다.

먼저 local에 있는 Lambda 폴더를 aws cloudformation package를 써서 업로드한다.

aws cloudformation package \
    --template-file /path_to_template/template.yaml \
    --s3-bucket bucket-name \
    --output-template-file packaged-template.yaml

이후 aws cloudformation deploy로 AWS 리소스를 생성한다.

aws cloudformation deploy \
    --template-file /path_to_template/packaged-template.yaml \
    --stack-name my-new-stack \
    --capabilities CAPABILITY_IAM

deploy가 끝나면 AWS cloudformation 콘솔에서 생성한 Stack을 확인할 수 있다.

코드 수정이나 리소스 수정은 만든 문서파일을 수정한 후 package, deploy를 하면 수정된다.

정리

Cloudformation 문법을 알아야 한다는 러닝커브가 있지만 리소스 관리, 수정, 조회 측면에서 SAM 문서를 활용하는 것은 분명히 이점이 있는 부분이다. Iot, s3, sns, kinesis, cloudwatch, alaxa등을 지원하니 추가 정보는 AWS Github을 참조.

Tags : aws 

comments powered by Disqus