Slack BoltによるアプリのCloud Runでの稼働
Cloud RunでSlack Boltを使ってアプリを作りたい
この間までLLMネタを何度もやってきたわけですが、やはりChatGPTを活用したBOTのようなものを作成するためには常時稼働する基盤が必要ですよね。
とはいえお金もないし… 🙄 みたいな状況のときはやはり サーバレスアーキテクチャに頼らざるを得ません。
この間実験的に用意したLLMのSlack用BOT(下の画像のやつです)においてはSlack BoltとCloud Runを使ったのですが、何かとハマりどころがあったので、本記事ではそのポイントをまとめていきます。
(参考: 社訓のようにBigQueryについて説明する、弊社のキャラクター・エビシーに模したPaLM 2)
Slack Boltとは
Slack Bolt とは、SlackでのBOTやアプリを作成するにあたってとても便利なフレームワークです。2023年9月現在、Node.js, Python, Javaで利用できます。
WebhookレベルでのBOT開発等を実施された方は多いかと思いますが、少し込み入った内容や、そうでなくてもBoltを活用することでかなりコーディングの手間は省るので、個人的には欠かせないものとなっています。
興味を持ったらとりあえずSlack Bolt入門ガイドで入門すると良いです。 重要なことはだいたいこのドキュメントに載っています。
Cloud Runとは
Cloud Run は言わずとしれたサーバレスコンテナ環境です。
フルマネージドでかつスケーラブル、しかもコンテナなので環境依存性を排除できる、ということでめちゃくちゃ便利に使えます。もちろん従量課金です。
言わずとしれた、なんて言いましたが、2019年頃に諸々の開発をしていたときはやっとβ版とかだった記憶があるので、たった4年で状況は変わるものだなとしみじみ感じますね。
コンテナベースでの開発・デプロイが気軽に行えるので今回のようなBoltを使ったアプリ開発にもうってつけです。
Slack BoltとCloud Runを組み合わせる際のポイント
Slack Boltの稼働させ方
Slack Boltを活用する際のSlackとのデータのやりとりはHTTPサーバー起動不要でWebSocketを用いてSlackと接続し、各種情報をやりとりする「ソケットモード」と、HTTPサーバーを立ち上げて受信する方法に2種類があります。
常時稼働しているサーバ等がある場合はソケットモードのほうが便利で、最近はソケットモードを採用しているTech記事等も多いです。
コードで具体的に言うと、
from slack_bolt.adapter.socket_mode import SocketModeHandler if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
で稼働させるパターンですね。
(Slack Boltのソケットモードの詳細については、ソケットモードについての説明の公式ドキュメントをご参照下さい。)
しかし、 Cloud Runで稼働させる場合はHTTPサーバーを必ず立ち上げる必要があります。
そうしていないと、下記のような「コンテナ立ち上がりませんでした」というエラーが出ます。
Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable.
PORT環境変数にデフォルト値は8080なので、8080で通信できるよう設定してあげる必要があります。
これは、
from slack_bolt import App app = App(token=os.environ.get("SLACK_BOT_TOKEN"), signing_secret=os.environ.get("SLACK_SIGNING_SECRET")) if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 8080)))
のようにすることで対応出来ます。
ここで、 SLACK_BOT_TOKEN
には、 xoxb
から始まるボットトークンを、SLACK_SIGNING_SECRET
には、Signing Secretをコピーします。
それぞれ、「Features > OAuth & Permissions」および 「Settings > Basic Information」にあります。
あとはソケットモードの場合と同様に設定していけば大丈夫です。
この例のように、 os.environ.get
を使用して、PORT
の環境変数の値を拾いつつ、なければ8080にしておくのがいいと思います。
Slack側での設定
Cloud Runのデプロイを実施した後、Slack側ではCloud RunのURLを設定する必要があります。
Features の中の Event Subscriptions にて、 Enable EventsをOnにしてから、Request URLを入力します。
URLは、デプロイしたCloud RunのエンドポイントURL (末尾 run.app
)に、 /slack/events
を付与したものです。
くれぐれも付与漏れにはお気を付けて… 付与を漏らすとVerifyが通りません。
逆に言うとVerifyする必要があるのでデプロイが先です。
SlackからCloud Runへのリトライ処理の捌き方
Cloud Run自体は「常にCPUを割り当てる」設定をしておきます。
このあたりは、「Slack BoltをGoogle Cloudにデプロイするノウハウ」の記事が非常によくまとまっています。
それでも間隔が空いてしまった場合はコールドスタートしてしまいますので、SlackからCloud Runへ応答遅延によりリクエストのリトライが走ります。
結果として、予期せず処理が重複して行われることがあり得ます。
このようなケースに対応するためには、リクエストヘッダの x-slack-retry-num
を拾う方法が一番手っ取り早いです。
この値が付与されている場合、Slackとしてはリクエストをリトライしているので、リクエスト自体をスルーしてしまいます。
そのような処理も実はBoltだと @app.middleware
を使うことで一発解決します。
具体的には
@app.middleware def skip_retry(logger, request, next): if "x-slack-retry-num" not in request.headers: next()
のような形です。
これで、 x-slack-retry-num
がリクエストヘッダに存在しない場合のみ次の処理へ進むことが出来ます。
Bolt、めちゃくちゃよく出来てますよね。SlackのBOTやアプリを作る上ではもはや欠かせない存在です。
まとめ
本記事では、Slack Boltを使って作ったアプリをサーバーレスであるCloud Runで稼働させる方法についてまとめました。
もちろん理想的には常時稼働するインスタンスを使用したいところですが、コストやインフラ管理の観点でCloud Runを使うという判断を行った際にはこの記事が参考になるかと思います。
なお、先述の通り、Cloud Run自体がコールドスタートしてしまう問題はあるので、BOTとのやりとりが頻繁に行われていない場合は待ち時間が発生することに注意が必要です。
なんにせよ、SlackのBOTやアプリを手軽に稼働させる際にはとても便利に使えるのではないかと思いますのでぜひお試し下さい。