CloudFormationでCloudWatchEvents→Lambdaの環境を作るときに気をつけたいこと

CloudWatchEventsルールでLambdaを動かす環境をCloudFormationで作っていました。

その際、Lambdaが動かずハマってしまったのでその備忘録です。

やりたかったこと

CloudWatchEventsでcron式を定義して、日次でLambdaを動かしたい! 作りたかったものは、ただこれだけのシンプルな構成です。

CloudFormationテンプレートで書くとこんな感じです。(※IAM Roleは適切に設定されているものとしてください)

---
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  RoleName:
    Type: String
    Default: SampleLambdaRole

Resources:
  # まずはLambdaを定義
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          # -- coding: utf-8 --

          import logging
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)

          def lambda_handler(event, context):
            logger.info("Event: " + str(event))
      Description: LambdaSample
      FunctionName: LambdaSample
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${RoleName}'
      Runtime: python3.6
      Timeout: 3
  # 次にCloudWatchEventsのルールを定義
  Rule:
    Type: 'AWS::Events::Rule'
    Properties:
      Description: sample-rule
      Name: everyday
      ScheduleExpression: 'cron(0 1 * * ? *)'
      State: ENABLED
      Targets:
        - Arn: !GetAtt Lambda.Arn
          Id: lambda
  # 最後にCloudWatchEventsからLambdaの実行を許可する
  LambdaEvent:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !Ref Lambda
      Principal: 'events.amazonaws.com'
      SourceAccount: !Ref AWS::AccountId
      SourceArn: !GetAtt Rule.Arn

一見すると正しそうなのに、時間になってもLambdaが実行されていない!なぜ!?

ってことが起きました。

Lambdaのテストをしてみる

まずはLambda単独で動くかテストをしてみました。

Lambda単独では動きそうです。 と、いうことはCloudWatchEventsの設定がおかしい?cron式が間違ってるとか?と、もやもやしながら次へ。

マネジメントコンソールから同じ環境を作ってみる

Lambdaを作って。

ソースを適当にコピペして。

動作確認用に毎分発火するCloudWatchEventsのルールを作って。

Lambdaのトリガーに設定して。

同じ環境を作ったつもりなのに普通に動いてる。なぜ???

公式ドキュメントを見てみる

何が起きているのか全然良くわからないのでググります。 CloudWatchEventsのトラブルシューティングに、今の状況にぴったりあいそうな記述がありました。

CloudWatch イベント のトラブルシューティング | ルールはトリガーされたが、Lambda 関数が呼び出されなかった

その中をよーく見るとちらっと書いてありました。

Lambda 関数がトリガーに失敗するもう 1 つの理由は、get-policy の実行中に表示されるポリシーに、SourceAccount フィールドが含まれている場合です。SourceAccount 設定により、CloudWatch イベントは関数を呼び出すことができなくなります。

そういえばCloudFormationテンプレートの中でSourceAccountを設定した気がする…。

関数のポリシーを比較する

実際そうなのかマネジメントコンソールで作ったLambdaの関数のポリシーを確認してみます。 ちなみに関数のポリシーはここから確認できます。

見てみると確かに、マネジメントコンソールで作られたLambdaでは、ポリシーにSoruceAccountが含まれていなさそうです。

CloudFormationテンプレートからSourceAccountの記述を消して、発火しないLambdaの問題は無事に解決いたしました。

まとめ

CloudWatchEventsからLambdaを実行したいときは、関数のポリシー(AWS::Lambda::Permission)にSourceAccountを設定してはいけない。

ということだけ、覚えて帰っていただければ幸いです。