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

#構成例
例えばこんな感じでEventBridgeルールをトリガーとするEC2の起動停止LambdaをCloudFormationで作成したいことがあります。
![template1-designer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/0a292962-d3c6-e747-7693-11324bd3c1b3.png)

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

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

すると以下のようになります。
![template1-designer (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/5420fc30-3838-2517-2bc4-df8babdd9244.png)

#問題
上記の構成を改めてCloudFormationのスタックでリソース作成すると。。。
![SnapCrab_NoName_2021-7-21_16-47-3_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/4c87b19c-05ca-1a27-7129-4489f7de7146.png)

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


作成したEventBridgeルールのほうを確認すると、
![SnapCrab_NoName_2021-7-21_16-49-38_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/0851bc6e-4a61-84ba-b419-b6d046688210.png)

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

#解決策
この問題を解決するには、LambdaPermissionも一緒にCloudFormationで作成するようにします。
手動でルールを更新もしくは作成すると、LambdaPermissionが自動的に設定されています。
![SnapCrab_NoName_2021-7-21_17-0-57_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/c5962ee2-7a69-b5bc-0650-cc927befc8e9.png)
![SnapCrab_NoName_2021-7-21_17-1-27_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/d2ad0729-2fe8-c029-23a9-419361812c06.png)
![SnapCrab_NoName_2021-7-21_17-3-27_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/6493cbe8-9467-876e-c6a3-fe78545b6f46.png)

Former2でLambdaPermissionも一緒に出力します。
![SnapCrab_NoName_2021-7-21_17-7-4_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/79e4d998-2058-09d6-8c87-066f230dc407.png)

これでCloudFormationからスタック作成を行うと、LambdaPermissionも一緒に作成され、トリガーもうまく設定されます。
![SnapCrab_NoName_2021-7-21_17-32-21_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/5d1f781d-38fc-c5b9-ec51-e51adde67f66.png)

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

* IAMポリシー
* IAMロール
* Lambda
* EventBridgeルール
* LambdaPermission
![template1-designer (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996764/b124a052-8836-7c2c-e9c1-8aaebb2cb177.png)

#CloudFormationテンプレート例
今回作成したリソースのテンプレートを載せておきます。
構成例ではわかりやすくするために、EventBridgeルールは１つとしましたが、実際には起動用と停止用で２つ作成しています。
また、リソース名等はパラメータを使用し、Lambdaコードは以下を参考にさせていただいております。
https://michimani.net/post/aws-auto-start-stop-ec2/

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