BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

AWS SAM CLIを使ったLambdaのローカル実行と簡単デプロイ

こんにちは。BASE BANK株式会社 Dev Division にて、 Software Developer をしている永野(@glassmonekey)です。

弊社ではAWS Lambdaを活用する機会が増えまして、 最近メジャーアップデートのあった「AWS SAM CLI」を使ってリリースフローの改善にチャレンジしてみました。

そこで、samコマンドで作成したサンプルプロジェクトをローカルで実行しデプロイする方法を紹介します。それに加えて、現状BASE BANKチームで行っている代表的な運用設定をご紹介します。

今回記事作成に際して、サンプルプログラムを用意しているのでもしよければ手元でご確認ください。

なお、今回LambdaにはGoを採用しました。検証に使用した環境は以下の通りです。

macOS:  10.15.x (Catalina)
SAM CLI:  version 1.2.0
AWS CLI: aws-cli/2.0.50 Python/3.7.4 Darwin/19.6.0 exe/x86_64
Go:  go1.15.2 darwin/amd64

SAM CLIとは

SAMとはServerless Application Modelの略称で、

AWS Lambdaのデプロイ管理に使われるツールで、ローカル上のデバッグビルドデプロイをサポートします。

公式のアナウンスによると7月にバージョン1 がリリースされました。

AWS SAMのキャッチ画像 画像はAWS SAM Local(ベータ版) – サーバーレスアプリケーションをローカルに構築してテストするより引用しました。

またこの愛らしいリスのキャラクターは公式の説明によると

"SAM the Squirrel (リスの SAM)" について SAM the Squirrel は、サーバーレスアプリケーションで使用されるリソースを定義するモデルで、AWS Serverless Application Model (AWS SAM) から名付けられました。SAM は、AWS ユーザーがサーバーレスアプリケーションを効果的かつ簡単に構築できるよう、ツリー (木) の中で居心地のよい生活を後にしました。

とのことで、なんだか愛着が湧いてきますね。

環境構築

samコマンドを実行するために必要な設定をしておきます。

1. AWS SAM CLIのインストール

最初にAWS SAM を動かすためのCLIをインストールします。 macOSの場合は公式に紹介されている通り、Homebrewを使って簡単にインストールすることができます。

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

または、公式で提供されているpipパッケージを利用する方法もあります。 CI環境などでbrewを入れたくない場合はこちらがおすすめです。

$ pip install aws-sam-cli

sam --versionでバージョンが返ってきたらインストール成功です。

$ sam --version

 SAM CLI, version 1.2.0

2. AWS CLIのインストール

直接SAMの実行には関係ありませんが、動作確認などで必要になってくるのでこちらも公式の方法に従って入れておきます。

$ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
$ sudo installer -pkg AWSCLIV2.pkg -target /

こちらもaws --versionで結果が返ってきたらインストール完了です。

$ aws --version
aws-cli/2.0.50 Python/3.7.4 Darwin/19.6.0 exe/x86_64

3. sam実行ユーザーの作成

AWSコンソールなどからsamコマンドを実行するユーザーを追加しておきます。

必要なポリシーなどは後ほど解説するので、この段階ではAdministratorAccessを設定しておきます。

また、cliからも触れるように設定しておきます。

aws configure

4. dockerをインストールしておく

ローカル実行を行う場合のみ、docker daemonが動いてないといけないのでインストールをしておいてください。 docker for mac はこちらからインストールできます。

SAMを使った開発入門

まず最初に公式が提供してくれるHello, Worldなapiをsamを使ってデプロイすることをためしてみます。

1. プロジェクトを作成する

sam initを行うとsam用のプロジェクトの初期化を行うことができます。今回はサンプルプログラムを流用するので対話型の設定を行いますが、既にディレクトリ構成などが完成している場合は不要なフローです。

sam init 

今回は入門用のAPIをぱぱっと作るので1を選びます。

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location

Goを使うので4を選びます。PHPも選択肢に来ないかなー

Which runtime would you like to use?
        1 - nodejs12.x
        2 - python3.8
        3 - ruby2.7
        4 - go1.x
        5 - java11
        6 - dotnetcore3.1
        7 - nodejs10.x
        8 - python3.7
        9 - python3.6
        10 - python2.7
        11 - ruby2.5
        12 - java8.al2
        13 - java8

プロジェクト名はデフォルトのsam-appのままで行います。

Project name [sam-app]:

今回は単純なapiを作成するので1を選びます。

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

すると プロジェクトテンプレートもデフォルトのhttps://github.com/aws/aws-sam-cli-app-templatesを使用した形でプロジェクトが初期化されています。

├── Makefile                    <-- Make to automate build
├── README.md                   <-- This instructions file
├── hello-world                 <-- Source code for a lambda function
│   ├── main.go                 <-- Lambda function code
│   └── main_test.go            <-- Unit tests
└── template.yaml

