3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

網屋 Tech BlogAdvent Calendar 2024

Day 12

TerraformでLambdaにSlackBotをデプロイしてみる

Last updated at Posted at 2024-12-11

TerraformでLambdaにSlackBotをデプロイしてみる

はじめに

みなさんSlackは使っているでしょうか。
私の職場には先輩方の作った便利なBotがたくさんあります。自分でもこういうのを作れるようになりたい!と思い、まずは簡単なBotを作ってみました。
本記事はその際の手順や知見等をまとめたものです。

Terraformを使う都合、ソースを全部載せるとみづらくなってしまうので、重要なところだけ載せてます。
公開はしてるので↓で全部見られます!
https://github.com/ymat19/SlackHelloWorldBot

アプリ概要

botにメンションすると、メンション付きでHelloが返ってくる。

こんな感じ

Hoge: @Bot
Bot:  Hello @Hoge!

構築手順

アプリの構築手順です。ブラウザ上でのSlackの設定編集と、terraformのデプロイ作業があります。

Slack側の設定

アプリの追加

まず、Slackアプリの設定画面を開きます。
https://api.slack.com/apps/

image.png
Create New Appを選択


image.png
From Scratchを選択


image.png
アプリ名と導入先のワークスペースを選択

トークン発行

Botを動かすには以下の二つのトークンが必要です。その取得手順です。

  • Signing Secret
    • BotがSlackのリクエストを検証するためのもの
  • Bot User OAuth Token
    • SlackがBotのリクエストを検証するためのもの

image.png
アプリ作成が成功するとこんな感じの画面になるので、Signing Secretを控えておいてください。


image.png
サイドバーからOAuth & Permissionsを選択して、少し下にスクロールすると、Bot Token Scopesというのが表示されます。
以下の三つを追加します

  • app_mentions:read
    • 今回はメンションで発火するので必要
  • chat:write
    • HelloWorldを書き込みたいので必要
  • users:read
    • メンション元のユーザに挨拶を返したいので必要(これがないとユーザ名を取れないっぽい)

image.png
先の手順で、権限が付与出来たらInstallボタンを押します。


image.png
Bot User OAuth Tokenが発行されるので、これも控えておきます。

terraformでデプロイ

セットアップ

terraformディレクトリに諸々置いてます。
https://github.com/ymat19/SlackHelloWorldBot/tree/main/terraform

まずterraform/terraform.tfvars.example を参考にvariablesへ、先ほど取得したトークン2種とterraformの実行ロールを設定します。

deploy_role_arn = "arn:aws:iam::..."
slack_bot_token = "xoxb-xxxxxxxxxxxxx..."
slack_signing_secret = "xxx..."

デプロイ

image.png
terraform applyでデプロイするとこんな感じにエンドポイントがoutputされるので、控えておきます。

tfファイルの解説

lambda関連
# create lambda execution role
resource "aws_iam_role" "slack_bot" {
  name = "slack-lambda-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy" "slack_bot" {
  name = "slack-lambda_policy"
  role = aws_iam_role.slack_bot.id
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
        ],
        Resource = "*"
      }
    ]
  })
}

# null_resource to update pip package
resource "null_resource" "slack_bot" {
  triggers = {
    always_run = timestamp()
  }
  provisioner "local-exec" {
    command = "pip install -r ../src/requirements.txt -t ../src/package && cp ../src/lambda_function.py ../src/package"
  }
}

# lambda source data
data "archive_file" "slack_bot" {
  depends_on  = [null_resource.slack_bot]
  type        = "zip"
  source_dir  = "../src/package"
  output_path = "lambda.zip"
}

# create python lambda function
resource "aws_lambda_function" "slack_bot" {
  depends_on       = [data.archive_file.slack_bot]
  function_name    = "slack-bot"
  role             = aws_iam_role.slack_bot.arn
  handler          = "lambda_function.lambda_handler"
  runtime          = "python3.12"
  filename         = data.archive_file.slack_bot.output_path
  source_code_hash = data.archive_file.slack_bot.output_base64sha256
  timeout          = 15
  architectures    = ["arm64"]
  environment {
    variables = {
      SLACK_BOT_TOKEN      = var.slack_bot_token
      SLACK_SIGNING_SECRET = var.slack_signing_secret
    }
  }
}

