15
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ゆめみAdvent Calendar 2020

Day 4

Serverless Framework で SNS + Lambda の Slack 通知を簡単に作る

Last updated at Posted at 2020-12-04

任意のメッセージを Slack の特定のチャネルに通知する仕組みを AWS SNS + Lambda で作ります。

  • アプリケーションから任意のメッセージを通知したい
  • 複数の通知先がある(Slackとメーリングリストなど)
  • しかし、通知先の設定などはなるべくアプリケーションで管理したくない

こういうときに役に立つと思います

Slack APP の登録とチャネルの Webhook URL の生成(手動)

https://api.slack.com/apps?new_app=1 にアクセスして、 Workspace に Slack APP を作成します

image.png

Incoming Webhooks を選択します

image.png

初期状態で Incoming Webhooks は有効になってないので、 ON にします

image.png

ON にすると Webhook を追加できるようになります
Add New Webhook to Workspace を選択します

image.png

OAuth で権限をリクエストする画面に遷移します
Webhook でメッセージを投稿するさきのチャンネルを選択して許可します

image.png

Webhook URL が生成されました1
これは後ほど、 Serverless Framework で利用します

image.png

Sample curl request も生成されています
指定したチャネルへ Webhook でメッセージ送信できるか、事前にテストしておくこともできます

image.png

$ curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
ok%

image.png

Serverless Framework で SNS + Lambda を作成する

実際にこの手順どおりに実装したリポジトリを参考に置いておきます

Serverless Framework のインストールとプロジェクト作成

Get startedCLI reference を見てもらうのが一番良いですが、必要なステップだけ抜粋しておきます
環境は MacOS/Linux で、言語は python3 にします
他の環境へのインストール方法や他の言語のテンプレートはドキュメントを参照してください

$ curl -o- -L https://slss.io/install | bash
$ sls create --template aws-python3
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v2.6.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

以下のファイルが生成されます

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .gitignore
        new file:   handler.py
        new file:   serverless.yml

handler.py の中身を見てもらえればわかりますが、固定の文字列と Lambda event を返す関数が定義されています

handler.py
import json


def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

    # Use this code if you don't use the http event with the LAMBDA-PROXY
    # integration
    """
    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event
    }
    """

このままデプロイしてみましょう

$ sls deploy -v
Serverless: Packaging service...

...(snip)...

Serverless: Stack update finished...
Service Information
service: sls-sns-lambda-to-slack
stage: dev
region: us-east-1
stack: sls-sns-lambda-to-slack-dev
resources: 6
api keys:
  None
endpoints:
  None
functions:
  hello: sls-sns-lambda-to-slack-dev-hello
layers:
  None

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:000123456789:function:sls-sns-lambda-to-slack-dev-hello:1
ServerlessDeploymentBucketName: sls-sns-lambda-to-slack-serverlessdeploymentbuck-1drc5f5y43luq


**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************

動作確認もしてみます

$ sls invoke -f hello -d "{\"message\": \"This is input message.\"}"
{
    "statusCode": 200,
    "body": "{\"message\": \"Go Serverless v1.0! Your function executed successfully!\", \"input\": {\"message\": \"This is input message.\"}}"
}

ここまでのコミット

Slack 通知する Lambda の実装

handler.py の実装

  • 受け取ったメッセージを Slack API の chat.postMessage payload に変換します
  • payload を先程作成した Webhook に POST すれば良いです
handler.py
import json
import urllib3
import os

http = urllib3.PoolManager()


def invoke(event, context):
    message = event['message']
    payload = json.dumps({'text': message})
    url = os.environ["WEBHOOK_URL"]
    resp = http.request('POST', url, body=payload)
    print({
        "url": url,
        "payload": payload,
        "status_code": resp.status,
        "response": resp.data
    })

    return payload
  • serverless.yml の修正も必要です
    • handler.py のエントリポイントの関数名を hello から invoke に変更したので
  • ついでに Lambda の関数名もいい感じの名前 ( post-to-slack ) に変えておきましょう
serverless.yml
-  hello:
-    handler: handler.hello
+  post-to-slack:
+    handler: handler.invoke

環境変数の設定

  • Webhook を環境変数として渡しますが、リポジトリには commit したくありません 1
  • .gitignore したファイルで読ませることにします
serverless.yml
- #  environment:
- #    variable1: value1
+   environment:
+     WEBHOOK_URL: ${file(./config.json):WEBHOOK_URL}
config.json
{
  "WEBHOOK_URL": "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
}

デプロイと動作確認

  • ここまでの内容をデプロイして動作確認してみましょう
  • Lambda へ渡す payload は {"message": "Slack に投稿されるメッセージ"} という形式です
    • Lambda が event['message'] で Slack に投稿するメッセージを受け取っています
$ sls deploy -v
$ sls invoke -f post-to-slack -d "{\"message\": \"Slack に投稿されるメッセージ\"}"
"{\"text\": \"Slack \\u306b\\u6295\\u7a3f\\u3055\\u308c\\u308b\\u30e1\\u30c3\\u30bb\\u30fc\\u30b8\"}"

image.png

ここまでのコミット

SNS Topic 作成と Lambda Subscription の設定

serverless.yml を少し修正するだけで、 SNS Topic と Lambda Subscription を設定できます

