はじめに
AWS CloudFormationを利用してLambdaを使用するAPI Gateway構築のテンプレートのサンプルです。
Lambdaは、S3アーティファクトを使用します。
テンプレートの概要が分からない場合は、はじめてのAWS CloudFormationテンプレートを理解するを参考にしてください。
コードはGitHubにもあります。
今回は、akane というシステムの dev 環境を想定しています。
同じ構成で違う環境を作成する場合は、{環境名}-parameters.jsonを別途作成します。
ディレクトリ構成
akane (システム)
├── apigw (スタック)
│ ├── apigw.yml (CFnテンプレート)
│ └── dev-parameters.json (dev 環境のパラメータ)
├── lambda (スタック)
│ ├── code
│ │ └── getTiAmo.py (S3アーティファクトソース)
│ ├── code-lambda-getTiAmo.zip (S3アーティファクト)
│ ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
│ ├── dev-parameters.json (dev 環境のパラメータ)
│ ├── lambda.yml (CFnテンプレート)
│ ├── mkzip.sh (S3アーティファクト作成シェル)
│ └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
└─ s3 (スタック)
├─ s3.yml (CFnテンプレート)
└─ all-parameters.json (all 環境のパラメータ)
AWS リソース構築内容
- apigwスタック
- CloudWatch ログのロール
- API Gateway
- 使用量プラン
- API キー
- lambdaスタック
- Lambdaロール
- Lambda
- s3スタック
- s3バケット (akane-all-s3-artifacts)
- バケットポリシー (s3:GetObject)
実行環境の準備
AWS CloudFormationを動かすためのAWS CLIの設定を参考にしてください。
AWS リソース構築手順
-
下記を実行してスタックを作成
./create_stacks.sh
-
下記を実行してLambdaの動作を確認
./test_lambda.sh
-
下記を実行してAPI Gatewayの動作を確認
./test_apigw.sh <URL> <API_KEY>
-
下記を実行してスタックを削除
./delete_stacks.sh
構築テンプレート
1. s3スタック
s3.yml
AWSTemplateFormatVersion: 2010-09-09
Description: S3 For Akane
# Metadata:
Parameters:
SystemName:
Type: String
AllowedPattern: '[a-zA-Z0-9-]*'
EnvType:
Description: Environment type.
Type: String
AllowedValues: [all, dev, stg, prod]
ConstraintDescription: must specify all, dev, stg, or prod.
# Mappings
# Conditions
# Transform
Resources:
# S3 Bucket作成
akaneS3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
BucketName: !Sub
- ${SystemName}-${EnvType}-s3-artifacts
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-s3-artifacts
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
# S3 BucketPolicy作成
akaneS3BucketPolicy:
Type: AWS::S3::BucketPolicy
DependsOn: akaneS3Bucket
Properties:
Bucket: !Ref akaneS3Bucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource: !Join
- ''
- - 'arn:aws:s3:::'
- !Ref akaneS3Bucket
- /*
Principal:
AWS: '*'
Outputs:
akaneS3Bucket:
Value: !Ref akaneS3Bucket
Export:
Name: !Sub
- ${SystemName}-${EnvType}-s3-artifacts
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
all-parameters.json
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "all"
}
]
}
2. lambdaスタック
lambda.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane
# Metadata:
Parameters:
SystemName:
Type: String
AllowedPattern: '[a-zA-Z0-9-]*'
EnvType:
Description: Environment type.
Type: String
AllowedValues: [all, dev, stg, prod]
ConstraintDescription: must specify all, dev, stg, or prod.
ArtifactS3Bucket:
Type: String
LambdaName:
Type: String
LambdaMemorySize:
Type: Number
LambdaRuntime:
Type: String
GirlName:
Type: String
# Mappings
# Conditions
# Transform
Resources:
# ロール作成
akaneRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Description: !Sub
- ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Path: /
RoleName: !Sub
- ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
# Lambda作成
akaneLambdaGetTiAmo:
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: !Ref ArtifactS3Bucket
S3Key: !Sub
- code-lambda-${LambdaName}.zip
- {LambdaName: !Ref LambdaName}
Description: !Sub
- ${SystemName}-${EnvType}-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
Environment:
Variables:
GIRL_NAME: !Ref GirlName
FunctionName: !Sub
- ${SystemName}-${EnvType}-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
Handler: !Sub
- ${LambdaName}.handler
- {LambdaName: !Ref LambdaName}
MemorySize: !Ref LambdaMemorySize
Role: !GetAtt akaneRole.Arn
Runtime: !Ref LambdaRuntime
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
Timeout: 900
TracingConfig:
Mode: PassThrough
Outputs:
akaneLambdaGetTiAmoArn:
Value: !GetAtt akaneLambdaGetTiAmo.Arn
Export:
Name: !Sub
- ${SystemName}-${EnvType}-lambda-arn
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
dev-parameters.json
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
},
{
"ParameterKey": "ArtifactS3Bucket",
"ParameterValue": "akane-all-s3-artifacts"
},
{
"ParameterKey": "LambdaName",
"ParameterValue": "getTiAmo"
},
{
"ParameterKey": "LambdaMemorySize",
"ParameterValue": "128"
},
{
"ParameterKey": "LambdaRuntime",
"ParameterValue": "python3.8"
},
{
"ParameterKey": "GirlName",
"ParameterValue": "akane"
}
],
"Capabilities": [
"CAPABILITY_NAMED_IAM"
]
}
3. apigwスタック
apigw.yml
AWSTemplateFormatVersion: 2010-09-09
Description: API Gateway For Akane
# Metadata:
Parameters:
SystemName:
Type: String
AllowedPattern: '[a-zA-Z0-9-]*'
EnvType:
Description: Environment type.
Type: String
AllowedValues: [all, dev, stg, prod]
ConstraintDescription: must specify all, dev, stg, or prod.
EnvTypeCommon:
Description: Environment type.
Type: String
AllowedValues: [all, dev, stg, prod]
ConstraintDescription: must specify all, dev, stg, or prod.
APIPathPart:
Type: String
APIGatewayStage:
Type: String
# Mappings
# Conditions
# Transform
Resources:
# ApiGateway CloudWatchRole 作成
akaneCloudWatchRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action: sts:AssumeRole
Description: !Sub
- ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
- {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
Path: /
RoleName: !Sub
- ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
- {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
- {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvTypeCommon
Value: !Ref EnvTypeCommon
akaneApiGatewayAccount:
Type: AWS::ApiGateway::Account
Properties:
CloudWatchRoleArn: !GetAtt akaneCloudWatchRole.Arn
# ApiGateway 作成
akaneApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
DependsOn: akaneApiGatewayAccount
Properties:
Name: !Sub
- ${SystemName}-${EnvType}-apigw
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Description: !Sub
- ${SystemName}-${EnvType}-apigw
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
EndpointConfiguration:
Types:
- REGIONAL
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-apigw
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
akaneApiGatewayResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref akaneApiGatewayRestApi
ParentId: !GetAtt akaneApiGatewayRestApi.RootResourceId
PathPart: !Ref APIPathPart
akaneApiGatewayResourceMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref akaneApiGatewayRestApi
ResourceId: !Ref akaneApiGatewayResource
ApiKeyRequired: true
AuthorizationType: NONE
HttpMethod: GET
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ''
- - 'arn:aws:apigateway:'
- !Ref AWS::Region
- ':lambda:path/2015-03-31/functions/'
- Fn::ImportValue: !Sub
- ${SystemName}-${EnvType}-lambda-arn
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- '/invocations'
# ApiGateway LambdaPermission 作成
akaneApiGatewayLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::ImportValue: !Sub
- ${SystemName}-${EnvType}-lambda
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
# ApiGateway Deployment 作成
akaneApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: akaneApiGatewayResourceMethod
Properties:
Description: !Sub
- ${SystemName}-${EnvType}-apigw-deployment
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
RestApiId: !Ref akaneApiGatewayRestApi
akaneApiGatewayStage:
Type: AWS::ApiGateway::Stage
Properties:
DeploymentId: !Ref akaneApiGatewayDeployment
Description: !Sub
- ${SystemName}-${EnvType}-apigw-stage
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
MethodSettings:
- CacheDataEncrypted: false
CachingEnabled: false
DataTraceEnabled: true # CloudWatch ログを有効化
HttpMethod: '*'
LoggingLevel: ERROR
MetricsEnabled: false # 詳細 CloudWatch メトリクスを有効化
ResourcePath: '/*'
RestApiId: !Ref akaneApiGatewayRestApi
StageName: !Ref APIGatewayStage
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-apigw-stage
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
TracingEnabled: false # X-Ray トレースの有効化
# ApiGateway APIキー 作成
akaneApiGatewayKey:
Type: AWS::ApiGateway::ApiKey
Properties:
Name: !Sub
- ${SystemName}-${EnvType}-apigw-key
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Description: !Sub
- ${SystemName}-${EnvType}-apigw-key
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Enabled: true
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-apigw-key
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
akaneApiGatewayUsagePlan:
Type: AWS::ApiGateway::UsagePlan
DependsOn: akaneApiGatewayStage
Properties:
ApiStages:
- ApiId: !Ref akaneApiGatewayRestApi
Stage: !Ref APIGatewayStage
Description: !Sub
- ${SystemName}-${EnvType}-apigw-usage-plan
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
UsagePlanName: !Sub
- ${SystemName}-${EnvType}-apigw-usage-plan
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
Tags:
- Key: Name
Value: !Sub
- ${SystemName}-${EnvType}-apigw-usage-plan
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
akaneApiGatewayUsagePlanKey:
Type: AWS::ApiGateway::UsagePlanKey
Properties:
KeyId: !Ref akaneApiGatewayKey
KeyType: API_KEY
UsagePlanId: !Ref akaneApiGatewayUsagePlan
dev-parameters.json
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
},
{
"ParameterKey": "EnvTypeCommon",
"ParameterValue": "all"
},
{
"ParameterKey": "APIPathPart",
"ParameterValue": "whisper"
},
{
"ParameterKey": "APIGatewayStage",
"ParameterValue": "v1"
}
],
"Capabilities": [
"CAPABILITY_NAMED_IAM"
]
}
4. 実行ファイル
create_stacks.sh
#!/bin/sh
cd `dirname $0`
SYSTEM_NAME=akane
create_stack () {
ENV_TYPE=$1
STACK_NAME=$2
aws cloudformation create-stack \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME} \
--template-body file://./${SYSTEM_NAME}/${STACK_NAME}/${STACK_NAME}.yml \
--cli-input-json file://./${SYSTEM_NAME}/${STACK_NAME}/${ENV_TYPE}-parameters.json
aws cloudformation wait stack-create-complete \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}
create_stack all s3
./akane/lambda/mkzip.sh
./akane/lambda/upload_artifact.dev.sh
create_stack dev lambda
create_stack dev apigw
exit 0
test_lambda.sh
#!/bin/sh
cd `dirname $0`
echo " --- \033[0;33m TEST LAMBDA \033[0;39m --- "
LAMBDA_NAME=akane-dev-lambda-getTiAmo
OUTPUT_FILE=response.json
aws lambda invoke --function-name ${LAMBDA_NAME} --log-type Tail ${OUTPUT_FILE} --query 'LogResult' --output text | base64 -D
cat ${OUTPUT_FILE}
exit 0
test_apigw.sh
#!/bin/sh
set -e
if [ $# -ne 2 ]; then
echo " --- \033[0;31m [ERROR]: The following arguments are required \033[0;39m --- "
echo " \033[0;33m$0 <URL> <API_KEY>\033[0;39m"
echo " \033[0;33m<URL> Example: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1/whisper\033[0;39m"
exit 1
fi
cd `dirname $0`
echo " --- \033[0;33m TEST APIGW \033[0;39m --- "
URL=$1
API_KEY=$2
curl -X GET -H 'Content-Type: application/json' \
-H "x-api-key: ${API_KEY}" \
"${URL}"
exit 0
delete_stacks.sh
#!/bin/sh
cd `dirname $0`
SYSTEM_NAME=akane
delete_stack () {
ENV_TYPE=$1
STACK_NAME=$2
aws cloudformation delete-stack \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
aws cloudformation wait stack-delete-complete \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}
delete_stack dev apigw
delete_stack dev lambda
./akane/lambda/delete_artifact.dev.sh
delete_stack all s3
exit 0
4. アーティファクト関連ファイル
mkzip.sh
#!/bin/sh
cd `dirname $0`
ENV_TYPE=dev
LAMBDA_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')
FILE=../code-lambda-${LAMBDA_NAME}.zip
cd ./code
ls . | grep -v -E ".gitignore|${LAMBDA_NAME}.py" | xargs rm -rf
rm -f ${FILE}
zip -r ${FILE} ./*
exit 0
upload_artifact.dev.sh
#!/bin/sh
cd `dirname $0`
BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}
BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')
ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')
aws s3 cp ${ARTIFACT_NAME}.zip s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip
exit 0
delete_artifact.dev.sh
#!/bin/sh
cd `dirname $0`
BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}
BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')
ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')
aws s3 rm s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip
exit 0