SAMっと(サクッと)Slack botを作っちゃおう
読者のみなさん、こんにちは!
今日はAWS SAMを用いてSlack botを作成する方法について紹介したいと思います。
AWS SAMとは
AWS Serverless Application Model (AWS SAM) は、Infrastructure as Code (IaC) を使用してサーバーレスアプリケーションを構築するためのオープンソースフレームワークです。 AWS SAMの短縮構文を使用すると、デベロッパーはデプロイ中にインフラストラクチャに変換されるAWS CloudFormationリソースと特殊なサーバーレスリソースを宣言します。このフレームワークには、 AWS SAMCLIと AWS SAM プロジェクトの 2 つの主要コンポーネントが含まれています。 AWS SAM プロジェクトは、 の実行時に作成されるアプリケーションプロジェクトディレクトリですsam init。 AWS SAM プロジェクトには、 AWS SAM テンプレート仕様 (リソースの宣言に使用する短縮構文) を含む テンプレートのようなファイルが含まれています。
上記は公式ドキュメントからの引用です。
- CloudFormationとそのテンプレートを利用して、宣言的にサーバーレスアプリケーションを定義できる
- CLIを通じて、プロジェクトの初期化からビルド、デプロイまで行える
という辺りがポイントです。
AWS上でサーバーレスアプリケーションを構築する場合、Lambdaを用いることが多いと思います。
Lambdaはコードの管理やデプロイが悩みの種になることが多いため、これらを解決できることは大きいです。
Slack botをサーバーレスアプリケーションとして構築する理由
一般的な感覚として我々がSlack botと呼んでいるものを作成する場合、Slackのプラットフォーム上ではSlack Appを作成する必要があります。
例:
- Slackのプラットフォーム上でAppを作成
- App上でたとえばどのようなイベントを受け取りたいかを設定(例:Appへのメンション)
- Slackからのリクエストを受け取ってレスポンスを返すAPIを実装
Slackからのリクエストに対してレスポンスを返すAPIというシンプルな単一のAPIを作成したいというユースケースにおいては、サーバーレスアプリケーションのようなインフラの考慮をあまりする必要がない仕組みはあっていると言えるでしょう。
botの利用頻度が限られる場合には、コスト的なメリットも出てくるでしょう。
AWS SAMを用いたSlack botの作成
それではさっそく、AWS SAMを用いてSlack botを作成する流れを見ていこうと思います。
SAM CLIのセットアップ
上記公式ドキュメントを参照してください。
IAMのセットアップなどを行い、AWS CLIが実行できる状態になっている必要があります。
私の場合は、macOSの手順を実行しました。
GUIとCLIでのインストール方法がありますが、どちらもインストーラーをダウンロードしてきて実行するという観点では大差はないと思われます。
macOSで開発を行っている方は、Homebrewを利用することも多いと思いますが、SAM CLIは公式ではHomebrew上での提供を取りやめているようなので注意です。(サードパーティ提供のパッケージとしては提供されているようです。)
2023 年 9 月以降、 AWS は AWS SAMCLI () の AWS マネージドHomebrewインストーラーを維持しなくなりますaws/tap/aws-sam-cli。
インストール後は下記コマンドで、インストールが成功しているか、パスが通っているかを確認します。
$ which sam
/usr/local/bin/sam
$ sam --version
SAM CLI, <latest version>
SAM CLIによるプロジェクトの初期化
sam init
コマンドを実行すると、SAMで作成したいアプリケーションの様々な設定について、対話的に設定を行うことができます。
$ sam init
対話的なインターフェースが開始されますが、 1 - AWS Quick Start Templates
を選択し、その中でも一番シンプルそうな 1 - Hello World Example
を選択しておきます。
Use the most popular runtime and package type? (Python and zip)
に関しては y
にしておき(Pythonが利用されます)、その他はシンプルな構成で検証を行いたいため全て N
にして設定しておきます。
アプリケーション名についても尋ねられるため、 sample-sam-slack-app
としておきます。
対話式のインターフェースが終了すると、カレントディレクトリに先ほど入力したアプリケーション名でディレクトリが作成されます。
$ cd sample-sam-slack-app/
$ tree
.
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
├── integration
│ ├── __init__.py
│ └── test_api_gateway.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
注目すべきは下記のファイルです。
-
samconfig.toml
: SAM CLIを実行する際に参照されるSAMの設定ファイル -
template.yaml
: SAMのデプロイ時に実行されるCloudFormationのテンプレートファイル
Slack Appの実装
sam init
で生成したコードにSlack Appとしての実装を行なっていきます。
生成されたコードをまずは丸っと消してしまいましょう。
$ rm -rf events/ hello_world/ tests/
次に src
ディレクトリを作成し、アプリケーションのロジックを書く app.py
を作成します。
$ mkdir src
$ touch src/app.py
app.pyでは下記のBolt for PythonというSlack謹製のフレームワークを利用します。
このようなフレームワークを利用することで、「Slackからのリクエストであるか」という認証を自前で実装する必要がなくなります。
また、Slack APIのラッパーとしての側面も持っているため、イベントに対して簡易的にメッセージを返すなどは簡単に実現できます。
Slackプラットフォーム上でのSlack App作成については、上記ドキュメントを参考にして行なってください。(細かい設定などは後述します)
Bolt for Pythonのドキュメントに書いてある通りここでは、venvを利用して仮想環境を作成して作業を行います。(Python環境のセットアップについては省略します)
$ python3 -m venv .venv
$ source .venv/bin/activate
$ which python3
/Users/${ユーザー名}/sample-sam-application/sample-sam-slack-app/.venv/bin/python3
次に仮想環境内にBolt for Pythonをインストールします。
$ pip install slack_bolt
SAM CLIではrequirements.txtを参照して、ビルドをしてくれるのでrequirements.txtも出力しておきます。
$ pip freeze > src/requirements.txt
次に src/app.py
を編集します。
import os
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
app = App(
token=os.environ["SLACK_BOT_TOKEN"],
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
process_before_response=True,
)
@app.event("app_mention")
def say_hello(event, say):
user_id = event["user"]
say(f"Hi, <@{user_id}>!")
def lambda_handler(event, context):
slack_handler = SlackRequestHandler(app=app)
return slack_handler.handle(event, context)
botに対してメンションをすると、メンションをしたユーザーに対して「Hi, @user_id」と返答メッセージを送信sするシンプルなロジックになっています。
環境変数の設定については後述します。
SAM CLIでのビルド
ここまで設定した段階で一度SAM CLIでのビルドを試してみましょう。
sam init
コマンドで生成したディレクトリ構成を変更しているため、 template.yaml
や samconfig.toml
の変更が必要です。
template.yaml
をまずは下記のように修正してください。
@@ -12,31 +12,31 @@
MemorySize: 128
Resources:
- HelloWorldFunction:
+ SampleSAMSlackAppFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
- CodeUri: hello_world/
+ CodeUri: src/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
- HelloWorld:
+ SampleSAMSlackApp:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
- Path: /hello
- Method: get
+ Path: /slack/events
+ Method: post
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
- HelloWorldApi:
- Description: "API Gateway endpoint URL for Prod stage for Hello World function"
- Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
- HelloWorldFunction:
- Description: "Hello World Lambda Function ARN"
- Value: !GetAtt HelloWorldFunction.Arn
- HelloWorldFunctionIamRole:
- Description: "Implicit IAM Role created for Hello World function"
- Value: !GetAtt HelloWorldFunctionRole.Arn
+ SampleSAMSlackAppApi:
+ Description: "API Gateway endpoint URL for Prod stage for Sample SAM Slack App function"
+ Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/slack/events"
+ SampleSAMSlackAppFunction:
+ Description: "Sample SAM Slack App Lambda Function ARN"
+ Value: !GetAtt SampleSAMSlackAppFunction.Arn
+ SampleSAMSlackAppFunctionIamRole:
+ Description: "Implicit IAM Role created for Sample SAM Slack App function"
+ Value: !GetAtt SampleSAMSlackAppFunctionRole.Arn
SAM CLIでのデプロイ
ここまで変更できたら、デプロイを試してみましょう。
$ sam deploy
を実行しましょう。
CloudFormationのStackのChangeSetが表示されます。
今回は初めて作成することになるので全てが新規作成かのような表示になっていると思います。
Deploy this changeset? [y/N]
と聞かれるため、y
を入力してデプロイを開始します。
Successfully created/updated stack - sample-sam-slack-app in ap-northeast-1
と表示されればデプロイ成功です。
CloudFormation outputs from deployed stack
には template.yaml
で設定した出力(例:API GatewayのURL)などが出力されていると思います。
SampleSAMSlackAppApi
の値は、Slack Appの設定に必要であるためメモしておきましょう。
ここで試しに表示されたURLについてCurlでリクエストを投げてみましょう。
$ curl -X POST '${URL}'
{"message": "Internal server error"}
環境変数の設定もされていないため、Internal server errorが返ってきます。
Slack Appの設定
まずは、Slackプラットフォーム上に作成したアプリケーションの「OAuth & Permissions」を開いてください。
「Scopes」までスクロールし、bot tokenに下記のようにScopeを追加します。
Scopeの追加後には、「Install to Workspace」ボタンからインストールしましょう。
Bot Tokenが取得できるようになります。
これがLambda関数に設定したい SLACK_BOT_TOKEN
の値になります。
次に、Basic Informationを開きましょう。
「App Credentials」 の中の、「Signing Secret」が SLACK_SIGNING_SECRET
の値になります。
template.yaml
を編集して環境変数として設定しましょう。
xではなく実際の値を設定してください。
@@ -20,6 +20,10 @@
Runtime: python3.9
Architectures:
- x86_64
+ Environment:
+ Variables:
+ SLACK_BOT_TOKEN: "x"
+ SLACK_SIGNING_SECRET: "x"
Events:
SampleSAMSlackApp:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
次にSlackプラットフォームから、イベント情報をリクエストしてもらうために、API GatewayのURLを設定しましょう。
「Event Subscriptions」を開き、Onにします。
「Request URL」に今回作成したAPI GatewayのURLを入力します。
デプロイ時に出力されたURLで、パス( /slack/events
)まで含めて入力します。
ここにURLを入力した時点で、Slack側からVerifyのリクエストが発行されるらしく、うまくいっている場合はVerified と表示されます。
また、その下の「Subscribe to bot events」にも設定が必要です。
「Add Bot User Event」ボタンを押して app:mention
を設定しておきましょう。
この設定を行うと、画面下部の「Save Changes」が有効になり、設定を保存できるようになったと思います。
ここまで来ればもうSlack Appは動く状態になっています。
任意のSlackチャンネルにSlack Appを追加して、メンションを飛ばしてみましょう。
SAM CLIでの後片付け
$ sam delete
上記のコマンドを実行することで、SAM CLIによって作成されたCloudFormationのスタックが削除されます。
Slackプラットフォーム上のSlack Appは手動で削除してください。
終わりに
以上、AWS SAMを利用してSlack botを作成する方法を紹介しました。
AWS SAMを利用することで、低コストかつ管理がしやすい状態でSlack botを作成することができることがわかったかと思います。
Slack Appには様々な制約があり、今回紹介したものだけではなかなか本番環境として活用可能なbotを作成することは難しいかもしれません。
今後、他の記事でそのような制約をうまく回避する方法についても、記載していきたいと思っています。
Discussion