Amazon Web Services ブログ

AWS SAM Local(ベータ版) – サーバーレスアプリケーションをローカルに構築してテストする

今回、新ツール、SAM Local のベータ版がリリースされました。これにより、簡単にサーバーレスアプリケーションをローカルに構築してテストできるようになります。この記事では、SAM Local を使用し、クイックアプリケーションの構築、デバッグ、デプロイを実行します。これにより、エンドポイントに curl コマンドを使用してタブやスペースで投票できるようになります。AWS は サーバーレスアプリケーションモデル(SAM)を昨年導入しました。これにより、デベロッパーはサーバーレスアプリケーションをより簡単にデプロイできるようになっています。SAM の基本にまだなじみがない場合は、私の同僚である Orr が SAM の使用方法について書いた素晴らしい記事を参照してください。5 分ほどで読めます。基本的に、SAM は AWS CloudFormation 上に構築された強力なオープンソース仕様で、サーバーレスインフラストラクチャをコードとして簡単に保持できるようにすることを目的としています。また、可愛いマスコットもついてきます。

SAM Localでは、SAM の優れた点をすべてローカルマシンで利用できます。

  • 以下を使って、AWS Lambda 関数をローカルに開発してテストすることができます。 sam local Docker
  • Amazon Simple Storage Service(S3)Amazon DynamoDBAmazon KinesisAmazon Simple Notification Service(SNS)など、既知のイベントソースからの関数の呼び出しをシミュレートできます。
  • SAM テンプレートからローカルな Amazon API Gateway を開始し、ホットリロードで関数を素早く繰り返すことができます。
  • SAM テンプレートを素早く検証し、さらに、その検証を linters や IDE で統合できます。
  • Lambda 関数についてインタラクティブなデバッグサポートを利用できます。

SAM Local のインストール方法はいくつかありますが、NPM を使用するのが一番簡単です。 npm install -g aws-sam-local で素早く入手できます。また、ソースから直接インストールすれば確実に最新バージョンを入手できます。 go get github.com/awslabs/aws-sam-local (「sam」ではなく、「aws-sam-local」という名前でバイナリが作成されます)

それでは、簡単な SAM アプリケーションを書き込んでスペースやタブでの投票を行ってみましょう。非常にシンプルな、しかし強力なアーキテクチャ、API GatewayLambda 関数を使用し、結果を DynamoDB に保存します。最後は API に curl を実行できる必要があります。 curl https://SOMEURL/ -d '{"vote": "spaces"}' そして投票数を取得します。

以下に、シンプルな SAM template.yaml を示します。

AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  VotesTable:
    Type: "AWS::Serverless::SimpleTable"
  VoteSpacesTabs:
    Type: "AWS::Serverless::Function"
    Properties:
      Runtime: python3.6
      Handler: lambda_function.lambda_handler
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref VotesTable
      Events:
        Vote:
          Type: Api
          Properties:
            Path: /
            Method: post

Lambda 関数に公開する [dynamo_i] テーブルを作成します。環境変数の TABLE_NAME を使用します。

このテンプレートの有効性をテストするため、 sam validate を呼び出してタイプミスがないことを確認します。返ってくるのは、 「Valid」です。 Lambda 関数の作業に進みます。

import os
import os
import json
import boto3
votes_table = boto3.resource('dynamodb').Table(os.getenv('TABLE_NAME'))

def lambda_handler(event, context):
    print(event)
    if event['httpMethod'] == 'GET':
        resp = votes_table.scan()
        return {'body': json.dumps({item['id']: int(item['votes']) for item in resp['Items']})}
    elif event['httpMethod'] == 'POST':
        try:
            body = json.loads(event['body'])
        except:
            return {'statusCode': 400, 'body': 'malformed json input'}
        if 'vote' not in body:
            return {'statusCode': 400, 'body': 'missing vote in request body'}
        if body['vote'] not in ['spaces', 'tabs']:
            return {'statusCode': 400, 'body': 'vote value must be "spaces" or "tabs"'}

        resp = votes_table.update_item(
            Key={'id': body['vote']},
            UpdateExpression='ADD votes :incr',
            ExpressionAttributeValues={':incr': 1},
            ReturnValues='ALL_NEW'
        )
        return {'body': "{} now has {} votes".format(body['vote'], resp['Attributes']['votes'])}

