いまさらSlackとLambdaとDynamoを連携させてみた


いまさらですが、LambdaとDynamoを触ってみたのでまとめておきたいと思います。
APIGateway/Lambda/Dynamoなど触ったことのない人の助けになれば幸いです。

今回やってみたことは以下です

1. SlackのAppにメッセージを送信
2. Slackのevent apiでAPI Gatewayのエンドポイントにリクエストを送る
3. Lambdaで、飛んできたリクエスト内容をDynamoDBに保存
4. Lambdaで登録した旨をSlackに返信

作成手順

以下の順序で作成を行います

  1. Lambda関数の作成
  2. API Gatewayでエンドポイントを作成
  3. Slackのevent APIの設定と、メッセージの送信
  4. CloudWatchでログを確認する
  5. Lambdaでのメッセージ処理
  6. LambdaからDynamoにデータを登録

1. Lambda関数の作成

Lambdaとは

AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。Lambda を使用すれば、実質どのようなタイプのアプリケーションやバックエンドサービスでも管理を必要とせずに実行できます。

凄く雑な説明をすれば、Lambda上にスクリプトを置いておくと、
こちらが指定したトリガー(HTTPリクエストなど)のタイミングでそのスクリプトを実行してくれます。
スクリプトを置いておくサーバーは意識する必要はなく、課金は実行回数によって発生します。
月100万リクエストまで無料なので、遊びで使う分には(多分)お金は発生しません。

関数の作成

AWSのマネジメントコンソールからLambdaを検索します。
「関数の作成」というボタンがあるはずなので、クリックして作成しましょう。

こういう画面になるので、関数名ランタイムの欄を入れて関数の作成をしましょう。
他に詳細設定などがありますが、今回は大枠を理解するだけなので、パスします。

作成後の画面に関数コードという画面があればOKです。

関数のテスト

Testというボタンがあるので、イベント名に適当な名前を入れて作成
再度Testボタンを教えてみましょう。
画面に以下のようなテスト結果が出ればOKです!

2. API Gatewayでエンドポイントを作成

API Gatewayの設定

非常に簡素ですが、Lambdaの関数は作成できました。
次はLambda関数の実行を外からできるように、API Gatewayを設定します。

Lambdaの関数の画面から、デザイナ>トリガーを追加 をクリック

すると、トリガーを追加という画面がでてくるので、以下のように設定してください。

大枠を理解するため、また後で消すのでオープンにしていますが、そうでない場合はきちんと設定してください。
作成後にAPI Gatewayの項目にslack_sample_APIができていればOKです。

エンドポイントの作成

API Gatewayのリンクをクリックし、設定画面に行きます。

アクションからメソッドの作成を行います。
POSTを選択した後、以下のように選択して、保存します

作成後、再度アクションボタンからAPIのデプロイを実行します。
デプロイされるステージをdefaultを選択肢、デプロイします。

このようなURLが表示されていればエンドポイントの作成は完了です。
とりあえず、テストでcurlを叩いてみましょう!
以下のように返ってきたら成功です!!

curl -X POST https://それぞれのURL/default/slack_sample
=> {"statusCode":200,"body":"\"Hello from Lambda!\""}%

3. Slackのevent APIの設定と、メッセージの送信

Slackのアプリ作成

こちらからAppの作成
名前を適当に入れ、workspaceを設定します。

request urlと、Lambada関数の修正

サイドバーのBasic Informationから
Add features and functionality>Event Subscriptionsを選択してください。
requestを送るURLを求められるので、先程作成したエンドポイントのURLを入れましょう。

すると、以下のように怒られます。
どうやらchallengeというパラメータを返す必要があるようです

Lambda関数のメソッドを、以下のように修正した後、Deployボタンを押しましょう。

