[新ツール] AWS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する

[新ツール] AWS Serverless Application Model (AWS SAM) を使ってサーバーレスアプリケーションを構築する

Clock Icon2016.11.21 18:51

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWS Serverless Application Model (AWS SAM) とは

AWS Serverless Application Model (以降 AWS SAM) は、AWS が公式で提供しているサーバーレスアプリケーションを構築するためのフレームワーク (モデル) です。Lambda, API Gateway, DynamoDB のリソースをひとまとめに管理 (作成 / 更新 / 削除) することができます。

AWS SAM は、元々 Flourish という名前で知られていたものです。今年5月に開催された Serverless Conf にて Tim Wagner から発表されて以来、期待が集まっていました。re:Invent 2016 を目前に、ついにリリースされました。

本記事では AWS SAM がどういったものなのか ざっくり調べてまとめてみました。

AWS SAM は AWS CloudFormation の拡張である

AWS SAM は言うならば、 AWS CloudFormation (以降 CFn) のサーバーレス拡張 です。AWS SAM の各リソースは CFn のこれまでのリソース定義と似たフォーマットで定義されており、このフォーマットで記述されたテンプレートを CFn に渡すことで、スタックとして AWS 上に展開することができます。具体的には以下のような感じです。YAML または JSON で記述することができます。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
FunctionName:
  Type: AWS::Serverless::Function
  Properties:
    Handler: index.handler
    Runtime: nodejs4.3

Transform: AWS::Serverless-2016-10-31 というところがミソで、これを書くことで CFn はテンプレートを AWS SAM フォーマットのテンプレートとして読むようです。

AWS SAM でサーバーレスアプリケーションを構築してみよう

本記事では、GitHub で公開されているサンプルの api_backend を参考に構築してみたいと思います。こちらは、DynamoDB テーブルの CRUD を API Gateway + Lambda で構築するといったものです。

作業用ディレクトリの作成

まずは AWS SAM で取り扱うファイルをまとめるため、ローカルの適当な場所にディレクトリを作成しておきましょう。

$ mkdir api_backend
$ cd api_backend

以降、このディレクトリ配下で作業します。

Lambda ファンクション

まずは DynamoDB テーブルの CRUD を提供する Lambda ファンクションを用意します *1dynamodb-doc を使って DynamoDB の API を呼び出しています。また、先日 AWS SAM と同時にリリースされた Lambda の環境変数を使って、DynamoDB のテーブル名を取得するようにしています。

'use strict';
console.log('Loading function');

let doc = require('dynamodb-doc');
let dynamo = new doc.DynamoDB();

const tableName = process.env.TABLE_NAME;

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

exports.get = (event, context, callback) => {
    var params = {
        "TableName": tableName,
        "Key": {
            "id": event.pathParameters.resourceId
        }
    };
    dynamo.getItem(params, (err, data) => {
        var response;
        if (err)
            response = createResponse(500, err);
        else
            response = createResponse(200, data.Item.doc);
        callback(null, response);
    });
};

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

    var params = {
        "TableName": tableName,
        "Item": item
    };
    dynamo.putItem(params, (err, data) => {
        var response;
        if (err)
            response = createResponse(500, err);
        else
            response = createResponse(200, null);
        callback(null, response);
    });
};

exports.delete = (event, context, callback) => {
    var params = {
        "TableName": tableName,
        "Key": {
            "id": event.pathParameters.resourceId
        }
    };
    dynamo.deleteItem(params, (err, data) => {
        var response;
        if (err)
            response = createResponse(500, err);
        else
            response = createResponse(200, null);
        callback(null, response);
    });
};

こちらの Lambda ファンクションを index.js という名前で保存し、app.zip という名前の Zip ファイルに圧縮しておきます。なお、これらのファイル名は何でも構いません。

$ zip app.zip index.js -x "*.DS_Store"

SAM テンプレートの作成

次に、SAM 形式のテンプレートを作成しましょう。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs4.3
      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: nodejs4.3
      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: nodejs4.3
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        DeleteResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: delete

  Table:
    Type: AWS::Serverless::SimpleTable

