Serverless Framework + SNSでLambdaのエラーをSlackに通知しよう!

記事タイトルとURLをコピーする

はじめに

こんにちは。孔子の80代目子孫兼技術5課の孔です。やっと梅雨が終わりましたね!日光で干した洗濯物の方が気持ちよく着れるので、とても嬉しいですが、ここ3ヶ月間家からほとんど出てないのでそんなに意味なかったな…とダークモードになっています。コロナ終息のために、頑張りましょう!

今日はServerless Framework + SNSというトピックを持ってきました。例えば、CloudWatch Eventsを使ってLambdaを毎日朝9時に走らせるような処理をするとしいて、毎日朝9時にLambdaが正常に起動したかどうか確認しにAWSコンソールを開くのはとても面倒な作業ですね。そこでLambdaが失敗した時にSNSで自動的に通知して、Slackでそのメッセージを受け取るような構成を作れば、毎日確認する作業がなくなってとても楽になります。

そのような構成とサンプルコードを、Serverless Frameworkでデプロイしてみよう!というのが本日のテーマとなります。Serverless FrameworkはAWS上にLambdaを中心にいろいろなAWSリソースをデプロイできるサービスとなります。それでは、実際やってみましょう。

※ Serverless Frameworkを触ったことのある方を対象としているため、基本的な操作については説明を割愛しています。

まずはPythonコード

2つのコードを用意しました。Slackに投稿するためのLambdaコード(notify_to_slack.py)と、SNSにpublishするコード(publisher.py)となります。

# notify_to_slack.py
import urllib3
import json
http = urllib3.PoolManager()
def lambda_handler(event, context):
    url = "https://hooks.slack.com/services/XXXXX...."
    msg = {
        "channel": "#slack_channel",  # Slackのチャンネル名
        "username": "Lambdaからのお知らせ",  # bot名
        "text": event['Records'][0]['Sns']['Message'],
        "icon_emoji": "emoji"  # botのアイコン
    }

    encoded_msg = json.dumps(msg).encode('utf-8')
    resp = http.request('POST', url, body=encoded_msg)
    return {
        "message": event['Records'][0]['Sns']['Message'],
        "status_code": resp.status,
        "response": resp.data
    }

urlの中には、SlackのWebhookを入力してください。msgの中身のtext以外のものも適切なものに変えてください。

# publisher.py
import boto3
from os import getenv

def lambda_handler(event, context):
    try:
        raise Exception('エラー発生!')
    except:
        client = boto3.client('sns')

        TOPIC_ARN = getenv('SNS_TOPIC')
        msg = 'テストです'
        subject = 'てすてす'

    response = client.publish(
        TopicArn=TOPIC_ARN,
        Message=msg,
        Subject=subject
    )

    return response

こちらのコードはclientにSNSを指定し、publishというAPIを使っています。publish APIは名前通りSNSのTopicにメッセージをpublishするAPIとなります。

これで、コードの準備は完了です。それではServerless FrameworkでSNSおよびLambda関数をデプロイしてみましょう!

serverless.ymlを記入する

serverless.ymlの中身は以下となります

service: sns-test

provider:
  name: aws
  runtime: python3.8
  stage: dev
  region: ap-northeast-1

functions:
  publisher:
    handler: publisher.lambda_handler
    environment:
      SNS_TOPIC: !Ref FailureTopic
    role: publishToSns
  notify_to_slack:
    handler: notify_to_slack.lambda_handler
    events:
      - sns: 
          arn: !Ref FailureTopic
          topicName: FailureTopic

resources:
  Resources:
    FailureTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: FailureTopic
    publishToSns:
      Type: AWS::IAM::Role
      Properties:
        RoleName: PublishToSNSRole
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole
        Policies:
          - PolicyName: PublishToSNSPolicy
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - sns:*
                  Resource:
                    - !Ref FailureTopic

まずfunctionsで先ほど作成した2つのコードをLambdaにデプロイしています。notify_to_slackの方で、eventsの中にSNSを指定していますが、こちらがLambdaのトリガーイベントとなります。

f:id:swx-kong:20200804174339p:plain

トリガーとして例えばAPIGWやS3イベントがあると思いますが、そのようなトリガーの設定をこちらのeventsから設定します。

また、resourcesの中でSNSトピックを作成しています。Serverless Frameworkを使ってリソースをデプロイする際にはこちらのresources項目を使います。このように作成したSNSトピックをnotify_to_slackのSNSイベント項目に!Ref FailureTopicとして指定することで、Serverless FrameworkがデプロイしたSNSトピックをデプロイされるLambdaが参照できるようになります。

デプロイしてからpublisher.pyをテストなどで起動してみると、無事Slackに指定したチャンネルにメッセージが飛んでくることが確認できるかと思います。

最後に

Serverless Frameworkはとても便利なデプロイツールですのでどんどん使ってみましょう!特に今回触れたeventsは、イベントドリブンで発火することの多いLambdaにとってとても高頻度で使用される機能ですので、覚えておくと役に立つことが多いかと思います。