これをローカルにテストします。通信のために実際に DynamoDB データベースを作成し、このデータベースに名前を設定する必要があります。環境変数の TABLE_NAME を使用します。これには env.json ファイルを使用します。コマンドラインでパスすることもできます。最初に以下を呼び出します。
$ echo '{"httpMethod": "POST", "body": "{\"vote\": \"spaces\"}"}' |\
TABLE_NAME="vote-spaces-tabs" sam local invoke "VoteSpacesTabs"

これで Lambda をテストします。スペースの投票数が返されるので、理論的にすべて問題なく機能していることがわかります。すべてをタイプするのは手間なので、 sam local generate-event api でサンプルイベントを作成してローカル呼び出しにパスします。API をローカルに実行するだけなので非常に簡単です。実際に sam local start-api でやってみましょう。これでローカルエンドポイントに curl を実行し、すべてをテストできるようになりました。
次のコマンドを実行します。 $ curl -d '{"vote": "tabs"}' http://127.0.0.1:3000/ 「tabs now has 12 votes」(タブの現在の投票数:12)と返ってきます。当然ながら、この関数は最初から完璧に書けたわけではありません。何度か編集と保存を繰り返しました。ホットリロードの利点の1つに、関数を変更しても新しい関数をテストするための追加作業が必要ないことが挙げられます。これにより、開発における反復作業が大幅に削減されます。

ネットワークを介して実際の DynamoDB データベースにアクセスすることを避けたい状況を考えてみましょう。どのような選択肢があるでしょうか?たとえば、DynamoDB Local をダウンロードして起動することができます。それには java -Djava.library.path= を使用します。/DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb 次に、Lambda 関数で AWS_SAM_LOCAL 環境変数を使用し、動作についての決定を行います。関数を少し変更してみましょう。

import os
import json
import boto3
if os.getenv("AWS_SAM_LOCAL"):
    votes_table = boto3.resource(
        'dynamodb',
        endpoint_url="http://docker.for.mac.localhost:8000/"
    ).Table("spaces-tabs-votes")
else:
    votes_table = boto3.resource('dynamodb').Table(os.getenv('TABLE_NAME'))

これでローカルエンドポイントがローカルデータベースに接続され、WiFi なしで簡単に作業できるようになります。

SAM Local はさらに、インタラクティブデバッグもサポートしています。Java や Node.js では、 -d フラグやポートをパスしてデバッガーをすぐに有効化できます。Python では、 import epdb; epdb.serve() などのライブラリを使用して接続できます。その後は、 sam local invoke -d 8080 "VoteSpacesTabs" を呼び出すことで、関数は実行を停止し、デバッガーの手順が完了するのを待機します。

これですべて機能するようになります。さっそくデプロイしましょう!

最初に sam package コマンドを呼び出します。これは aws cloudformation package のエイリアスです。このコマンドの結果を sam deploy に使用します。

$ sam package --template-file template.yaml --s3-bucket MYAWESOMEBUCKET --output-template-file package.yaml
Uploading to 144e47a4a08f8338faae894afe7563c3  90570 / 90570.0  (100.00%)
Successfully packaged artifacts and wrote output template to file package.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file package.yaml --stack-name 
$ sam deploy --template-file package.yaml --stack-name VoteForSpaces --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - VoteForSpaces

これで API に以下が出てきます。

これで本稼働ステージに移り、投票が多い場合にはレート制限を追加します。ローカルで作業してクラウドに簡単にデプロイすることもできます。1度目のデプロイで問題なく機能したときは本当にいい気分です。

これで、投票して結果をライブで見ることができます。http://spaces-or-tabs.s3-website-us-east-1.amazonaws.com/

SAM Local により、サーバーレスアプリケーションのテスト、デバッグ、デプロイが簡単になることを願っています。CONTRIBUTING.md ガイドを用意しています。是非プルリクエストを送信してください。構築が終わった際には是非ツイートでお知らせください。最新情報の記事やドキュメントはこちらで確認できます。

Randall