Lambdaの実行ロール作ったりpythonのコードをアップロードしたりしてます。
SlackBot特有の話があるとしたら、下でトークンを環境変数にセットしてるぐらいです。

    variables = {
      SLACK_BOT_TOKEN      = var.slack_bot_token
      SLACK_SIGNING_SECRET = var.slack_signing_secret
    }
API Gateway関連
resource "aws_apigatewayv2_api" "slack_bot" {
  name          = "slack-bot-api"
  protocol_type = "HTTP"
}

resource "aws_lambda_permission" "slack_bot" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.slack_bot.arn
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_apigatewayv2_api.slack_bot.execution_arn}/*/*"
}

resource "aws_apigatewayv2_integration" "slack_bot" {
  api_id                 = aws_apigatewayv2_api.slack_bot.id
  integration_type       = "AWS_PROXY"
  integration_uri        = aws_lambda_function.slack_bot.invoke_arn
  integration_method     = "POST"
  payload_format_version = "2.0"
}

resource "aws_apigatewayv2_route" "slack_bot" {
  api_id    = aws_apigatewayv2_api.slack_bot.id
  route_key = "POST ${var.end_point_path}"
  target    = "integrations/${aws_apigatewayv2_integration.slack_bot.id}"
}

resource "aws_apigatewayv2_stage" "slack_bot" {
  api_id      = aws_apigatewayv2_api.slack_bot.id
  name        = "$default"
  auto_deploy = true
  default_route_settings {
    throttling_rate_limit  = 1
    throttling_burst_limit = 10
  }
}

SlackがAPI Gatewayを叩けるように公開しています。
SlackがAPI叩く時のグローバルIPの範囲が公開されていたら絞り込み等した方が良い気もしますが、調べた感じなさそうでした。
代わりではないですがDDoS対策でRate制限は厳しめにしてます。

  default_route_settings {
    throttling_rate_limit  = 1
    throttling_burst_limit = 10
  }

Slackにイベント設定

再びSlack側に戻って、イベントを設定します。


image.png
サイドバーからEvent SubScriptionに移って、イベントの通知先と、通知するイベントを設定します。

  • Request URLには先ほどterraform applyで出力されたエンドポイントを入力
    • APIがちゃんと動いてたらVerifiedになるはず
  • Subscrie to bot eventsにapp_mentionを登録
    • 今回はメンションに反応したいだけなのでこれのみです

埋まったら保存します。Save Changesの位置が見落としやすいので注意。

使ってみる

image.png
こんな感じに挨拶が返ってきたら成功です!

補足バックエンドの話

構築手順で触れる機会がありませんでしたが、バックエンドはこんな感じです。

バックエンド(Python)
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
from slack_bolt import App

# Boltアプリのインスタンスを作成
app = App(process_before_response=True)


# メンションされたときのイベントハンドラ
@app.event("app_mention")
def handle_app_mention(event, say):
    user = event["user"]
    say(f"Hello, <@{user}>!")


# Lambdaのハンドラを作成
def lambda_handler(event, context):
    slack_handler = SlackRequestHandler(app)
    return slack_handler.handle(event, context)

ここのオプションがめちゃくちゃ重要で、FaaSで動かすなら必須です。 
https://tools.slack.dev/bolt-python/ja-jp/concepts/lazy-listeners/

app = App(process_before_response=True)

これがないとBOTが動いたり動かなかったりします(多分Lambdaのインスタンスがウォームスタートな時しか成功しない)。
生成AIに書いてもらったソースにこれが抜けていて結構な時間を失いました。まずドキュメント読むの大事:innocent:
ちなみに、トークン周りは既定の環境変数( SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET)が勝手に読まれてるっぽいです。

最後に

ここまで読んでいただきありがとうございました!
とりあえずSlack起点でLambdaを叩ける仕組みができたので、何かしら作って遊んでみたいです。
SlackアプリのフレームワークであるBoltはかなり便利そうで、コマンド対応やGUI追加も割と簡単にできそうで良い感じです。
なんと日本語のチュートリアルもあります。
https://tools.slack.dev/bolt-python/ja-jp/getting-started

参考

【全手順を網羅】AWSを利用したSlackアプリの作り方
タイトル通り網羅的でLambda環境でのBot作成手順を理解することができました。手順だけでなく成果物のBot自体も参考にさせていただいてます。
Amazon BedrockとSlackで生成AIチャットボットアプリを作る (その2:Lambda+API Gatewayで動かす)
こちらの記事のおかげでprocess_before_responseに気づきました。ありがとうございます。

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?