SNS Topic のリソースを定義

serverless.yml
resources:
  Resources:
    snsTopic:
      Type: AWS::SNS::Topic
  Outputs:
    snsTopicArn:
      Description: "ARN of SNS Topic"
      Value: !Ref snsTopic

Lambda Subscription の設定

関数にイベントとして SNS Topic を紐付けます

serverless.yml
  functions:
    post-to-slack:
      handler: handler.invoke
+     events:
+       - sns:
+           arn: !Ref snsTopic
+           topicName: snsTopic

たったこれだけです
デプロイして SNS Topic と Lambda Subscription が作成されたことを確認しましょう

image.png

ここまでのコミット

SNS Topic からのメッセージを処理するよう、 Lambda 関数を修正

  • SNS Topic はできましたが、まだ Lambda 関数が SNS Topic から送られてくる payload を解釈できていません
  • AWS のコンソールなどから SNS Topic にメッセージを発行できますが、これまで使ってきた payload の形式で送ってもうまくいきません

image.png

image.png

[ERROR] KeyError: 'message'
Traceback (most recent call last):
  File "/var/task/handler.py", line 9, in invoke
    message = event['message']
  • SNS から送られてくる payload がどういう形で event に格納されているか、知る必要があります
  • とりあえず、関数の冒頭で print(event) してログを見るとわかりやすいです
handler.py
  def invoke(event, context):
+     print(event)
      message = event['message']

image.png

Records.0.Sns.Message に SNS Topic 経由で送信されたメッセージの内容が格納されていることがわかりました

image.png

Lambda 関数のコードを修正してデプロイしましょう

handler.py
 def invoke(event, context):
-    message = event['message']
+    message = event['Records'][0]['Sns']['Message']
     payload = json.dumps({'text': message})
     url = os.environ["WEBHOOK_URL"]
     resp = http.request('POST', url, body=payload)
  • AWS コンソールから SNS 経由でメッセージを送ってみます
  • JSON payload にする必要がないので、メッセージ本文をそのまま submit します

image.png

届きましたね

image.png

ここまでのコミット

アプリケーションから SNS Topic にメッセージを publish する

  • あとは、アプリケーションから AWS SDK で SNS Topic にメッセージを publish するだけです
  • 例として、 PHP/Laravel 2 で実装するサンプルはこんな感じです
$client = App::make('aws')->createClient('sns');
$client->publish([
    'Message' => 'アプリケーションから送信するメッセージ', // REQUIRED
    'TopicArn' => 'arn:aws:sns:us-east-1:000123456789:sls-sns-lambda-to-slack-dev-snsTopic-1OUEKFS94QV16'
]);

応用編: 通知の環境分離

  • 環境ごとに通知先を分けたいニーズはよくあると思います
  • Serverless Framework は環境の概念を持っていて、環境ごとに独立したリソースをデプロイできます

通知先チャネルごとの Webhook URL の作成

冒頭で実施した Webhook URL 生成と同じ手順で、Slack App に Webhook URL を追加しましょう

image.png

serverless.ymlconfig.json の修正

環境ごとに読み込む config.json を分けることで、環境変数の Webhook URL を切り替えられるようにします

serverless.yml
   environment:
-    WEBHOOK_URL: ${file(./config.json):WEBHOOK_URL}
+    WEBHOOK_URL: ${file(./config.${opt:stage, self:provider.stage, 'dev'}.json):WEBHOOK_URL}
config.*.json
$ ls -l config*
-rw-r--r-- 1 m_kanno AD\Domain Users 103 Dec  4 19:58 config.dev.json
-rw-r--r-- 1 m_kanno AD\Domain Users 103 Dec  4 19:58 config.stg.json

commit したくないファイルの名前が変わるので、 .gitignore の修正も忘れずに

.gitignore
 # config
-config.json
+config.*.json

環境ごとにデプロイする

  • --stage オプションでデプロイ先の環境を指定できます
  • --stage オプションを指定しなかった場合、デフォルトで dev 環境にデプロイされます
    • これまでは dev 環境にデプロイさていました
    • デフォルトの環境は serverless.yml で上書きすることも可能です
serverless.yml
# you can overwrite defaults here
#  stage: dev
#  region: us-east-1
$ sls deploy -v --stage stg

stg 環境の SNS Topic などが作成されました

image.png

メッセージを送ってみます

image.png

別の Webhook URL で指定したチャネルにメッセージが届きました

image.png

ここまでのコミット

リソースの削除

  • 必要なくなったら、 sls remove でリソースを削除できます
  • これも --stage オプションで環境ごとに削除できます
$ sls remove
$ sls remove --stage stg

Slack App は手動で削除してください

まとめ

  • Serverless Framework を使って、Slack 通知する SNS Topic + Lambda を簡単に作れます
    • 慣れればリポジトリ作成から30分くらいでできるようになります
  • Seerverless Framework は関連するリソースや IAM ポリシーもざっと作ってくれるのでアプリケーション開発に集中できます

参考

  1. Webhook URL を公開してしまわないように注意しましょう。 Webhook URL を知っていると、その Webhook に紐付いているチャンネルにメッセージを投稿し放題になってしまいます。 2

  2. サンプルコードは aws/aws-sdk-php-laravel を利用する前提です

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?