AWS SAMでLambda + API Gateway環境のローカルテスト


こんにちは。
入社一年目のT.A.です。

初の実務案件でLambda + API GatewayAPIに触れる機会がありました。

作成したLambda関数の動作確認は、実際にデプロイして確認する必要があり、
即時確認できないことに煩わしさを感じていました。

そこで見つけたのが、AWS SAMというフレームワークです。
Lambdaの動作確認をローカルで完結させることができます。
今回は「AWS SAM」のローカルテストを試していきたいと思います。

AWS SAM 概要


※詳細は下記をご参照ください
 参考文献:AWS サーバーレスアプリケーションモデル
aws.amazon.com

AWS SAMの利点

  • 設計したサーバレスアプリケーションをローカルのDockerコンテナ上で動作させ、テストすることができる。
  • API Gateway等の周辺リソースも同時に構築することができる。
  • SAMで構築したリソースをCloudFotmationを介してデプロイすることができる。

  • DynamoDB Local等を併用することで、それらのリソース含めた動作確認ができる。

インストール


SAM LocalはDockerコンテナ上で動作するため、
Dockerも併せてインストールする必要があります。

SAM CLIのインストール手順は公式の記述がありますので、
ここでは割愛いたします。

参考文献:Installing the AWS SAM CLI

docs.aws.amazon.com

試してみること

API Gateway + Lambda のREST API

今回はお試しで、以下二種の単純なAPIを作成します。

  • クエリストリングを取得し、レスポンスの文字列に組み込んで返すAPI
  • パスパラメータを取得し、レスポンスの文字列に組み込んで返すAPI

今回は、Lambda関数にGo言語を使用します。
 

実践

構築

アプリケーション作成

プロジェクト名は"hellosam"にします。

$ sam init -r go1.x -n hellosam
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1 # お試しなのでテンプレートを使用

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

AWS quick start application templates: 1 - Hello World Example 2 - Step Functions Sample App (Stock Trader) Template selection: 1


Generating application:

Name: hellosam Runtime: go1.x Dependency Manager: mod Application Template: hello-world Output Directory: .

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

テンプレートのファイル確認


sam initで以下のファイル群が作成されました。

  • hellosam/

    • Makefile
    • README.md
    • hello-world/
      • main.go
      • main_test.go
      • go.mod
    • template.yaml

テンプレートの動作確認


テンプレートをそのままビルド・実行します。
その後、APIにGETリクエストを投げてみます。

ビルド・実行


sam buildでビルドします。

$ sam build
Building function 'HelloWorldFunction'
Running GoModulesBuilder:Build

Build Succeeded

Built Artifacts : .aws-sam\build Built Template : .aws-sam\build\template.yaml

Commands you can use next

[] Invoke Function: sam local invoke [] Deploy: sam deploy --guided

sam local start-apiでローカルでAPIを実行します。

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-10-05 11:28:00  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)


※Dockerを起動せずにsam local start-apiを行うと下記のエラーが出ます。
 先にDockerを起動しておきましょう。

$ sam local start-api
Error: Running AWS SAM projects locally requires Docker. Have you got it installed and running?


GETリクエスト送信
http://127.0.0.1:3000/hello


上記アドレスにGETリクエストを投げてみます。

Hello,
... (グローバルIPアドレス)

上記のレスポンスが返ってきました。

ソースコード確認


ここでhello-world/main.goを確認してみます。

package main

import ( "errors" "fmt" "io/ioutil" "net/http"

"github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" )