def lambda_handler(event:, context:)
  # event["challenge"]がどこから来たかは、後述します
  { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end

デプロイ後にSlack画面のretryボタンを押すと、Request URL Verified と表示されるはずです

Subscribe to bot events

botの権限を設定します。
今回はBotへのメンションをトリガーにrequestを送信。Botぽくそれに返信をしたいので

app_mentions:readchat:writeを入れておきます

workspaceへのインストール

再び、Basic Informationに戻り、
今度はInstall your appからworkspaceにアプリをインストールします

Slackでメッセージを送る

Bot的に使いたいので、適当なchannelにbotを入れて、
メンションをつけてメッセージを送ってみましょう

4. CloudWatchでログを確認する

実はLambda関数を作成したタイミングで

のロググループが出来ています。
AWSでCloudWatchを検索し、
サイドバーのログ>ロググループ
から/aws/lambda/slack_sampleを見てみましょう。

ログストリームの項目に、先程メンションを送った時間のものが増えていれば、
SlackからAPI Gatewayを介してLambda関数が実行出来ています!!

5. Lambdaでのメッセージ処理

Botぽく反応させたいのでSlackのスレッドに返信するようにしましょう。
とりあえず、メンション付きで「hoge」と送ったら、
「hogeと受信したよ!」とでも返すようにします。

Slackからのeventを見る

slackからのメッセージをLambda上で処理したいので、requestがどんな形か知りたいです。
lambda関数を以下のように修正して

def lambda_handler(event:, context:)
    puts event
    { statusCode: 200, body: JSON.generate('Hello from Lambda!'), challenge: event["challenge"] }
end

デプロイしたあとに、
再度、slackでbotにメンション付きでメッセージを送りましょう。
CloudWatchのログに以下のようなものが出ていればOKです。

{
  "token"=>"hoge", 
  "team_id"=>"foo", 
  "api_app_id"=>"fuga", 
  "event"=>{
    "client_msg_id"=>"1234", 
    "type"=>"app_mention", 
    "text"=>"<@id> hoge", 
    "ts"=>"1614412451.000800",
  ~~~~~略~~~~
} 

スレッドに返信する

Token

返信するためにSlackのTokenが必要です。
Slackアプリ画面のサイバーの
Features>OAuth&PermissionsからBot User OAuth Tokenをメモしておきます。

コードの修正

Lambda関数を

と、先程のCloudWatchのログを参考に、以下のように書き換えます。

# サンプルなので、ここに書いていますが本当は適切に暗号化する必要があります
SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")

  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ!",
      thread_ts: event_data["ts"]
    }

  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end

これで、Slack API → API Gateway → Lambda →Slackの流れができました!

7. LambdaからDynamoにデータを登録

最後に、なんとなくDynamoにSlackのメッセージを入れてみましょう!

Dynamoとは

KeyValue型のNoSQLです。集計などには向きませんが、1件のデータの登録、抽出に優れています。

テーブルの作成

Dynamoの画面からテーブルの作成をクリック
テーブル名とプライマリーキーを入れます。
他の設定はデフォルトでOKです。

テーブルが作成できたら、項目の作成から増やしてみます。
今回はtextを増やしてみました。

Lambdaの設定変更

このままだとLambdaからDynamoにまだアクセスできないのでポリシーを追加します
Lambda関数の画面のアクセス権限のタブから実行ロールのロール名をクリック
IAMの画面からポリシーをアタッチします。

自分しか使わないアカウントなのでFullAccessですが、本当は良くないです。

Lambda関数の修正

後は、

を参考に、
Lambda関数を以下のように修正します

require 'json'
require "net/http"
require 'aws-sdk-dynamodb'

SLACK_TOKEN = "token"
def lambda_handler(event:, context:)
  event_data = event["event"]
  # メッセージにメンションがついてしまうため、送信されたテキストからIDを削除
  text = event_data.dig("text").delete("<@id>")

  # Dynamoへのinput
  dynamodb = Aws::DynamoDB::Client.new(region: 'ap-northeast-1')
  item = {
    text: text,
    sender_id: event_data["user"],
  }
  params = {
    table_name: 'dynamo_table_name',
    item: item
  }
  dynamodb.put_item(params)

  # slackへの送信
  channel = event_data.dig("channel")
  params = 
    {
      token: SLACK_TOKEN,
      channel: channel,
      as_user: true,
      text: "#{text}と受信したよ!",
      thread_ts: event_data["ts"]
    }

  uri = URI.parse("https://slack.com/api/chat.postMessage")
  Net::HTTP.post_form(uri, params)
  { statusCode: 200, body: "test", challenge: event["challenge"] }
end

ここまでうまく行っていれば、このように
Dynamoにレコードが登録され、

Slack上でも返信が来ていると思います。

とりあえずしたかったことは出来たので、完了です。

最後に

やっぱり、インフラが分かって無くても、AWSがいい感じにしてくれるのはすごいですね。
工程も簡単で、この記事を書きながらしても3時間ぐらいで終わりました。
多分、作業だけすれば1時間ぐらいで終わると思います。

大枠を理解するために雑な紹介でしたが、
IAMの設定や、API Gatewayのアクセス制限、Lambda関数が失敗したときの処理など
本当はしないといけないことが、実はまだまだあります。
今回の記事で興味を持ってくれた人がいたら、ぜひやってみください。
「LambdaやDynamo全然わからないけど、なんか触ってみたい!」という人に役立てばば幸いです。