Cloud Runでハマったslack-bolt + flaskをデプロイした際のエラー対処法
はじめに
Slackアプリ開発でslack-bolt-pythonとFlaskを組み合わせてCloud Runにデプロイしようとしたところ、エラーに遭遇し、かなり苦戦しました...
遭遇したエラーとその解決方法をまとめます。
環境・構成
- Python: 3.10
- slack-bolt: 1.23.0
- Flask: 3.1.1
- Google Cloud Run
発生したエラー
エラー1: Slackのイベント検証失敗
症状
Your URL didn't respond.
もしくは
Your URL didn't respond with the value of the `challenge` parameter.
Slack側でEvent Subscriptionsの設定を行う際に、エンドポイントURLの検証で失敗するエラーです。
エラー2: Cloud Runと疎通失敗
症状
"The request failed because either the HTTP response was malformed or connection to the instance had an error. Additional troubleshooting documentation can be found at: https://cloud.google.com/run/docs/troubleshooting#malformed-response-or-connection-error"
このエラーメッセージを調べてもあまり情報がなく、どこが原因なのか全くわかりませんでした。
解決方法:変数名の修正
問題のあったコード
import os
from slack_bolt import App
from flask import Flask, request
from slack_bolt.adapter.flask import SlackRequestHandler
app = App(
token=os.environ.get("SLACK_BOT_TOKEN"),
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
process_before_response=True
)
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)
@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
return handler.handle(request)
@app.event("app_mention")
def handle_mention(event, say):
say("Hello!")
修正後のコード
変数名を以下のように変更しました:
-
app
→slack_app
-
flask_app
→app
import os
from slack_bolt import App
from flask import Flask, request
from slack_bolt.adapter.flask import SlackRequestHandler
slack_app = App(
token=os.environ.get("SLACK_BOT_TOKEN"),
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
process_before_response=True
)
app = Flask(__name__)
handler = SlackRequestHandler(slack_app)
@app.route("/slack/events", methods=["POST"])
def slack_events():
return handler.handle(request)
@slack_app.event("app_mention")
def handle_mention(event, say):
say("Hello!")
原因の詳細
Cloud RunでコンテナイメージをCloud Buildで作成していましたが、デフォルトでは以下のようにエントリーポイントが設定されていました:
[builder] Setting default entrypoint: "gunicorn -b :8080 main:app"
Cloud Buildもgunicornもそれまで詳しくなかったのですが、調べてみると、gunicornでFlaskアプリケーションを起動する際の正しいコマンド形式は {module_import}:{app_variable}
という形であることがわかりました。
つまり、main:app
は「mainモジュール内のapp変数」を指しています。しかし、元のコードではFlaskアプリケーションの変数名が flask_app
だったため、gunicornが正しくアプリケーションを見つけられず、疎通がうまくいかなかったのです。
Flask公式ドキュメントでも、この命名規則について説明されています。
まとめ
今回のエラーは、変数名という非常にシンプルな問題が原因でした。
Cloud Runやgunicornの仕組みを理解していれば早期に解決できた問題ですが、エラーメッセージからは原因を特定するのが困難でした。。。同じような問題で困っている方の参考になれば幸いです。
Discussion
記事の公開前日に絶賛この件でハマってまして、CloudRunでの実行諦めたのですがスッキリしました、ありがとうございます。