#構成例
例えばこんな感じでEventBridgeルールをトリガーとするEC2の起動停止LambdaをCloudFormationで作成したいことがあります。
手動で上記の構成のリソースを作成した後、Former2でCloudFormatinテンプレートを出力します。
Lambdaの実行に必要なIAMロールも一緒に作成したいので、Former2で以下のリソースを選択し、出力します。
- IAMポリシー
- IAMロール
- Lambda
- EventBridgeルール
#問題
上記の構成を改めてCloudFormationのスタックでリソース作成すると。。。
Lambdaの画面からトリガーが確認できません。
ターゲットにはLambda関数が指定されています。
cron式のスケジュール実行を設定していますが、指定時刻になってもLambdaは実行されないので、やはりトリガーの設定がうまくいっていないようです。
#解決策
この問題を解決するには、LambdaPermissionも一緒にCloudFormationで作成するようにします。
手動でルールを更新もしくは作成すると、LambdaPermissionが自動的に設定されています。
Former2でLambdaPermissionも一緒に出力します。
これでCloudFormationからスタック作成を行うと、LambdaPermissionも一緒に作成され、トリガーもうまく設定されます。
つまり、今回Former2で出力すべきリソースはLambdaPermissionを加えた以下になります。
#CloudFormationテンプレート例
今回作成したリソースのテンプレートを載せておきます。
構成例ではわかりやすくするために、EventBridgeルールは1つとしましたが、実際には起動用と停止用で2つ作成しています。
また、リソース名等はパラメータを使用し、Lambdaコードは以下を参考にさせていただいております。
https://michimani.net/post/aws-auto-start-stop-ec2/
AWSTemplateFormatVersion: "2010-09-09"
Metadata:
Generator: "former2"
Description: ""
Parameters:
ManagedPolicyName:
Type: String
Default: start-stop-ec2-python-policy
RoleName:
Type: String
Default: StartStopEc2PythonRole
FunctionName:
Type: String
Default: StartStopEc2Lambda
startEventsRuleName:
Type: String
Default: start-ec2-python-rule
startEventsRuleSchedule:
Type: String
Default: cron(0 23 ? * * *)
stopEventsRuleName:
Type: String
Default: stop-ec2-python-rule
stopEventsRuleSchedule:
Type: String
Default: cron(0 11 ? * * *)
Resources:
IAMManagedPolicy:
Type: "AWS::IAM::ManagedPolicy"
Properties:
ManagedPolicyName: !Sub "${ManagedPolicyName}"
Path: "/"
PolicyDocument: |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*"
}
]
}
IAMRole:
Type: "AWS::IAM::Role"
Properties:
Path: "/"
RoleName: !Sub "${RoleName}"
AssumeRolePolicyDocument: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
MaxSessionDuration: 3600
ManagedPolicyArns:
- !Ref IAMManagedPolicy
Description: ""
LambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Description: ""
FunctionName: !Sub "${FunctionName}"
Handler: "index.lambda_handler"
Code:
ZipFile: |
import json
import boto3
import traceback
def lambda_handler(event, context):
try:
region = event['Region']
action = event['Action']
client = boto3.client('ec2', region)
responce = client.describe_instances(Filters=[{'Name': 'tag:AutoStartStop', "Values": ['TRUE']}])
target_instans_ids = []
for reservation in responce['Reservations']:
for instance in reservation['Instances']:
target_instans_ids.append(instance['InstanceId'])
print(target_instans_ids)
if not target_instans_ids:
print('There are no instances subject to automatic start / stop.')
else:
if action == 'start':
client.start_instances(InstanceIds=target_instans_ids)
print('started instances.')
elif action == 'stop':
client.stop_instances(InstanceIds=target_instans_ids)
print('stopped instances.')
else:
print('Invalid action.')
return {
"statusCode": 200,
"message": 'Finished automatic start / stop EC2 instances process. [Region: {}, Action: {}]'.format(event['Region'], event['Action'])
}
except:
print(traceback.format_exc())
return {
"statusCode": 500,
"message": 'An error occured at automatic start / stop EC2 instances process.'
}
MemorySize: 128
Role: !GetAtt IAMRole.Arn
Runtime: "python3.8"
Timeout: 3
TracingConfig:
Mode: "PassThrough"
LambdaPermission:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt LambdaFunction.Arn
Principal: "events.amazonaws.com"
SourceArn: !GetAtt EventsRule.Arn
LambdaPermission2:
Type: "AWS::Lambda::Permission"
Properties:
Action: "lambda:InvokeFunction"
FunctionName: !GetAtt LambdaFunction.Arn
Principal: "events.amazonaws.com"
SourceArn: !GetAtt EventsRule2.Arn
EventsRule:
Type: "AWS::Events::Rule"
Properties:
Name: !Sub "${startEventsRuleName}"
ScheduleExpression: !Sub "${startEventsRuleSchedule}"
State: "ENABLED"
Targets:
-
Arn: !GetAtt LambdaFunction.Arn
Id: !Sub "Id-${startEventsRuleName}"
Input: !Sub "{\"Region\": \"${AWS::Region}\", \"Action\": \"start\"}"
EventBusName: "default"
EventsRule2:
Type: "AWS::Events::Rule"
Properties:
Name: !Sub "${stopEventsRuleName}"
ScheduleExpression: !Sub "${stopEventsRuleSchedule}"
State: "ENABLED"
Targets:
-
Arn: !GetAtt LambdaFunction.Arn
Id: !Sub "Id-${stopEventsRuleName}"
Input: !Sub "{\"Region\": \"${AWS::Region}\", \"Action\": \"stop\"}"
EventBusName: "default"
#まとめ
手動でリソース作成を行った際に自動で付与される権限があったり、各サービスが疎結合になっているが故にテンプレートで作成した際にはエラーにならなかったりと、便利すぎて意識しない設定というのがけっこうありそうです。
手動作成では意識しない設定も、テンプレートでは記述しないといけないことがあるので、CloudFormationはサービスの理解を深めるのにも有効な気がします。