var ( // DefaultHTTPGetAddress Default Address DefaultHTTPGetAddress = "https://checkip.amazonaws.com"

// ErrNoIP No IP found in response ErrNoIP = errors.New("No IP in HTTP response")

// ErrNon200Response non 200 status code in response ErrNon200Response = errors.New("Non 200 Response found") )

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { resp, err := http.Get(DefaultHTTPGetAddress) if err != nil { return events.APIGatewayProxyResponse{}, err }

if resp.StatusCode != 200 { return events.APIGatewayProxyResponse{}, ErrNon200Response }

ip, err := ioutil.ReadAll(resp.Body) if err != nil { return events.APIGatewayProxyResponse{}, err }

if len(ip) == 0 { return events.APIGatewayProxyResponse{}, ErrNoIP }

return events.APIGatewayProxyResponse{ Body: fmt.Sprintf("Hello, %v", string(ip)), StatusCode: 200, }, nil }

func main() { lambda.Start(handler) }


処理内容をざっくり要約すると以下のような流れです。

  1. https://checkip.amazonaws.comにGETリクエスト。
    これによってローカルコンピュータのグローバルIPアドレスを取得。
  2. 取得したIPアドレスの検証。
  3. 取得したIPアドレスをレスポンスボディに組み込む。
  4. レスポンスを返す。


レスポンスを確認し、正常に動作していることがわかりました。

 参考文献:https://qiita.com/G-awa/items/b28bd16c95ff4ecfe441

API作成


APIの作成と動作確認を行います。

Lambda関数作成


ファイル構成を以下のように変更します。

  • Makefile
  • README.md
  • hello-world/

    • main.go
    • main_test.go
    • go.mod

  • hello-str/ (新規作成)

    • main.go (新規作成)
    • go.mod (新規作成)

  • hello-req/ (新規作成)

    • main.go (新規作成)
    • go.mod (新規作成)

  • template.yaml



以下をそれぞれ記述します。

  • hello-str/main.go...クエリストリングを取得、レスポンスボディに含めて返す関数

  • hello-req/main.go...パスパラメータを取得、レスポンスボディに含めて返す関数


hello-str/main.go

package main

import ( "fmt"

"github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

return events.APIGatewayProxyResponse{ Body: fmt.Sprintf("Hello, %v", request.QueryStringParameters["str"]), StatusCode: 200, }, nil } func main() { lambda.Start(handler) }


hello-req/main.go

package main

import ( "fmt"

"github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" )

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {

return events.APIGatewayProxyResponse{ Body: fmt.Sprintf("Hello, %v", request.PathParameters["req"]), StatusCode: 200, }, nil }

func main() { lambda.Start(handler) }



go.modはhello-world/直下のものをコピーします。
その上でmoduleの項目をそれぞれのディレクトリ名に変更します。


hello-str/go.mod

require github.com/aws/aws-lambda-go v1.13.3

module hello-str // 変更箇所

go 1.15


hello-req/go.mod

require github.com/aws/aws-lambda-go v1.13.3

module hello-req // 変更箇所

go 1.15


※main_test.goは単体テストコードです
 今回は省略します。



template.yamlの編集


template.yamlを以下のように編集します。
ここでAPI Gatewayのパス等を設定します。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hellosam

Sample SAM Template for hellosam

application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 5

Resources:

# 変更なし HelloWorldFunction: # 省略

# 新規追加 HelloStrFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-str/ Handler: hello-str Runtime: go1.x Tracing: Active Events: CatchAll: Type: Api Properties: Path: /hello/str Method: GET RequestParameters: "method.request.path.str": Required: true Caching: false Environment: Variables: PARAM1: VALUE

# 新規追加 HelloReqFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-req/ Handler: hello-req Runtime: go1.x Tracing: Active Events: CatchAll: Type: Api Properties: Path: /hello/req/{req} Method: GET RequestParameters: "method.request.path.req" : "integration.request.path.req" Environment: Variables: PARAM1: VALUE

#以後省略

動作確認


テンプレートの動作確認と同様の手順でビルド・実行・動作確認を行います。

ビルド


sam buildでビルドします。

$ sam build
Building function 'HelloWorldFunction'
Running GoModulesBuilder:Build
Building function 'HelloStrFunction'
Running GoModulesBuilder:Build
Building function 'HelloReqFunction'
Running GoModulesBuilder:Build

Build Succeeded

Built Artifacts : .aws-sam\build Built Template : .aws-sam\build\template.yaml

Commands you can use next

[] Invoke Function: sam local invoke [] Deploy: sam deploy --guided



実行


sam local start-apiAPIを実行します。

$ sam local start-api
Mounting HelloReqFunction at http://127.0.0.1:3000/hello/req/{req} [GET]
Mounting HelloStrFunction at http://127.0.0.1:3000/hello/str [GET]
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-10-06 10:51:27  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

動作確認


GETリクエストを作成したそれぞれのAPIに送信します。
今回は文字列を返すだけですので、クエリストリングおよびパスパラメータは適当な文字列で構いません。

http://127.0.0.1:3000/hello/str?str=[任意の文字列]

http://127.0.0.1:3000/hello/req/[任意の文字列]



それぞれ以下のようなレスポンスになれば成功です。

Hello, [入力した任意の文字列]

終わりに


SAMを用いてLambda + API Gatewayの動作確認をローカルで行ってみました。


テンプレート記述のため、SAMを用いる際は最低限CloudFormationの知識およびテンプレートの書き方を学ぶ必要があります。
しかし、ローカルで動作確認しながら構築できるのでAWS上の環境を壊す心配が減ります。これは大きなメリットです。


また、単体テストであればテストコードで済みますが、
デプロイ前にAPI Gateway等周辺リソースを含めた動作確認ができる点で役に立ちます。


次回はSAMでモデリングしたサーバレスアプリケーションを実際にAWS上にデプロイしてみます。