AWS SAMとCloudFormationで同じ構成のAPI Gateway + Lambdaを構築

API Gateway + Lambdaの同じ構成をSAMとCloudFormationの2通りで作成しました。

SAMのほうが簡単に作成できるのですが、細かい設定が中途半端に隠蔽されている感じが好きではないです。

CloudFormationのほうがテンプレートが長いのですが、細かい設定ができます。

SAM

aws-sam-cli のインストールが必要です。

$ pip install aws-sam-cli

$ sam --version
SAM CLI, version 1.6.2

SAMのテンプレートの雛形をコマンドで作成します。

$ sam init --runtime python3.8
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Project name [sam-app]: hello1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
        1 - Hello World Example
        2 - EventBridge Hello World
        3 - EventBridge App from scratch (100+ Event Schemas)
        4 - Step Functions Sample App (Stock Trader)
        5 - Elastic File System Sample App
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: hello1
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./hello1/README.md

でき上がったテンプレートのファイル構成は以下のとおりです。

$ cd hello1

$ tree
.
├── events
│   └── event.json
├── hello_world
│   ├── app.py
│   ├── __init__.py
│   └── requirements.txt
├── README.md
├── template.yaml
└── tests
    └── unit
        ├── __init__.py
        └── test_handler.py

4 directories, 8 files

template.yamlsamコマンドにより自動生成されますが、Resourcesは次の内容が書かれています。CloudFormationで書くよりも短いです。

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

これをデプロイするには以下のコマンドです。 --profile AWS_CREDENTIAL_PROFILE_NAME のところは ~/.aws/credentials に複数のプロファイルが設定されている場合に必要に応じて指定するものです。 --s3-bucket MYBUCKET_NAME はLambdaのソースコードを保存するS3バケット名を指定します。

$ sam package --profile AWS_CREDENTIAL_PROFILE_NAME --template-file template.yaml --output-template-file packaged.yaml --s3-bucket MYBUCKET_NAME --s3-prefix hello1

$ sam deploy --profile AWS_CREDENTIAL_PROFILE_NAME --region ap-northeast-1 --template-file packaged.yaml --stack-name hello1 --capabilities CAPABILITY_IAM

これでAPI Gatewaycurlコマンドでアクセスできます。

$ curl 'https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello'
{"message": "hello world"}

このSAMにより作成されるCloudFormationのStackのテンプレートは以下のコマンドで確認できます。JSON形式です。

$ aws --profile AWS_CREDENTIAL_PROFILE_NAME cloudformation get-template --stack-name hello1 | jq .TemplateBody -C | less -R

CloudFormation

上記SAMで作成したAWSのリソースとほぼ同じ構成をCloudFormationで作成します。

次のようなディレクトリ構成で2ファイルのみです。

$ tree
.
├── hello_world
│   └── app.py
└── template.yaml

1 directory, 2 files

template.yaml は以下の内容です。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  HelloWorldFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code: hello_world
      Handler: app.lambda_handler
      FunctionName: hello2
      Role: !GetAtt HelloWorldFunctionRole.Arn
      Runtime: python3.8
      Timeout: 3
  HelloWorldFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - "sts:AssumeRole"
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  ServerlessRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Body:
        info:
          version: 1.0
          title: !Ref AWS::StackName
        paths:
          "/hello":
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloWorldFunction.Arn}/invocations
        swagger: 2.0
  ServerlessRestApiProdStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref ServerlessRestApiDeployment
      RestApiId: !Ref ServerlessRestApi
      StageName: Prod
  ServerlessRestApiDeployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref ServerlessRestApi
      StageName: Stage
  HelloWorldFunctionHelloWorldPermissionProd:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName: !Ref HelloWorldFunction
      SourceArn: !Sub
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/hello
        - __Stage__: "*"
          __ApiId__: !Ref ServerlessRestApi

hello_world/app.py はSAMのケースと同じPythonソースです。次の内容です。

import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }

これをデプロイするには以下のコマンドです。 --profile AWS_CREDENTIAL_PROFILE_NAME のところは ~/.aws/credentials に複数のプロファイルが設定されている場合に必要に応じて指定するものです。 --s3-bucket MYBUCKET_NAME はLambdaのソースコードを保存するS3バケット名を指定します。

$ aws --profile AWS_CREDENTIAL_PROFILE_NAME cloudformation package --template-file template.yaml --s3-bucket MYBUCKET_NAME --s3-prefix hello2 --output-template-file packaged.yaml

$ aws --profile AWS_CREDENTIAL_PROFILE_NAME cloudformation deploy --template-file packaged.yaml --stack-name hello2 --capabilities CAPABILITY_IAM

これでAPI Gatewaycurlコマンドでアクセスできます。

$ curl 'https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello'
{"message": "hello world"}

このCloudFormationのStackのテンプレートは以下のコマンドで確認できます。といってもローカルに残っている package.yaml と同じです。YAML形式です。

$ aws --profile AWS_CREDENTIAL_PROFILE_NAME cloudformation get-template --stack-name hello2 | jq .TemplateBody -r | less