BLOG

Incoming Webhook を使った Slack へ通知するシステムを AWS SAM で作ってみた

Created at:
Category: AWS

今回は AWS と Slack を連携させて、 Slack にメッセージ (通知) を送るシステムを作ってみました。

AWS SAM (Serverless Application Model) でサーバーレスアプリケーションの構築を行っています。

Incoming Webhook URL を Slack に設定することで、Webhook URL 宛に Python のライブラリである slackweb の機能を使って通知を送る仕組みです。

開発環境

  • AWS CLI : aws-cli/1.18.39 Python/3.7.5 Linux/5.3.0-45-generic botocore/1.15.39
  • AWS SAM : 0.47.0
  • Python : 3.7

Incoming Webhook の設定

次のリンク先を参考に設定を行いました。SlackのIncoming Webhooksを使い倒す

Slack の App ディレクトリで Incoming Webhook と検索して、 Slack に追加します。

通知を投稿する Slack チャンネルを選び、Incoming Webhook インテグレーションの追加をします。

表示される Webhook URL をメモします。この URL に対して メッセージを POST すると Slack のチャンネルに投稿することができます。

設定完了後、「設定を保存する」をクリックして閉じます。

AWS SAM CLI の準備

SAM ベースのアプリケーションの構築を開始するには、AWS SAM CLI を使用します。

SAM CLI を使うことで、SAM テンプレートで定義されたアプリケーションの構築、テスト、デバッグをローカルで実行できます。

インストール

AWS SAM CLI をインストールするためには Docker および AWS CLI のインストールが必要です。

AWS の Developer Guide を参考にインストールしてください。

インストール完了後、確認を行います。

$ sam --version
SAM CLI, version 0.47.0

初期設定

IAM ユーザーの Access Key ID などの設定を行います。

Secret Access Key を忘れてしまった場合、再度異なる Secret Key を作成しなければなりません。 (Secret Key は新規作成時のみ表示されるため、後で再取得できません。)

また、リージョンは ap-northeast-1 (東京) に設定しています。

$ aws configure
AWS Access Key ID [None]: ****
AWS Secret Access Key [None]: ****
Default region name [None]: ap-northeast-1
Default output format [None]: json

プロジェクトフォルダの作成

AWS SAM でフォルダを作成します。

アプリケーションの Lambda ランタイムと生成されるフォルダのプロジェクト名を指定します。

sam init --runtime python3.7 --name AWS_notify

使うテンプレートを選択します。1 を選びました。

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

次も 1 を選びます。

AWS quick start application templates:
	1 - Hello World Example
	2 - EventBridge Hello World
	3 - EventBridge App from scratch (100+ Event Schemas)
Template selection: 1

template ファイル

作成したプロジェクトフォルダの中に template.yaml があります。

この template ファイルのコードは次のサイトを参考に作成しました。

AWSサービス毎の請求額を毎日Slackに通知してみた | Developers.IO

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  AWS_notify
  Notify Slack to AWS   
Globals:
  Function:
    Timeout: 3

Resources:
  AWSIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "NotifySlackLambdaPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "*"

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: function/
      Handler: notify_awsToslack.lambda_handler
      Runtime: python3.7
      Role: !GetAtt AWSIamRole.Arn
      Events:
        NotifySlack:
          Type: Schedule
          Properties:
            Schedule: cron(0 12 ? * SAT *) # JST (日本標準時) 毎週土曜日の 21:00 に通知

Outputs:
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt AWSIamRole.Arn

Cron を使ったスケジュール式

template ファイル内で、スケジュール式を使って通知時間を指定します。

このとき、UTC (協定世界時) と JST (日本標準時) の時差が 9 時間あることに注意が必要です。

cron を使った時間指定の書き方は次のリンクを参考にしてください。

Rate または Cron を使用したスケジュール式 - AWS Lambda

Lambda 関数

app.py ファイルは次のようになります。

単語リストの中からランダムに単語を選び、 Slack の指定した channel に通知しています。

Slack に slackweb を使って通知メッセージを送ります。

今回は使っていませんが、slackweb の attachments 引数を指定することでメッセージのフォーマットを変えることができます。

タイトルを追加したり、マークダウン記法の使用もできるので大変便利です。

#coding: UTF-8

import random

import slackweb

WEBHOOK_URL = "https://hooks.slack.com/services/xxxxxxxxxxxxx"

CHA_WARDS = ["風邪ひくなよ", "風呂入れよ", "宿題やれよ", "歯磨けよ"]

def lambda_handler(event, context):
    slack = slackweb.Slack(url=WEBHOOK_URL)
    slack.notify(text=random.choice(CHA_WARDS), channel='#slack-channel')

S3 bucket の作成

コードを格納するための s3 バケットを作成します。

バケット名は任意の名前を指定して下さい。

aws s3 mb s3://gnk327-lambda-bucket

Build

cd で作成したプロジェクトフォルダへ移動し、次のコマンドを実行して build します。

sam build

s3 bucket への upload

作成した s3 バケット名を指定して、次のコマンドを実行します。

packaged.yaml が作成されていれば成功です。

※sam deploy –guided コマンドを利用することで sam package 省略可能 sam deploy が sam package の機能を暗黙的に実行するようになりました。sam deploy コマンドを直接使用して、アプリケーションをパッケージ化およびデプロイできます。

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket gnk327-lambda-bucket

deploy

最後に deploy を行います。

sam deploy \
    --template-file packaged.yaml \
    --stack-name Notifyaws \
    --capabilities CAPABILITY_IAM \

エラー

deploy の完了後、通知時間になっても Slack へ通知が来ませんでした。

原因を探るため、CloudWatch ー> ロググループ よりログを見てみると次のエラーが発生していました。

[ERROR] Runtime.ImportModuleError: Unable to import module 'app': No module named 'slackweb'

調べてみると、 Lambda で slackweb を使うためにはLayer に設定する作業が必要みたいです。

Lambda に実装されていないライブラリ (numpy や pandas) は、Layer に設定することで Lambda で使えるようになります。

slackweb を Layer へ設定

Lambda の Layer 機能の使い方は、次のリンク先を参考にしました。 Lambdaの新機能Layerの使い方 | ハックノート

slackweb を指定したディレクトリにインストールし、zip ファイルを作成します。

mkdir python
cd python
pip install -t ./python
zip -r slackweb.zip ./*

AWS コンソール画面 ー> Lambda ー> Layer より、レイヤーの作成を行います。作成した zip ファイルをアップロードします。

作成した Layer を関数に追加します。Lambda ー> 関数 -> 設定 ー> Designer で関数名の下にある Layers クリックします。下にレイヤーが表示されるので、レイヤーの追加ボタンが出てくるのでクリックします。

先ほど作成したレイヤーを選び、追加します。

関数の画面に戻ると、レイヤーが追加されて Layers (1) になっていることが確認できます。

Slack への通知

無事に Slack へ通知が来ました。下の画像ではテストのため、通知時間を 16:00 に設定しています。

インストール

今回作成した プロジェクトフォルダは GitHub に公開しました。

https://github.com/dmiyabe/Notify-to-Slack

また、Lambda 関数と template のコードは以下のリンク先で公開しています。

※ソースコードは自己責任でご使用下さい。

参考文献

この記事は以下のサイトを参考にしています。