AWS SAM CLIでSNSからLambdaを起動する(Python 3.8)、S3からSNSに通知する

[最終更新] 2020年10月19日

AWS SAM CLIを使って、 SNS -> Lambda (Python3.8) 起動できるようなテンプレートのメモ。ついでに、S3のイベントでSNSに通知するような場合についても記述する。

スポンサーリンク

環境

  • AWS SAM CLI 1.6.2
  • LambdaのRuntimeはPython 3.8

SNS -> Lambda

node12.xだとSNS起動のテンプレートが用意されているのだが、Pythonでは用意されていない。まぁだいたい同じなので、node12.xのテンプレートをPython用にちょっとアレンジして使う。

ディレクトリ構造

まずディレクトリ構造は以下。

.
├── hello_world_function
│   ├── hello_world
│   │   ├── __init__.py
│   │   └── app.py
│   └── requirements.txt
├── samconfig.toml
└── template.yaml

app.py

実行される関数。ここではeventをログに出すだけのものとする。

def lambda_handler(event, context):
    print(event)

__init__.py

空ファイル

requrements.txt

sam cli使っていて本当に楽だなぁと思うのが、ここで手軽にライブラリを導入できることだ。まぁでも今回は空ファイルでよい。

template.yaml

テンプレートだと.ymlなのだが、個人的には.yamlのほうが馴染むんだが、一般的にはどうなんだろうか。

node12.xのテンプレートをちょっと変えたものを以下に。あえてコメントアウトは残している。参照に使えるので。

# This is the SAM template that represents the architecture of your serverless application
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-template-basics.html

# The AWSTemplateFormatVersion identifies the capabilities of the template
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/format-version-structure.html
AWSTemplateFormatVersion: 2010-09-09
Description: >-
  sam-app

# Transform section specifies one or more macros that AWS CloudFormation uses to process your template
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html
Transform:
- AWS::Serverless-2016-10-31

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

# Resources declares the AWS resources that you want to include in the stack
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html
Resources:
  # This is an SNS Topic with all default configuration properties. To learn more about the available options, see
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html
  SimpleTopic:
    Type: AWS::SNS::Topic
  
  # This is the Lambda function definition associated with the source code: sns-payload-logger.js. For all available properties, see
  # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
  SNSPayloadLogger:
    Type: AWS::Serverless::Function
    Properties:
      Description: A Lambda function that logs the payload of messages sent to an associated SNS topic.
      Runtime: python3.8
      CodeUri: hello_world_function
      Handler: hello_world/app.lambda_handler
      # This property associates this Lambda function with the SNS topic defined above, so that whenever the topic
      # receives a message, the Lambda function is invoked
      Events:
        SNSTopicEvent:
          Type: SNS
          Properties:
            Topic: !Ref SimpleTopic
      MemorySize: 128
      Timeout: 100
      Policies:
        # Give Lambda basic execution Permission to the helloFromLambda
        - AWSLambdaBasicExecutionRole

これでsam build; sam deploy –guided で samconfig.toml を作れば出来上がり。早い。

S3からSNSに通知する

せっかくなので、 S3 -> SNS -> Lambda まで組んでみる。

SAM側

S3のイベントでSNSのトピックに通知する時などは、SNSのアクセスポリシーで当該のバケットからのアクセスを許可する必要がある(S3 の put event を sns に流そうとした時の Permissions error の解決方法 – Qiita)。

ということで、トピックポリシーをAWS::SNS::TopicPolicyでする必要がある。Resrouces:の下に以下を挿入する。ここでは、既存のバケットを使うものとする(SAMでS3指定するには、バケット生成からやらないといけないから…)。なおS3のバケット名は、バケットポリシーから確認できる。

  SimpleTopicPolicy:
    Type: AWS::SNS::TopicPolicy
    Properties: 
      PolicyDocument:
        Version: "2008-10-17"
        Id: "from_s3_policy_id"
        Statement:
          -
            Sid: SendToSns
            Effect: Allow
            Principal:
              AWS: "*"
            Action:
              SNS:Publish
            Resource: !Ref SimpleTopic
            Condition:
              ArnLike:
                aws:SourceArn: arn:aws:s3:::バケット名
      Topics: 
        - !Ref SimpleTopic

