電話番号 電話番号 お問い合わせ

受付時間 平日10:00~18:00

TOP

資料ダウンロードはこちら >>

ロゴ

AWSやシステム・アプリ開発の最新情報|クロスパワーブログ

AWS AWS SAM Lambda

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

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

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

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

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

AWS SAM 概要

  • SAM…Serverless Application Modelの略

  • サーバレスアプリケーション構築用オープンソースフレームワーク

  • リソース定義はYAMLを使用

※詳細は下記をご参照ください
 参考文献:https://aws.amazon.com/jp/serverless/sam/

AWS SAMの利点

  • 設計したサーバレスアプリケーションをローカルのDockerコンテナ上で動作させ、テストすることができる。

  • API Gateway等の周辺リソースも同時に構築することができる。

  • SAMで構築したリソースをCloudFotmationを介してデプロイすることができる。

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

インストール

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

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

参考文献:https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-windows.html

試してみること

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-apiでAPIを実行します。

$ 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上にデプロイしてみます。