`` と書かれている部分は、Lambda ファンクションの Zip ファイルのアップロード先となる S3 バケットの名前に変更してください。S3 バケットを作成するには、次のようなコマンドを叩きます。

$ aws s3 mb s3://<bucket-name> --region <region>

パッケージ & デプロイ

次に、パッケージとデプロイを行います。ここからは AWS CLI を使うことが必須となるので、念のため最新バージョンにしておきましょう。

$ aws --version
aws-cli/1.11.19 Python/2.7.11 Darwin/16.3.0 botocore/1.4.76

パッケージ

パッケージは、Lambda ファンクションの Zip ファイルを S3 バケットにアップロードすることを指します。AWS CLI の CloudFormation の package コマンド を叩くだけで、コマンドを実行したディレクトリにある Zip ファイルをアップロードすることができます。

$ aws cloudformation package \
   --template-file template.yaml \
   --output-template-file serverless-output.yaml \
   --s3-bucket <bucket-name>
   
Uploading to d922f3e6e1268d5923d0746c1996de62  2482 / 2482.0  (100.00%)
Successfully packaged artifacts and wrote output template to file serverless-output.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/User/work/api_backend/serverless-output.yaml --stack-name <YOUR STACK NAME>

S3 に Zip ファイルがアップロードされ、そのファイルの Uri が --template-file で指定したテンプレートに書き足され、--output-template-file で指定したファイル名で出力されます。抜粋すると、以下のようになっています。

AWSTemplateFormatVersion: '2010-09-09'
Description: Simple CRUD webservice. State is stored in a SimpleTable (DynamoDB) resource.
Resources:
  DeleteFunction:
    Properties:
      CodeUri: s3://<bucket-name>/d922f3e6e1268d5923d0746c1996de62
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Events:
        DeleteResource:
          Properties:
            Method: delete
            Path: /resource/{resourceId}
          Type: Api
      Handler: index.delete
      Policies: AmazonDynamoDBFullAccess
      Runtime: nodejs4.3
    Type: AWS::Serverless::Function

デプロイ

パッケージで書き出したテンプレートファイルを元に、AWS CLI の CloudFormation の deploy コマンド を叩きます。これで、テンプレートに記述されている通りのリソースが構築されます。

$ aws cloudformation deploy \
   --template-file serverless-output.yaml \
   --stack-name <new-stack-name> \
   --capabilities CAPABILITY_IAM
   
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - api-backend

`` には、適当なスタック名を付けてください。

処理が完了すると、各リソースが構築されていることが確認できます。

CFn Stack が完了しており、

aws-sam-01

DynamoDB がいい感じで構築されており、

aws-sam-02

Lambda がいい感じで構築されており、

aws-sam-03

API Gateway がいい感じで構築されています。

aws-sam-04

試す

まずは PUT から。DynamoDB を考慮し、Upsert を行うような API になっています。

$ curl -H "Accept: application/json" -H "Content-type: application/json" \
     -X PUT https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/resource/hoge \
     -d '{"name":"hoge", "description":"I am hoge."}'

上記で作成したものを GET します。

$ curl -H "Content-type: application/json" \
    -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/resource/hoge
{"name":"hoge", "description":"I am hoge."}

最後に DELETE で消してみます。

$ curl -H "Content-type: application/json" \
    -X DELETE https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/resource/hoge

いい感じに動作しています! DynamoDB のマネジメントコンソールで、アイテムの変化を確認しながら試すと良いでしょう。

まとめ

AWS SAM についてざっくりと触れてみました。サーバーレスアプリケーションは適用可能範囲が多岐に渡るので、用途によってテンプレートの構成は変わってきます。今後は AWS SAM を使ってサーバーレスアプリケーションを構築する方法を、用途別にご紹介できればと思います。

参考

脚注

  1. 筆者が試した時点では、このソースコードの20行目の「"id"」の部分が GitHub のサンプルコードでは「id」となっており GET でエラーが発生します。ご留意を。

この記事をシェアする

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.