2020/7/23 追記
こちらの記事の内容はかなり古くなってしまったので、以下を参照いただければと思います。
AWS SAM CLI 再入門 2020.07
https://qiita.com/hayao_k/items/7827c3778a23c514e196
はじめに
aws-sam-local が バージョンアップされ、aws-sam-cli となりました。
https://aws.amazon.com/about-aws/whats-new/2018/04/aws-sam-cli-releases-new-init-command/
また、新しいコマンド sam init が追加され、
より簡単にサンプルアプリケーションが作れるようになりました。
SAM Localの復習もかねてサンプルアプリケーションの作成からデプロイまで行ってみました。
※2018/9/3
version 0.6.0 のgenerate-events コマンドの構文変更について追記しました。
※2018/11/8
version 0.6.2 で追加された Homebrew/MSIによるインストールについて追記しました。
インストール
後述するinvokeやstart-apiコマンドを利用してローカルでテストする場合は
前提としてDocker のインストールが必要ですが、手順は割愛します。
SAM CLI の古いバージョンがインストールされている場合はアンインストールしておきます。
$ sam --version
A newer version of the AWS SAM CLI is available!
Your version: 0.2.11
Latest version: 0.3.0
See https://github.com/awslabs/aws-sam-local for upgrade instructions
$ npm uninstall -g aws-sam-local
新しい SAM CLI は python で実装されているため、pip でインストールします。
バージョンが 0.3.0 以上であることを確認します。
$ pip install --user aws-sam-cli
$ sam --version
SAM CLI, version 0.3.0
できました!
※2018/11/8時点の最新版は 0.6.2 です。
このバージョンからHomebrewおよびMSIによるインストールが可能になっています。
masOS
$ brew tap aws/tap
$ brew install aws-sam-cli
Windows
※ versionおよびビット数により、URLが変わります。
https://github.com/awslabs/aws-sam-cli/releases/download/v0.6.2/AWS_SAM_CLI_64_PY3.msi
sam init を試す
サンプルアプリケーションの作成
sam init コマンドで Node.js v8.10 のサンプルアプリケーションを作成してみます。
$ sam init --runtime nodejs
[+] Initializing project structure...
[SUCCESS] - Read sam-app/README.md for further instructions on how to proceed
[*] Project initialization is now complete
$ cd sam-app/
$ ls -l
total 16
drwxrwxr-x 3 ec2-user ec2-user 4096 May 9 16:48 hello_world
-rw-rw-r-- 1 ec2-user ec2-user 4625 May 9 16:48 README.md
-rw-rw-r-- 1 ec2-user ec2-user 1684 May 9 16:48 template.yaml
テストコードを含むサンプルコード と SAM テンプレートが作成されていることがわかります。
const axios = require('axios')
const url = 'http://checkip.amazonaws.com/';
let response;
exports.lambda_handler = async (event, context, callback) => {
try {
const ret = await axios(url);
response = {
'statusCode': 200,
'body': JSON.stringify({
message: 'hello world',
location: ret.data.trim()
})
}
}
catch (err) {
console.log(err);
callback(err, null);
}
callback(null, response)
};
'use strict';
const app = require('../../app.js');
const chai = require('chai');
const expect = chai.expect;
var event, context;
describe('Tests index', function () {
it('verifies successful response', async () => {
const result = await app.lambda_handler(event, context, (err, result) => {
expect(result).to.be.an('object');
expect(result.statusCode).to.equal(200);
expect(result.body).to.be.an('string');
let response = JSON.parse(result.body);
expect(response).to.be.an('object');
expect(response.message).to.be.equal("hello world");
expect(response.location).to.be.an("string");
});
});
});
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: nodejs8.10
Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
Variables:
PARAM1: VALUE
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Outputs:
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
ローカルでテストする
まずはユニットテストを実行します。
$ cd hello_wolrd
$ npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
added 34 packages in 1.611s
$ npm test
> hello_world@1.0.0 test /home/ec2-user/sam-app/hello_world
> mocha tests/unit/
Tests index
? verifies successful response (445ms)
1 passing (453ms)
成功しました。
generate-event コマンドで 指定したサービスからLambdaに送られる疑似イベントを作成することができます。
複数サービスのペイロードを作成可能ですが、ここではAPI Gatewayのイベントを作成します。
※ verison 0.6.0 から対応サービスが増え、さらにサービスごとに複数のイベントタイプをサポートするようになり、コマンドの構文が変わっています。
$ cd ../
$ sam local generate-event apigateway aws-proxy > event_file.json
# version 0.5.0 以前
$ sam local generate-event api > event_file.json
次にローカルで Lambda を Invoke してみます。
テンプレートファイルを読み取るのでtemplate.yaml があるディレクトリで実行します。
ローカルにdocker lambda のイメージがない場合は最初にダウンロードされるので少し時間がかかります。
$ sam local invoke HelloWorldFunction --event event_file.json
2018-05-09 17:16:26 Invoking app.lambda_handler (nodejs8.10)
2018-05-09 17:16:26 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:16:26 Starting new HTTP connection (1): 169.254.169.254
Fetching lambci/lambda:nodejs8.10 Docker container image...................................................................................................
2018-05-09 17:16:59 Mounting /home/ec2-user/sam-app/hello_world as /var/task:ro inside runtime container
START RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b Version: $LATEST
END RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b
REPORT RequestId: c6c1315d-7391-1717-5ab6-6130f1c4461b Duration: 372.69 ms Billed Duration: 400 ms Memory Size: 128 MB Max Memory Used: 35 MB
{"statusCode":200,"body":"{\"message\":\"hello world\",\"location\":\"xxx.xxx.xxx.xxx\"}"}
続いてローカルAPI Gateway を起動します。
$ sam local start-api
2018-05-09 17:19:44 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-05-09 17:19:44 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
2018-05-09 17:19:44 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
表示されたエンドポイントに curl すると、メッセージとIPアドレスが返ってきます。
$ curl http://127.0.0.1:3000/hello
{"message":"hello world","location":"xxx.xxx.xxx.xxx"}
ローカル API Gateway でも HTTP200 が返っていることを確認できます。
2018-05-09 17:24:21 Invoking app.lambda_handler (nodejs8.10)
2018-05-09 17:24:21 Starting new HTTP connection (1): 169.254.169.254
Fetching lambci/lambda:nodejs8.10 Docker container
2018-05-09 17:24:57 Mounting /home/ec2-user/sam-app/hello_world as /var/task:ro inside runtime container
START RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7 Version: $LATEST
END RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7
REPORT RequestId: 36fdf7c7-5885-1873-ce3f-ff9c4c04f0d7 Duration: 478.00 ms Billed Duration: 500 ms Memory Size: 128 MB Max Memory Used: 35 MB
2018-05-09 17:24:58 No Content-Type given. Defaulting to 'application/json'.
2018-05-09 17:24:58 127.0.0.1 - - [09/May/2018 17:24:58] "GET /hello HTTP/1.1" 200 -
デプロイ
ローカル上で動作確認できたので、実際にCloudFormationでデプロイしてみます。
validate コマンドで テンプレートがSAMの仕様に沿っているか検証できます。
$ sam validate
2018-05-09 17:45:35 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:45:35 Starting new HTTP connection (1): 169.254.169.254
2018-05-09 17:45:35 Starting new HTTPS connection (1): iam.amazonaws.com
/home/ec2-user/sam-app/template.yaml is a valid SAM Template
$ sam package --template-file template.yaml --s3-bucket <bucketname> --output-template-file packaged.yaml
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /home/ec2-user/sam-app/packaged.yaml --stack-name <YOUR STACK NAME>
$ sam deploy --template-file packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - mystack
デプロイが成功したら、Stack の Outputs に API Gateway の URL が出力されているため確認します。
$ aws cloudformation describe-stacks --stack-name mystack --query 'Stacks[].Outputs[1]'
[
{
"Description": "API Gateway endpoint URL for Prod stage for Hello World function",
"OutputKey": "HelloWorldApi",
"OutputValue": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
}
]
動作確認は省略します。
IAMポリシー周りの補足(7/19追記)
sam packge や sam deploy では 実体としてはCloudFormationが動いていますので
CloudFormation関係の権限が必要です。
あとは実際に作られるリソースの作成権限などが必要ですので、
今回のサンプルアプリケーション(Lambda/API Gateway/IAMロール)の場合、
一例ですが、こんな感じでしょうか。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:getObject",
"s3:PutObject",
"cloudformation:Describe*",
"cloudformation:EstimateTemplateCost",
"cloudformation:Get*",
"cloudformation:List*",
"cloudformation:ValidateTemplate",
"cloudformation:CreateChangeSet",
"cloudformation:ExecuteChangeSet",
"iam:CreateRole",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:GetRole",
"iam:PassRole",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunctionConfiguration",
"lambda:AddPermission",
"lambda:RemovePermission",
"apigateway:POST",
"apigateway:PATCH",
"apigateway:DELETE",
"apigateway:GET"
],
"Resource": "*"
}
]
}
リソースの作成権限を直接持たせたくない場合は、サービスロールに切り出して
deploy 実行時に --role-arn を指定するのも良いかと思います。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html
sam deploy --template-file packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM --role-arn arn:aws:iam::0123456789012:role/rolename
以上です。
参考になれば幸いです。