MAGAZINE

ルーターマガジン

AWS

SAMを使ったAWSサーバーレスサービスのローカル実行方法

2022.12.11
Pocket

お疲れさまです。福島です。今回はAWS SAM(Serverless Application Model)を利用して、AWSのサービス(lambdaやAPI Gatewayなど)をローカルで実行する方法を紹介します。

AWS SAM(Serverless Application Model)とは

SAMとはAWSが提供するIaC(Infrastructure as Code)で、インフラをコードとして記述・管理できるサービスです。

他のIaCサービスとして有名なのは、TerraformやServerless Frameworkが挙げられます。

SAMの強み

SAMが他のIaCツールより優れている点は以下の2つが考えられます。

1.AWSが提供している

AWS公式が開発を行っているためコミュニティが廃れる心配や陳腐化する可能性が低いです。

2.ローカルで実行できる

Terraformなどの他のIaCを利用しても個別にテストをすることは可能ですが、結合テストを行うことはできません。しかしSAMは、Dockerを利用して本番と同等の環境がローカルに構築されるため、デプロイをしなくても全体の動作確認が行えます。ローカルで十分にテストが行えるので、デプロイの回数を最小限に抑えることができ、ダウンタイムの減少、精神的安心感などのメリットがあります。

使ってみる

SAMは内部でaws cliとDockerを使用しているため、前提としてこれらがインストール済みである必要があります。

aws cliのインストールはこちら

Dockerのインストールはこちら

iam ユーザーの設定を行っておく必要があります。

https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html

samのインストール

MacOSの例を示します

brew tap aws/tap
brew install aws-sam-cli

インストール確認

$ sam --version
SAM CLI, version 1.61.0

ディレクトリ作成

$ mkdir sam_development && cd sam_development

sam initをすると、公式が用意したテンプレートを利用することができるのですが、今回は理解促進のため、自分で実装します。

また、ローカルにdynamodbを構築するためdocker-compose.ymlも作成します。

$ touch template.yaml
$ mkdir src && touch src/app.py
$ touch docker-compose.yml

src/app.rbにビジネスロジック、template.yamlにインフラの構成を記述します。

作るもの

本記事でどんなものを作るのか確認します。

登場するサービスは API Gateway, Lambda, DynamoDBの3つです。ユーザーはAPI Gatewayに対してHTTPリクエストを送信することでDynamoDBを操作します。

名簿のような役割を担うテーブルを想定し、テーブル名はmembers, パーティションキーはnameとします。

エンドポイントに対してPOSTリクエストをし、テーブルに新たにデータを登録することを目標とします。

app.py

import json
import boto3

client = boto3.client('dynamodb', endpoint_url = "http://dynamodb-local:8000")

def post_handler(event, context):
    # リクエストbody取得
    body = json.loads(event["body"])
    name = body["name"]
    # db登録
    result = client.put_item(TableName="members", Item={"name": {"S": name}})
    response = {
        "statusCode": 200,
        "body": json.dumps(result)
    }

    return response

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  dva-01-001
  Sample SAM Template for dva-01-001

Globals:
  Function:
    Timeout: 10

Resources:
  PostMemberFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: app.post_handler
      Role: !GetAtt PostMemberFunctionRole.Arn
      Runtime: python3.9
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /members
            Method: post
  PostMemberFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: PostMemberFunctionPolicies
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:List*
                  - dynamodb:DescribeReservedCapacity*
                  - dynamodb:DescribeLimits
                  - dynamodb:DescribeTimeToLive
                Resource: "*"
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                Resource: !GetAtt Table.Arn
            
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: name
          AttributeType: S
      BillingMode: PAY_PER_REQUEST
      KeySchema:
        - AttributeName: name
          KeyType: HASH
      TableName: members

docker-compose.yml

version: '3.8'
services:
  dynamodb-local:
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
    image: "amazon/dynamodb-local:latest"
    container_name: dynamodb-local
    ports:
      - "8000:8000"
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal
    networks:
      - lambda-local
networks:
  lambda-local:
    external: true

これら3つのファイルが作成できたら、ビルド、ネットワーク作成、dynamodbの立ち上げを行います。

$ sam build
$ docker network create lambda-local
$ docker-compose up -d

dynamodbが正常に起動しているのか確かめるためにテーブルの作成をaws dnamodbコマンドから行います。

$ aws dynamodb create-table --table-name members \
  --attribute-definitions AttributeName=name,AttributeType=S \
  --key-schema AttributeName=name,KeyType=HASH  \
  --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
  --endpoint-url "http://localhost:8000"

テーブルの作成ができています。

$ aws dynamodb list-tables --endpoint-url 'http://localhost:8000'
{
    "TableNames": [
        "members"
    ]
}

次にAPI Gatewayをローカルで起動させます。ローカルのdynamodbを使用するために、先程作成したネットワークを指定しています。

$ sam local start-api --docker-network lambda-local

POSTリクエストを送信して登録を試してみます。

$ curl "http://localhost:3000/members" -X POST -d '{"name": "fukushima"}'

登録できているか確認します。

$ aws dynamodb scan --table-name members --endpoint-url "http://localhost:8000"
{
    "Items": [
        {
            "name": {
                "S": "fukushima"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

期待通りデータが登録できました。

最後に

AWS公式が提供しているSAMを使い、ローカル上でクラウドサービスを実行する方法をご紹介しました。

SAMはデプロイまでカバーしており

sam deploy --guided

とすることで、実際にローカルで作成したサービスをAWSにデプロイすることができます。

コードベースでインフラを管理することで、属人化しにくくできます。

今回紹介したlambdaやDynamoDB以外にもS3やEC2なども同様にSAMで記述できます。積極的に取り入れていきましょう!

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから