Kon's DX Lab - Case Study

Day 77 │ Slack勤怠ボット MVP 全手順「Cloud Run編」

Published on 2025-06-17

🔬 Case Study Summary
Problem

(ここに課題を記述)

Result

(ここに具体的な成果を記述)


Tech & Process

(ここに採用技術とプロセスを記述) コードを詳しく見る »

こんにちは、こんです🦊

今回は「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 を用意

  1. Googleスプレッドシートを新規作成(例:勤怠記録)

  2. 1行目(A1〜D1)に以下のカラム名を入力:

    • 日時

    • ユーザー名

    • タイプ(clockin / clockout)

    • コメント

  3. シートURLの /d/xxxxx/ の部分をコピー → .envまたはCloud Run環境変数に設定する SPREADSHEET_ID になります

⚠ このシートはCloud Run側のサービスアカウントにも「編集者」として共有しておく必要があります!


🚀 STEP 2. Slack App 作成

  1. Slack API にアクセス → 「From scratch」で新規アプリ作成

  2. 「OAuth & Permissions」でBot Tokenスコープを追加:

    • commands(Slash Command利用のため)

    • chat:write(DMで返信するため)

    • users:read(ユーザー名を取得するため)

  3. App Homeの「Show Tabs」設定でHomeとMessages TabをONに

  4. 「Install App」でワークスペースにインストール → トークンとSigning Secretを控える


🚀 STEP 3. Google Cloud Project

  1. Google Cloud Consoleで新規プロジェクト作成(例:workpulse)

  2. 有効化するAPI:

    • Cloud Run Admin API

    • Cloud Build API

    • Google Sheets API

  3. 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 へデプロイ

  1. Cloud Console右上 ▶️ アイコン → Cloud Shellを起動

    1. 👉 https://console.cloud.google.com/cloudshell

  2. 左上の「︙」メニューから「アップロード」を選択

  3. workpulse/フォルダ全体を選択して、Cloud Shellにアップロードする

  4. 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管理画面から以下を設定

  1. Slash Commands

    1. /clockin

      1. Request URL: https://xxxxx.run.app/slack/commands

    2. /clockout

      1. Request URL: https://xxxxx.run.app/slack/commands

  2. Event Subscription 有効

    • Request URL: https://xxxxx.run.app/slack/events

  3. Subscribe to bot events

    1. app_mention

    2. message.channels

    3. message.im

    4. 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開発