はじめに
AWS CloudFormationを利用してLambda Layerを使用したSlack通知定期実行Lambdaの構築のテンプレートのサンプルです。
CloudWatchにて、Lambdaを定期実行します。また、Lambdaは、S3アーティファクトを使用します。
通知内容は起動しているEC2インスタンスを停止する際の情報になります。
テンプレートの概要が分からない場合は、はじめてのAWS CloudFormationテンプレートを理解するを参考にしてください。
コードはGitHubにもあります。
今回は、akane というシステムの dev 環境を想定しています。
同じ構成で違う環境を作成する場合は、{環境名}-parameters.jsonを別途作成します。
akane (システム)
├── lambda (スタック)
│ ├── code
│ │ └── getEC2InfoToSlack.py (S3アーティファクトソース)
│ ├── code-lambda-getEC2InfoToSlack.zip (S3アーティファクト)
│ ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
│ ├── dev-parameters.json (dev 環境のパラメータ)
│ ├── lambda.yml (CFnテンプレート)
│ ├── mkzip.sh (S3アーティファクト作成シェル)
│ └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
├── lambda-layer
│ ├── all-parameters.json (all 環境のパラメータ)
│ ├── code
│ │ └── python
│ │ ├── requirements.txt
│ │ └── setSlackWebHook.py (S3アーティファクトソース)
│ ├── code-lambda-setSlackWebHook.zip (S3アーティファクト)
│ ├── delete_artifact.all.sh (S3アーティファクト削除シェル)
│ ├── lambda-layer.yml (CFnテンプレート)
│ ├── mkzip.sh (S3アーティファクト作成シェル)
│ └── upload_artifact.all.sh (S3アーティファクトアップロードシェル)
└─ s3 (スタック)
├─ s3.yml (CFnテンプレート)
└─ all-parameters.json (all 環境のパラメータ)
AWS リソース構築内容
- lambdaスタック
- Lambdaロール
- Lambda
- lambda-layerスタック
- Lambda-layer
- s3スタック
- s3バケット (akane-all-s3-artifacts)
- バケットポリシー (s3:GetObject)
実行環境の準備
AWS CloudFormationを動かすためのAWS CLIの設定を参考にしてください。
AWS リソース構築準備
-
akane/lambda/dev-parameters.json
のSlackWebHookUrl
にSlack Incoming WebhookのURLを記述する。※ incoming-webhookにて作成する
-
akane/lambda/dev-parameters.json
のSlackChannel
にSlack通知のチェンネル名を記述する。#test_slack
AWS リソース構築手順
-
下記を実行してスタックを作成
./create_stacks.sh
※ 実行後、AWS Certificate Manager にて、対象ドメインを選択し、
Route 53 でのレコード作成
を押下する。 -
下記を実行してLambdaの動作を確認
./test_lambda.sh
-
下記を実行してスタックを削除
./delete_stacks.sh
構築テンプレート
1. s3スタック
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}
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "all"
}
]
}
2. lambda-layerスタック
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda Layer 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
LambdaLayerName:
Type: String
CompatibleRuntime1:
Type: String
# Mappings
# Conditions
# Transform
Resources:
# LambdaLayer 作成
akaneLambdaLayerSetSlackWebHook:
Type: AWS::Lambda::LayerVersion
Properties:
CompatibleRuntimes:
- !Ref CompatibleRuntime1
Content:
S3Bucket: !Ref ArtifactS3Bucket
S3Key: !Sub
- code-lambda-${LambdaLayerName}.zip
- {LambdaLayerName: !Ref LambdaLayerName}
Description: !Sub
- ${SystemName}-${EnvType}-lambda-layer-${LambdaLayerName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaLayerName: !Ref LambdaLayerName}
LayerName: !Ref LambdaLayerName
LicenseInfo: MIT
Outputs:
akaneLambdaLayerSetSlackWebHook:
Value: !Ref akaneLambdaLayerSetSlackWebHook
Export:
Name: !Sub
- ${SystemName}-${EnvType}-lambda-layer-arn-${LambdaLayerName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaLayerName: !Ref LambdaLayerName}
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "all"
},
{
"ParameterKey": "ArtifactS3Bucket",
"ParameterValue": "akane-all-s3-artifacts"
},
{
"ParameterKey": "LambdaLayerName",
"ParameterValue": "setSlackWebHook"
},
{
"ParameterKey": "CompatibleRuntime1",
"ParameterValue": "python3.8"
}
]
}
3. lambdaスタック
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.
EnvTypeCommon:
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
SlackWebHookUrl:
Type: String
SlackChannel:
Type: String
LambdaLayerName:
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
- arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
Path: /
Policies:
- PolicyName: ec2
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:Start*
- ec2:Stop*
Resource: '*'
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 作成
akaneLambdaGetEC2InfoToSlack:
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:
SLACK_WEB_HOOK_URL: !Ref SlackWebHookUrl
SLACK_CHANNEL: !Ref SlackChannel
FunctionName: !Sub
- ${SystemName}-${EnvType}-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
Handler: !Sub
- ${LambdaName}.handler
- {LambdaName: !Ref LambdaName}
Layers:
- Fn::ImportValue: !Sub
- ${SystemName}-${EnvTypeCommon}-lambda-layer-arn-${LambdaLayerName}
- {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon, LambdaLayerName: !Ref LambdaLayerName}
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, LambdaName: !Ref LambdaName}
- Key: SystemName
Value: !Ref SystemName
- Key: EnvType
Value: !Ref EnvType
Timeout: 900
TracingConfig:
Mode: PassThrough
# スケジュール 作成
akaneEventsRule:
Type: AWS::Events::Rule
Properties:
Description: !Sub
- ${SystemName}-${EnvType}-rule-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
Name: !Sub
- ${SystemName}-${EnvType}-rule-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
ScheduleExpression: "rate(24 hours)"
State: ENABLED
Targets:
- Arn: !GetAtt akaneLambdaGetEC2InfoToSlack.Arn
Id: !Sub
- ${SystemName}-${EnvType}-lambda-${LambdaName}
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
# LambdaPermission 作成
akaneLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt akaneLambdaGetEC2InfoToSlack.Arn
Principal: events.amazonaws.com
SourceArn: !GetAtt akaneEventsRule.Arn
Outputs:
akaneLambdaGetEC2InfoToSlackArn:
Value: !GetAtt akaneLambdaGetEC2InfoToSlack.Arn
Export:
Name: !Sub
- ${SystemName}-${EnvType}-lambda-arn
- {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
},
{
"ParameterKey": "EnvTypeCommon",
"ParameterValue": "all"
},
{
"ParameterKey": "ArtifactS3Bucket",
"ParameterValue": "akane-all-s3-artifacts"
},
{
"ParameterKey": "LambdaName",
"ParameterValue": "getEC2InfoToSlack"
},
{
"ParameterKey": "LambdaMemorySize",
"ParameterValue": "128"
},
{
"ParameterKey": "LambdaRuntime",
"ParameterValue": "python3.8"
},
{
"ParameterKey": "SlackWebHookUrl",
"ParameterValue": "https://hooks.slack.com/services/XXXXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
},
{
"ParameterKey": "SlackChannel",
"ParameterValue": "#test_slack"
},
{
"ParameterKey": "LambdaLayerName",
"ParameterValue": "setSlackWebHook"
}
],
"Capabilities": [
"CAPABILITY_NAMED_IAM"
]
}
4. 実行ファイル
#!/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-layer/mkzip.sh
./akane/lambda-layer/upload_artifact.all.sh
create_stack all lambda-layer
./akane/lambda/mkzip.sh
./akane/lambda/upload_artifact.dev.sh
create_stack dev lambda
exit 0
#!/bin/sh
cd `dirname $0`
LAMBDA_NAME=akane-dev-lambda-getEC2InfoToSlack
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
#!/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 lambda
./akane/lambda/delete_artifact.dev.sh
delete_stack all lambda-layer
./akane/lambda-layer/delete_artifact.all.sh
delete_stack all s3
exit 0
4. アーティファクト関連ファイル
#!/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|requirements.txt" | xargs rm -rf
if [ -e "requirements.txt" ]; then
pip install -r requirements.txt -t .
fi
rm -f ${FILE}
zip -r ${FILE} ./*
exit 0
#!/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
#!/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