AWS SAMってなーに?
AWS サーバーレスアプリケーションモデル (SAM、Serverless Application Model) とは、
サーバーレスアプリケーション構築用のオープンソースフレームワークです。迅速に記述可能な構文で関数、API、データベース、イベントソースマッピングを表現できます。リソースごとにわずか数行で、任意のアプリケーションを定義して YAML を使用してモデリングできます。デプロイ中、SAM が SAM 構文を AWS CloudFormation 構文に変換および拡張することで、サーバーレスアプリケーションの構築を高速化することができます。
引用: AWS サーバーレスアプリケーションモデル
つまり、サーバレスなアプリケーションを割と簡単に構築可能にします。
サーバレスに特化したCloudFormation(というか拡張版CloudFormation)
やりたいこと
- モバイルアプリのバックエンドをAWSサービスで実現したい!(APIとかDBとか)
- API Gateway + Lambda + DynamoDB
- できるだけコードベースで実現したい!(Infrastructure as Code)
- AWS SAM
- 単体テスト自動化とかやってみたい!
- Jest
- CI/CDとかもやってみたい!
- CircleCI
つくったもの
実際にAWS SAMで構築したアーキテクチャ全体
SAM Templete(クリックで展開)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS Serverless Specification template for KameraYohou.
Globals:
Function:
Runtime: nodejs10.x
Timeout: 3
MemorySize: 128
Description: Functions Created by SAM template yaml
Resources:
# API Gateway
kameraApi:
Type: AWS::Serverless::Api
Properties:
# Auth: ApiAuth
Name: KameraYohouAPI
StageName: develop
EndpointConfiguration: REGIONAL
Auth:
ApiKeyRequired: true
# Lambda
getSubjects:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
CodeUri: getSubjects
Policies:
- DynamoDBReadPolicy:
TableName: subject
Events:
putSubjectApi:
Type: Api
Properties:
Path: /subjects
Method: GET
RestApiId: !Ref kameraApi
registerSubject:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
CodeUri: registerSubject
Policies:
- DynamoDBCrudPolicy:
TableName: subject
Events:
putSubjectApi:
Type: Api
Properties:
Path: /subjects
Method: POST
RestApiId: !Ref kameraApi
disableSubject:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
CodeUri: disableSubject
Policies:
- DynamoDBCrudPolicy:
TableName: subject
Events:
putSubjectApi:
Type: Api
Properties:
Path: /subjects/{subject_id}
Method: DELETE
RestApiId: !Ref kameraApi
getSpots:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
CodeUri: getSpots
Policies:
- DynamoDBReadPolicy:
TableName: spot
Events:
getSpotApi:
Type: Api
Properties:
Path: /spots
Method: GET
RestApiId: !Ref kameraApi
# DynamoDB
spot:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: spot
PrimaryKey:
Name: spot_id
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
subject:
Type: AWS::Serverless::SimpleTable
Properties:
TableName: subject
PrimaryKey:
Name: subject_id
Type: String
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
# API Key
FlutterApiKey:
Type: AWS::ApiGateway::ApiKey
DependsOn: kameraApi
Properties:
Name: flutter-api-key
Enabled: true
StageKeys:
- RestApiId: !Ref kameraApi
StageName: !Ref kameraApidevelopStage
FlutterApiUsagePlan:
Type: AWS::ApiGateway::UsagePlan
DependsOn: kameraApi
Properties:
ApiStages:
- ApiId: !Ref kameraApi
Stage: !Ref kameraApidevelopStage
Throttle:
BurstLimit: 20
RateLimit: 10
UsagePlanName: flutter-api-usage-plan
FlutterApiUsagePlanKey:
Type: AWS::ApiGateway::UsagePlanKey
DependsOn:
- FlutterApiKey
- FlutterApiUsagePlan
Properties :
KeyId: !Ref FlutterApiKey
KeyType: API_KEY
UsagePlanId: !Ref FlutterApiUsagePlan
Outputs:
ApiGatewayID:
Description: The API Gateway ID
Value: !Ref kameraApi
ApiKeyID:
Description: The API Key ID
Value: !Ref FlutterApiKey
テストやCI/CDを実行するフロー
CircleCI Config(クリックで展開)
version: 2.1
orbs:
node: circleci/node@1.1.6
jobs:
unit-test:
executor: node/default
steps:
- checkout
- node/with-cache:
steps:
- run: npm install
- run: npm test
- store_test_results:
path: coverage
build-test:
docker:
- image: cleartone1216/aws-sam-cli:0.0.2
steps:
- checkout
- run: sam validate
- run: sam build
- run: sam deploy
post-output:
docker:
- image: cleartone1216/aws-sam-cli:0.0.2
steps:
- checkout
- run: chmod +x ./.circleci/getResult.sh
- run: ./.circleci/getResult.sh
workflows:
version: 2
pull-request:
jobs:
- unit-test
- build-test:
requires:
- unit-test
- post-output:
requires:
- build-test
むずかしかったこと
CircleCI上でAWS SAM CLIを実行する
CircleCI上でどうしてもaws-sam-cliが使いたかったので、実行可能なCircleCI Orbを利用してみる。
https://circleci.com/orbs/registry/orb/circleci/aws-serverless
すると、テスト実行ごとにいろんなツールがインストールされて時間がめちゃくちゃかかる
(自分の環境では、全体で2分。そのうちツール導入で1分半)
ほかに手法がないか調べたが、疲れちゃったので自分でコンテナを作ることに
FROM python:3.8.1
RUN pip install awscli aws-sam-cli
RUN apt update && apt install -y nodejs npm jq && apt clean -y
LABEL com.circleci.preserve-entrypoint=true
ビルドした内容をDocker Hubにあげて、CircleCIから参照することで解決
無いなら作る、エンジニアの基本かも。
SAM実行ユーザのIAM Policy
導入ガイドでは「管理者ユーザを用意します」とあるが、最小権限の原則に反するため必要最低限のユーザを作成。
情報収集不足のせいか、どのポリシーが必要なのかが全く分からず。。。
仕方がないので、実行➡権限エラー➡権限付与➡実行➡権限エラー...を繰り返してみた。
結果以下のようなポリシーを作成してIAMユーザに付与。
ざっくりまとめると、
- S3 (Get,Put)
- Lambda (CRUD,Configuration)
- IAM (RoleのCRUD, PolicyのCRUD, アタッチデタッチ)
- DynamoDB (Create, Delete, Describe)
- CloudFormation (ChangeSet, Template, Stackまわり)
- API Gateway (method)
SAM CLI用IAM Policy(クリックで展開)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"lambda:UpdateFunctionConfiguration",
"lambda:UpdateFunctionCode",
"lambda:GetFunctionConfiguration",
"lambda:GetFunction",
"lambda:DeleteFunction",
"lambda:CreateFunction",
"lambda:AddPermission",
"iam:PutRolePolicy",
"iam:PassRole",
"iam:ListPolicies",
"iam:GetRolePolicy",
"iam:GetRole",
"iam:DetachRolePolicy",
"iam:DeleteRolePolicy",
"iam:DeleteRole",
"iam:CreateRole",
"iam:AttachRolePolicy",
"dynamodb:DescribeTable",
"dynamodb:DeleteTable",
"dynamodb:CreateTable",
"cloudformation:ValidateTemplate",
"cloudformation:GetTemplateSummary",
"cloudformation:ExecuteChangeSet",
"cloudformation:DescribeStacks",
"cloudformation:DescribeStackEvents",
"cloudformation:DescribeChangeSet",
"cloudformation:CreateChangeSet",
"apigateway:PUT",
"apigateway:POST",
"apigateway:PATCH",
"apigateway:GET",
"apigateway:DELETE"
],
"Resource": "*"
}
]
}
APIエンドポイントとAPI Keyの共有
エンドポイントのURLやAPI Keyが変更されるたびに、開発チーム内で共有するのが大変だったのでいい方法ないかと思案
そこで、CircleCI上でデプロイ成功したタイミングで、Slackに通知するようにした。
(セキュリティ的な観点は一旦考慮しない)
流れとしては、
- AWS SAMのtemplete.yamlにOutputsとして、「通知したい内容」を記載
- aws-cliの
aws cloudformation describe-stacks
コマンドから「通知したい内容」を取得 - Slack Incoming Webhookを叩く
問題点としては、template.yaml内でAPI GatewayのAPI Keyを取得する仕様がないこと
そこで、「通知したい内容」にAPI KeyのIDを含めてから、
aws-cliのaws apigateway get-api-key
でKeyの中身を取得することにした。
これら一連の流れをシェルスクリプトにまとめて、CircleCI内で実行。
#!/bin/bash
ApiKeyID=`aws cloudformation describe-stacks --stack-name KameraLambda | jq -r '.Stacks[].Outputs | map(select(.OutputKey == "ApiKeyID")) | .[].OutputValue'`
ApiGatewayID=`aws cloudformation describe-stacks --stack-name KameraLambda | jq -r '.Stacks[].Outputs | map(select(.OutputKey == "ApiGatewayID")) | .[].OutputValue'`
ApiKey=`aws apigateway get-api-key --api-key $ApiKeyID --include-value | jq -r .value`
curl -X POST --data-urlencode "payload={\"text\": \"API_BASE_URL=https://${ApiGatewayID}.execute-api.ap-northeast-1.amazonaws.com/develop
\n API_KEY=${ApiKey}\"}" $Slack_URL
※$Slack_URLはCircleCI上の環境変数で定義
これからやること
CircleCI上でAWS LambdaのInvokeテスト
現状だと、テストはJestのみのUnitテストなので、aws-sam-cliのsam local invoke
でLambdaの機能テストも実施したい。
sam local
の実行にはDocker環境が必要なので、CircleCI上でDockerコンテナが動くようにする、ということが課題になりそう。