ちなみにArnLikeでバケットを指定する時は、ワイルドカード(*)で一括指定もできる。

ここまでエラーとめっちゃ戦った。めっちゃエラー出た。どうも最初、Actionで色々指定したのがだめくさかった気がするが……。

S3の設定

今回S3は既存のバケットを使っているので、Webコンソールからバケットの設定をする。

当該バケットから「プロパティ」→「イベント」→SNSトピックとか設定して保存。

おまけ: Lambdaのeventの中身

S3 -> SNS -> Lambda の時

ちなみにこれでLambdaが受け取る内容は以下みたいな感じ。これはファイル生成した時。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "TopicのARNになんか加えたやつが入る",
      "Sns": {
        "Type": "Notification",
        "MessageId": "9e9a2471-37df-5726-b997-738c618e37d1",
        "TopicArn": "TopicのARNが入る",
        "Subject": "Amazon S3 Notification",
        "Message": "<serialized-json>"
        "Timestamp": "2020-10-11T18:50:57.486Z",
        "SignatureVersion": "1",
        "Signature": "BASE64っぽい",
        "SigningCertUrl": "xxxxpem",
        "UnsubscribeUrl": "xxxx",
        "MessageAttributes": {}
      }
    }
  ]
}

Sns.Message部分はシリアライズされたjsonで、戻すと以下。

{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "ap-northeast-1",
      "eventTime": "2020-10-11T18:50:51.353Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "なんかID"
      },
      "requestParameters": {
        "sourceIPAddress": "xx.xx.xx.xx"
      },
      "responseElements": {
        "x-amz-request-id": "xxxx",
        "x-amz-id-2": "xxxx"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "xxxx",
        "bucket": {
          "name": "バケット名",
          "ownerIdentity": {
            "principalId": "xxxx"
          },
          "arn": "バケットのARN"
        },
        "object": {
          "key": "ファイル名",
          "size": 844,
          "eTag": "xxxx",
          "sequencer": "xxxx"
        }
      }
    }
  ]
}

ファイル名しかわからんね。まぁ仕方ない。

CloudWatch(Alarm) -> SNS -> Lambda の時

今回関係ないけどよく使うので。

API Gatewayで4xxエラーが出た時にSNSにalarmを飛ばしている。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "xxxx",
      "Sns": {
        "Type": "Notification",
        "MessageId": "xxxx",
        "TopicArn": "xxxx",
        "Subject": "ALARM: \"xxxx\" in Asia Pacific Tokyo",
        "Message": "serialized-json"
        "Timestamp": "2020-10-12T04:50:23.043Z",
        "SignatureVersion": "1",
        "Signature": "xxxx",
        "SigningCertUrl": "xxxx",
        "UnsubscribeUrl": "xxxx",
        "MessageAttributes": {}
      }
    }
  ]
}

Message部分をjson.loads()するとこんな感じ。

{
  'AlarmName': 'xxxx',
  'AlarmDescription': None,
  'AWSAccountId': 'xxxx',
  'NewStateValue': 'ALARM',
  'NewStateReason': 'xxxx',
  'StateChangeTime': '2020-10-12T04:50:22.996+0000',
  'Region': 'Asia Pacific Tokyo',
  'AlarmArn': 'xxxx',
  'OldStateValue': 'OK',
  'Trigger': {
    'MetricName': '4XXError',
    'Namespace': 'AWS/ApiGateway',
    'StatisticType': 'Statistic',
    'Statistic': 'AVERAGE',
    'Unit': None,
    'Dimensions':
      [
        {
          'value': 'xxxx',
          'name': 'xxxx'
        }
      ],
    'Period': 300,
    'EvaluationPeriods': 1,
    'ComparisonOperator': 'GreaterThanThreshold',
    'Threshold': 0.03,
    'TreatMissingData': 'xxxx'
    'EvaluateLowSampleCountPercentile': ''
  }
}

参考

ありがとうございました。

関連コンテンツ

関連記事

スポンサーリンク

カテゴリーaws

コメントを残す

メールアドレスが公開されることはありません。