こんにちは、こんです🦊
今回は「Slackから仕事の出社・退社を記録する」ためのMVPアプリとして、Slack勤怠ボットをGoogle Cloud Run上に構築・公開するまでの手順をまとめました。
Slackアプリの作成から、Google Sheets連携、Cloud Runでの無料ホスティング、認証設定、再デプロイ方法まで、つまずきポイントや工夫を含めて下記します。
📅 STEP 0. 事前準備
SlackとGoogle環境を使ってSlack勤怠ボットのMVPを構築します。以下を準備してください:
Slackアカウント(App作成にはワークスペース管理者権限が必要)
Googleアカウント(Google Cloud ConsoleとGoogle Sheets利用)
Python実行環境(VSCode, venv, AnacondaなどでOK)
💡 VSCode等で .env 管理したい方は python-dotenv もインストールしておきましょう
🔢 STEP 1. Google Sheets を用意
Googleスプレッドシートを新規作成(例:勤怠記録)
1行目(A1〜D1)に以下のカラム名を入力:
日時
ユーザー名
タイプ(clockin / clockout)
コメント
シートURLの /d/xxxxx/ の部分をコピー → .envまたはCloud Run環境変数に設定する SPREADSHEET_ID になります
⚠ このシートはCloud Run側のサービスアカウントにも「編集者」として共有しておく必要があります!
🚀 STEP 2. Slack App 作成
Slack API にアクセス → 「From scratch」で新規アプリ作成
「OAuth & Permissions」でBot Tokenスコープを追加:
commands(Slash Command利用のため)
chat:write(DMで返信するため)
users:read(ユーザー名を取得するため)
App Homeの「Show Tabs」設定でHomeとMessages TabをONに
「Install App」でワークスペースにインストール → トークンとSigning Secretを控える
🚀 STEP 3. Google Cloud Project
Google Cloud Consoleで新規プロジェクト作成(例:workpulse)
有効化するAPI:
Cloud Run Admin API
Cloud Build API
Google Sheets API
IAM設定:Cloud Runのサービスアカウント(例:xxx-compute@developer.gserviceaccount.com)に以下ロールを追加:
編集者(Editor)
Sheetsへのアクセス権限(スプレッドシート側の「共有」で編集者として追加)
⚠ IAMと共有設定どちらも忘れると「書き込みできない」エラーで詰まります
🔧 STEP 4. Flask App の構成
ローカルに以下の構成でディレクトリ作成(例:~/workpulse/)
/workpulse/
├── app.py
├── .env ← ローカル開発用の.envファイル。Cloud Runでは使用されません(--set-env-vars を使用)
├── requirements.txt
└── Procfile
🔧 STEP 5. アプリコードを設置
🧪 app.py
よくあるエラー:Cloud Run エラー App failed to load.
Flaskアプリで flask_app を定義しても、app = flask_app を忘れるとCloud Runがルートを見つけられずクラッシュします
import os
import datetime
import hmac
import hashlib
import time
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from flask import Flask, request
from google.auth import default
from googleapiclient.discovery import build
from dotenv import load_dotenv
load_dotenv()
SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")
SLACK_SIGNING_SECRET = os.getenv("SLACK_SIGNING_SECRET")
SPREADSHEET_ID = os.getenv("SPREADSHEET_ID")
app = App(token=SLACK_BOT_TOKEN, signing_secret=SLACK_SIGNING_SECRET)
# Google Sheets API
creds, _ = default()
service = build('sheets', 'v4', credentials=creds)
def verify_slack_request(req):
timestamp = req.headers.get('X-Slack-Request-Timestamp')
slack_signature = req.headers.get('X-Slack-Signature')
# 時間差チェック(リプレイ攻撃防止、300秒 = 5分)
if abs(time.time() - int(timestamp)) > 60 * 5:
return False
sig_basestring = f'v0:{timestamp}:{req.get_data(as_text=True)}'
my_signature = 'v0=' + hmac.new(
SLACK_SIGNING_SECRET.encode(),
sig_basestring.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(my_signature, slack_signature)
def append_to_sheet(values):
body = {"values": [values]}
service.spreadsheets().values().append(
spreadsheetId=SPREADSHEET_ID,
range='A1',
valueInputOption='USER_ENTERED',
body=body
).execute()
@app.command("/clockin")
def handle_clockin(ack, body, client):
ack()
user = client.users_info(user=body['user_id'])['user']['real_name']
now = datetime.datetime.now().strftime('%Y/%m/%d %H:%M')
append_to_sheet([now, user, 'clockin', ''])
client.chat_postMessage(channel=body['user_id'], text="✅ 出勤を記録しました")
@app.command("/clockout")
def handle_clockout(ack, body, client):
ack()
user = client.users_info(user=body['user_id'])['user']['real_name']
now = datetime.datetime.now().strftime('%Y/%m/%d %H:%M')
append_to_sheet([now, user, 'clockout', ''])
client.chat_postMessage(channel=body['user_id'], text="✅ 退勤を記録しました")
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)
@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
if not verify_slack_request(request):
return "Unauthorized", 401
payload = request.get_json(force=True)
if payload.get("type") == "url_verification":
return payload.get("challenge"), 200, {"Content-Type": "text/plain"}
return handler.handle(request)
app = flask_app # Cloud Run が探すエントリポイント
🧪 .env
.env を使っても、Cloud Run上では使われません(アップロードしても無視される)。
✔ Cloud Run では --set-env-vars を使うことが前提なので、.env をローカル検証用と明記すると親切です。
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx ← 「Install App」でSlackにインストール後表示される「Bot User OAuth Token」
SLACK_SIGNING_SECRET=xxxxxxxxxxxx ← 「Basic Information」の「Signing Secret」
SPREADSHEET_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxx ← シートID(URLの /d/xxx/ の xxx)
📦 requirements.txt
flask
slack_bolt
google-api-python-client
google-auth
python-dotenv
gunicorn
☀ STEP 6. Cloud Shell から Cloud Run へデプロイ
Cloud Console右上 ▶️ アイコン → Cloud Shellを起動
左上の「︙」メニューから「アップロード」を選択
workpulse/フォルダ全体を選択して、Cloud Shellにアップロードする
Cloud Shellで以下を実行:
# 作業フォルダに移動
cd /home/xxx/workpulse
# 初回のみプロジェクト設定
gcloud init
# Pick configuration to use: と表示されるので...
1
# Choose the account you want to use for this configuration. と表示されるので...
1
# Pick cloud project to use: と表示されるので...
workpulse-mvp
# プロジェクトIDを設定
gcloud config set project workpulse-mvp
# Procfileの追加
echo "web: gunicorn app:app" > Procfile
gcloud alpha iam policies lint-condition workpulse-app \
--region us-central1 \
--member="allUsers" \
--role="roles/run.invoker"
# Google Cloudにデプロイ
gcloud run deploy workpulse-app \
--source . \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars SLACK_BOT_TOKEN=xxx,SLACK_SIGNING_SECRET=xxx,SPREADSHEET_ID=xxx
完了後、https://xxx.us-central1.run.app/slack/commands のようなURLが発行されます。
✨ STEP 7. Cloud Run の認証設定
Cloud Run 管理画面のセキュリティタブ から
「サービス」→作成した「workpulse-app」を開く
「セキュリティ」タブを開く
「認証」設定において、「IAM を使用して受信リクエストを認証する」のチェックを外す
✏ STEP 8. Slack 設定
Slack API管理画面から以下を設定
Slash Commands
/clockin
Request URL: https://xxxxx.run.app/slack/commands
/clockout
Request URL: https://xxxxx.run.app/slack/commands
Event Subscription 有効
Request URL: https://xxxxx.run.app/slack/events
Subscribe to bot events
app_mention
message.channels
message.im
message.groups
✅ STEP 9. 動作確認
Slack から /clockin, /clockoute で
メッセージは Slack DM に
記録は Google Sheets に発灯
Cloud Run ログは ログタブ で POST /slack/commands を確認
📆 コード修正後の再デプロイ
コードの修正はCloud Shellからが便利。修正したら要再デプロイ!
gcloud run deploy appname \
--source . \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars SLACK_BOT_TOKEN=xxx,SLACK_SIGNING_SECRET=xxx,SPREADSHEET_ID=xxx
💡 まとめ
Slack App を Cloud Run にデプロイするまでの手順は、視覚的に簡単そうですが、すこしの步間違いで「ずっと動かない」状態になるのがこのルートの難しさ。
このNoteで、一人で挑戦してる方の救いになれば幸いです!
#100日チャレンジ #Slackボット開発 #CloudRun #Flask #DX実験記録 #GoogleSheetsAPI #勤怠管理 #GCPデプロイ #Python開発 #MVP開発