AWS SAMとしてLambdaの構成管理しているのはtemplate.ymlになります。 これはCloud Formationのラッパーとなっており、基本的な文法はCloud Formationに準拠します。

また、内容に関しての詳細はSAMのドキュメントCloud Formationのテンプレートリファレンス を一読することをおすすめします。

2. ローカルで実行してみる。

2.1 ビルド実行

実行の前にビルドをしてみます。

$ sam build

実行後に下記のようにbundle含めてデプロイ用のファイル群が作成される。

├── .aws-sam
│   └── build
│       ├── HelloWorldFunction
│       │   └── hello-world(バイナリ)
│       └── template.yaml

生成内容の設定に関してはtemplate.ymlに記述されている

      CodeUri: hello-world/
      Handler: hello-world

と対応しているので、生成したいhandler名やソースディレクトリを変更したい場合はここを変更します。

2.2 ローカル実行

次にローカルで実行を行うsam local invokeを実行します。

するとAWSリソースを介さずにローカルで完結して結果が返ってくるはずです。

$ sam local invoke

{"statusCode":200,"headers":null,"multiValueHeaders":null,"body":"Hello, $実行環境IPアドレス\n"}

また -eオプションをつけることでイベント情報のjsonを渡すことも可能です。

特にAPIコール以外(SQS経由など)で呼び出されるLambdaの場合だとデバッグ用のイベントを都度作成するのも手間だったりするので、イベント情報も極力Git管理しておくことをおすすめします。

副次的効果として、チーム内にどのようなイベントのやりとりが発生するかの共有にもつながります。

