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

ServerlessConf
100件のシェア(ちょっぴり話題の記事)

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

<bucket> と書かれている部分は、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

<new-stack-name> には、適当なスタック名を付けてください。

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

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 でエラーが発生します。ご留意を。