LoginSignup
2
1

More than 1 year has passed since last update.

CloudFormationでLambdaにトリガーを設定するときはLambdaPermissionに注意する

Posted at

構成例

例えばこんな感じでEventBridgeルールをトリガーとするEC2の起動停止LambdaをCloudFormationで作成したいことがあります。
template1-designer.png

手動で上記の構成のリソースを作成した後、Former2でCloudFormatinテンプレートを出力します。
Lambdaの実行に必要なIAMロールも一緒に作成したいので、Former2で以下のリソースを選択し、出力します。

  • IAMポリシー
  • IAMロール
  • Lambda
  • EventBridgeルール

すると以下のようになります。
template1-designer (1).png

問題

上記の構成を改めてCloudFormationのスタックでリソース作成すると。。。
SnapCrab_NoName_2021-7-21_16-47-3_No-00.png

Lambdaの画面からトリガーが確認できません。

作成したEventBridgeルールのほうを確認すると、
SnapCrab_NoName_2021-7-21_16-49-38_No-00.png

ターゲットにはLambda関数が指定されています。
cron式のスケジュール実行を設定していますが、指定時刻になってもLambdaは実行されないので、やはりトリガーの設定がうまくいっていないようです。

解決策

この問題を解決するには、LambdaPermissionも一緒にCloudFormationで作成するようにします。
手動でルールを更新もしくは作成すると、LambdaPermissionが自動的に設定されています。
SnapCrab_NoName_2021-7-21_17-0-57_No-00.png
SnapCrab_NoName_2021-7-21_17-1-27_No-00.png
SnapCrab_NoName_2021-7-21_17-3-27_No-00.png

Former2でLambdaPermissionも一緒に出力します。
SnapCrab_NoName_2021-7-21_17-7-4_No-00.png

これでCloudFormationからスタック作成を行うと、LambdaPermissionも一緒に作成され、トリガーもうまく設定されます。
SnapCrab_NoName_2021-7-21_17-32-21_No-00.png

つまり、今回Former2で出力すべきリソースはLambdaPermissionを加えた以下になります。

  • IAMポリシー
  • IAMロール
  • Lambda
  • EventBridgeルール
  • LambdaPermission template1-designer (2).png

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はサービスの理解を深めるのにも有効な気がします。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1