$ sam local invoke -e path/to/event.json`

なお、雛形はsam local generate-event $リソース名 $API名で作成することができます。

例えば、SQSのreceiveMessageのイベントのJsonは下記のような形式となります。

 $ sam local generate-event sqs receive-message 

{
  "Records": [
    {
      "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
      "receiptHandle": "MessageReceiptHandle",
      "body": "Hello from SQS!",
      "attributes": {
        "ApproximateReceiveCount": "1",
        "SentTimestamp": "1523232000000",
        "SenderId": "123456789012",
        "ApproximateFirstReceiveTimestamp": "1523232000001"
      },
      "messageAttributes": {},
      "md5OfBody": "7b270e59b47ff90a553787216d55d91d",
      "eventSource": "aws:sqs",
      "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue",
      "awsRegion": "us-east-1"
    }
  ]
}

3. デプロイしてみる。

3.1 デプロイファイルの作成

初回デプロイの場合は--guidedオプションを使用して対話形式でデプロイ設定ファイルsamconfig.tomlを作ります。

$ sam deploy --guided

 Stack Name [sam-app]: 
        AWS Region [us-east-1]: ap-northeast-1
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: n
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: Y
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to samconfig.toml [Y/n]: y

各項目に関してですが

  • Stack Name … 内部的に使用するcloud formationのstack名。samを実行するユーザーはこのstack名に関しての権限が不足しているとデプロイに失敗するので、必要なので注意が必要です。最低限以下の権限が必要なことは確認しています。
          "cloudformation:DescribeStackEvents",
          "cloudformation:CreateChangeSet",
          "cloudformation:DescribeChangeSet",
          "cloudformation:ExecuteChangeSet",
          "cloudformation:GetTemplateSummary",
          "cloudformation:DescribeStacks",
  • Confirm changes before deploy: deploy前に確認を必要とするか。CI上で動かす想定ならnにしておくと良いです。

  • Allow SAM CLI IAM role creation: デプロイするlambdaに付与するロールを自動作成するか。自動作成する場合は余計な権限もつくことがあるので要注意です。

  • Save arguments to samconfig.toml: yならこの対話型操作の設定内容を保存します。

すると以下のようなファイルが生成されます。次回以降のsam deploy実行時はこの設定ファイルを見てくれるので、必要に応じて設定を書き換えると良いでしょう。

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-app"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-6qa86tkchc0g"
s3_prefix = "sam-app"
region = "ap-northeast-1"
capabilities = "CAPABILITY_IAM"

なお、capabilitiesで指定できる値は以下のいずれかを設定することができます。

  • CAPABILITY_IAM
  • CAPABILITY_NAMED_IAM
  • CAPABILITY_RESOURCE_POLICY
  • CAPABILITY_AUTO_EXPAND

詳細はAWS Serverless Application Repository開発者ガイドに記載されていますが、template.yml管理下のリソースの複雑さで設定の切り替えが必要なようです。Iamロールをtemplate.ymlで管理する場合などのケースでは要注意です。

今回はシンプルにlambdaのみのリソース管理化なのでデフォルトのCAPABILITY_IAMで良いようです。

3.2デプロイの実行

samconfig.tomlが存在していればsam deployでデプロイされるはずです。

f:id:glassmonekey:20201001202748p:plain
デプロイ結果のスクショ

無事にデプロイされました  👍

lambdaのコンソールを見に行くとAPI Gatewayの項目からエンドポイントが確認できるはずです。

f:id:glassmonekey:20201001202831p:plain
コンソール画面を確認

確認できたエンドポイントを仮にhttps://hogehoge.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/とすると結果が返ってくれば成功です。

$ curl  https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/     

{"statusCode":200,"headers":null,"multiValueHeaders":null,"body":"Hello, $実行環境IPアドレス\n"}

エンドポイントにcurlを投げてみるとローカル実行と同じようなレスポンスが返ってくるはずです。

しかし、ローカル実行時とは内部的にはネットワーク構成が異なっているので返ってくるIPは違います。

その他設定項目について

次に現状BASE BANKチームで行っている運用設定の一部をご紹介します。

環境ごとの設定の切り替え

リソースのarnの切り替えなどを環境ごとに切り替えたい設定を外部から注入できるようにします。 今回はシンプルにAPP_NAMEENVを環境変数経由で受け取り表示するように書き換えてみます。

name := os.Getenv("APP_NAME")
env := os.Getenv("ENV")
return events.APIGatewayProxyResponse{
    Body:       fmt.Sprintf("%s@%s", name, env),
    StatusCode: 200,
}, nil

この環境変数を受け取るために、以下のようにトップレベルにParametersを追加し、Functionリソース配下にEnvironment.Variablesを追加します。

ここの意味としてはtemplateがパラメータとして受け取った値は環境変数としてFunctionリソースに受け渡すという形になります。!RefCloud Forrmationの関数で、セクション間の参照を表します。

Parameters:
  ENV:
    Type: String
  AppName:
    Type: String

Resources:
  HelloWorldFunction:
~~~ 省略 ~~~~
      Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
        Variables:
          ENV: !Ref ENV
          APP_NAME: !Ref AppName

このようにしておくと実行時にKey=Valueの形式でパラメータを渡すことができます。

$ sam local invoke \
    --parameter-overrides 'ENV=local AppName=Hello'
$ sam deploy \
    --parameter-overrides 'ENV=dev AppName=Hello'

値を渡す方法は他にもあったりはしますが、ローカル実行とdeploy時の対照が取れているのところがおすすめなポイントです。

若干ハマったポイントとしてパラメータキーには_などの記号を含むパラメータは読み込んでくれないようでAPP_NAMEではなくAppName としています。

環境ごとのデプロイ設定

特にCI上ので実行を加味した場合、環境変数の注入用のparameter-overridesの他にも設定しておきたいパラメータがあります。

$ sam deploy \
    --s3-bucket dev-sam-stacks \
    --parameter-overrides 'ENV=dev AppName=Hello' \
    --force-upload \
    --no-fail-on-empty-changeset
  • s3-bucket: cloud formationのアップロード先のバケット。基本的に環境ごとで、切り替えることになる思うのでsamconfig.toml から記述を消し、パラメータで受け取れるようにしておきます。
  • force-upload: ソースコードの変更を伴わないLambdaの設定値のみの変更の際に、正しくデプロイされないケースがあったのでフラグを追加しておきます。
  • no-fail-on-empty-changeset: 内容の変更がなくてもエラーにならないフラグです。CIで動かす場合を考えると、変更がないときも正常系と考えて良いはずなのでこのフラグを立てておきます。

Makefileの用意

以上を踏まえて環境ごとの設定に切り替えはあらかじめMakefile内に記述しておきます。

Makefileの用意を用意しておくと、チーム内にコマンドの共有ができるのと、CI 上で何かあったときに手元での検証がしやすくなるので便利です。

.PHONY: build test local-run local-up dev-deploy

build:
    sam build

# テストの実行
test:
    @cd ./hello-world/ && \
    go test -v ./...

# ローカルでlambdaを実行する
local-run: build
    sam local invoke \
        --parameter-overrides 'ENV=local AppName=Hello'

# apiとして起動する場合
local-up: build
    sam local start-api \
        --parameter-overrides 'ENV=local AppName=Hello'

# デプロイする
dev-deploy: build
    sam deploy \
    --s3-bucket sam-app-stack  \
    --parameter-overrides 'ENV=dev AppName=Hello' \
    --force-upload \
    --no-fail-on-empty-changeset

まとめ

AWS SAMを使った入門と運用設定の一部を紹介しました。 もしこれからAWS SAMを使用される方々の助けになれれば幸いです。

従来の都度deployして確かめる方法だとどうしても1回の検証に時間がかかってしまい、Lambdaに手を出しづらい状況下だったので、今後の量産体制に無事に繋げられそうです。バージョン1 になったことにより大分扱いやすくなった印象をうけました。

また、今回は時間の関係で導入まではできてないのですがlocal stackを使えばAWSリソース依存のテストもできそうなので、機会があればこちらも挑戦してみたいと思います。