SNSからLambdaを呼び出す

SNSでLambdaを呼び出す

SNSからLambdaを呼び出す

以下のページでSNSについて取り上げました。

https://awstut.com/2022/07/30/introduction-to-sns-with-cfn-email

上記のページでは、サブスクライバーとしてメールアドレスを指定しました。

今回はサブスクライバーにLambda関数を指定する方法を確認します。

構築する環境

Diagram of invoking Lambda from SNS

構成は冒頭にご紹介したページと概ね同様です。

Lambda関数2からSNSトピックにメッセージを送信します。
つまりこの関数はパブリッシャーとして動作します。
この関数の働きは、現在日時からUNIX時間を取得し、メッセージとしてこれを送信します。

SNSトピックのサブスクライバーとしてLambda関数1を指定します。
この関数の働きは、メッセージとして渡されたUNIX時間が偶数/奇数か判定します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/blob/main/100/fa-100.yaml

テンプレートファイルのポイント解説

(参考)Lambda関数でSNSにパブリッシュする

Resources:
  Function2:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          REGION: !Ref AWS::Region
          TOPIC: !Ref TopicArn
      Code:
        ZipFile: |
          import boto3
          import datetime
          import os
          import time

          topic = os.environ['TOPIC']
          region = os.environ['REGION']

          client = boto3.client('sns', region_name=region)

          def lambda_handler(event, context):
            now = datetime.datetime.now()
            now_str = now.strftime('%Y-%m-%d %H:%M:%S.%f')
            unix_time = str(time.mktime(now.timetuple()))
            print(now_str, unix_time)

            response = client.publish(
              TopicArn=topic,
              Subject=now_str,
              Message=unix_time
              )
            print(response)
      FunctionName: !Sub "${Prefix}-function-02"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole2.Arn

  FunctionRole2:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: SNSPublishPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sns:Publish
                Resource:
                  - !Ref TopicArn
Code language: YAML (yaml)

Lambda関数からSNSにメッセージをパブリッシュします。
詳細は冒頭でご紹介したページをご確認ください。

なおCloudFormationでLambda関数を作成する方法については、以下のページをご確認ください。

https://awstut.com/2022/02/02/3-patterns-for-creating-lambda-with-cloudformation

今回はインラインで実行するコードを記載します。

SNSサブスクライバーにLambda関数を指定する

Resources:
  Topic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref FunctionArn1
          Protocol: lambda
      TopicName: !Ref Prefix
Code language: YAML (yaml)

Subscriptionプロパティでサブスクライバーを設定します。
今回はLambda関数でSNSメッセージを受け取りますから、Endpointプロパティに関数のARNを、Protocolプロパティに「lambda」を指定します。

サブスクライバーにLambda関数を指定する場合、内部的にはSNSが関数を呼び出すという挙動になります。
そのためSNSに関数を呼び出す権限を与える必要があります。

Resources:
  SNSPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref Function1
      Action: lambda:InvokeFunction
      Principal: sns.amazonaws.com
      SourceArn: !Ref Topic
Code language: YAML (yaml)

以下のAWS公式ページを参考にして設定しました。

https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-subscribe-sns-topic-same-account/

Lambda関数でSNSメッセージを受け取る

Resources:
  Function1:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Code:
        ZipFile: |
          def lambda_handler(event, context):
            subject = event['Records'][0]['Sns']['Subject']
            message = event['Records'][0]['Sns']['Message']
            print(subject, message)

            unix_time = int(float(message))

            if (unix_time % 2) == 0:
              print('{int} is even !'.format(int=unix_time))
            else:
              print('{int} is odd !'.format(int=unix_time))
      FunctionName: !Sub "${Prefix}-function-01"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole1.Arn
Code language: YAML (yaml)

eventオブジェクト内に、SNSから発信されたメッセージが格納されています。
今回は受け取ったUNIX時間の偶数/奇数を判定します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

https://awstut.com/2021/12/02/cloudformation-nested-stacks

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • SNSトピック:fa-100
  • Lambda関数2:fa-100-function-02
  • Lambda関数1:fa-100-function-01

作成されたリソースをAWS Management Consoleから確認します。
SNSトピックを確認します。

Detail of SNS 1.
Detail of SNS 2.

正常にSNSトピックが作成されていることがわかります。
そしてLambda関数1がサブスクライバーとして登録されていることもわかります。

Lambda関数を確認します。

Detail of Lambda 1.
Detail of Lambda 2.

2つの関数も正常に作成されていることがわかります。
関数1に注目すると、SNSからの呼び出しが許可されていることもわかります。

動作確認

1回目

準備が整いましたので、関数2を実行します。
関数2のTestタブから「Test」をクリックして実行します。

Detail of Lambda 3.

関数が正常に実行されたことがわかります。
現在日時からUNIX時間が計算されて、それをSNSメッセージとして発信したことがわかります。
今回のUNIX時間は「1669204672」でした。

続いて関数1のログを確認します。

Detail of Lambda 4.

関数が実行された形跡があります。
つまりパブリッシャーである関数2からSNSメッセージが発行されたことによって、サブスクライバーである関数1が自動的に呼び出されたということです。

UNIX時間の偶数/奇数判定の結果、「1669204672 is even !」が出力されました。

2回目

もう一度、同じ操作を行います。

以下が両関数の実行ログです。

Detail of Lambda 5.
Detail of Lambda 6.

今度は関数2でUNIX時間「1669204701」を持つSNSメッセージが発信されました。
これを受けて自動的に関数1が実行され、奇数と判定されて「1669204701 is odd !」が出力されました。

まとめ

SNSサブスクライバーにLambda関数を指定する方法を確認しました。