🚀 Cloud Run + LINE Bot + Gemini を使った Webhook のデプロイ手順

このガイドでは、Cloud Run に **LINE Bot の Webhook** をデプロイし、**Gemini API を使った自動返信** を実装する方法をまとめます。  

実際に動作確認も出来ましたが、あくまで忘備録です。記事はいつも通りChatGPTに書いてもらっています。この手順もChatGPTと相談させてもらいながら作りました。

---

## **📌 1. 全体の構成**
### **🎯 目標**
- **LINE メッセージの Webhook を受信**
- **Gemini API を使って返信を生成**
- **LINE Bot を通じて返信**
- **Cloud Run にデプロイ**
- **Secret Manager で API キーを管理**

### **🗂 ファイル構成**
```
webhook_project/
│── app.py                    # LINE Webhook のメイン処理
│── reply_generator.py         # Gemini API で返信を生成
│── requirements.txt           # 必要な Python ライブラリ
│── Dockerfile                 # Cloud Run 用の Docker 設定
│── cloudbuild.yaml            # Cloud Build でビルド & デプロイ
└── .gcloudignore              # GCP に不要なファイルをアップしないための設定
```

---

## **📌 2. Secret Manager のセットアップ**
LINE と Gemini API のキーを Secret Manager に保存して管理します。

### **🔹 1️⃣ Secret Manager に `LINE_CHANNEL_ACCESS_TOKEN` を作成**
```sh
gcloud secrets create LINE_CHANNEL_ACCESS_TOKEN --replication-policy="automatic"
```

### **🔹 2️⃣ `LINE_CHANNEL_ACCESS_TOKEN` の登録**
```sh
echo -n "YOUR_LINE_ACCESS_TOKEN" | gcloud secrets versions add LINE_CHANNEL_ACCESS_TOKEN --data-file=-
```

### **🔹 3️⃣ Secret Manager に `GEMINI_API_KEY` を作成**
```sh
gcloud secrets create GEMINI_API_KEY --replication-policy="automatic"
```

### **🔹 4️⃣ `GEMINI_API_KEY` の登録**
```sh
echo -n "YOUR_GEMINI_API_KEY" | gcloud secrets versions add GEMINI_API_KEY --data-file=-
```

---

## **📌 3. Webhook の実装**
### **📝 `app.py`（Webhook のメイン処理）**
```python
import os
import logging
import requests
from flask import Flask, request, jsonify
from reply_generator import generate_reply  # Gemini API の処理

app = Flask(__name__)

# Secret Manager から取得した環境変数を使用
LINE_CHANNEL_ACCESS_TOKEN = os.getenv("LINE_CHANNEL_ACCESS_TOKEN")

logging.basicConfig(level=logging.INFO)

@app.route('/webhook', methods=['POST'])
def webhook():
    """LINE Webhook を受信し、Gemini API の返信を送信"""
    data = request.json
    logging.info(f"Received data: {data}")

    if "events" in data and len(data["events"]) > 0:
        event = data["events"][0]
        if event["type"] == "message" and event["message"]["type"] == "text":
            text = event["message"]["text"]
            reply_token = event["replyToken"]

            # Gemini の API から適切な応答を取得
            gemini_reply = generate_reply(text)

            # LINE に返信
            reply_message(reply_token, gemini_reply)

    return jsonify({"message": "Webhook received"}), 200

def reply_message(reply_token, text):
    """LINE にメッセージを返信する"""
    if not LINE_CHANNEL_ACCESS_TOKEN:
        logging.error("LINE_CHANNEL_ACCESS_TOKEN is not set!")
        return

    url = "https://api.line.me/v2/bot/message/reply"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {LINE_CHANNEL_ACCESS_TOKEN}"
    }
    payload = {
        "replyToken": reply_token,
        "messages": [{"type": "text", "text": text}]
    }
    
    response = requests.post(url, headers=headers, json=payload)
    logging.info(f"LINE API response: {response.status_code} {response.text}")

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
```

---

### **📝 `reply_generator.py`（Gemini API で返信を生成）**
```python
import os
import google.generativeai as genai

# Gemini API Key を環境変数から取得
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")

# Gemini API の初期化
genai.configure(api_key=GEMINI_API_KEY)

def generate_reply(text):
    """Gemini API を使用して返信を生成"""
    if not GEMINI_API_KEY:
        return "Gemini API Key が設定されていません。"

    try:
        model = genai.GenerativeModel("gemini-pro")
        response = model.generate_content(text)
        return response.text.strip() if response and response.text else "適切な応答を生成できませんでした。"
    
    except Exception as e:
        return f"エラーが発生しました: {str(e)}"
```

---

## **📌 4. Cloud Run のデプロイ**
### **📝 `requirements.txt`**
```
Flask
gunicorn
requests
google-generativeai
```

### **📝 `Dockerfile`**
```dockerfile
FROM python:3.9

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . .

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "app:app"]
```

---

### **📝 `cloudbuild.yaml`（ビルド & デプロイ）**
```yaml
steps:
  # 1️⃣ Docker イメージをビルド
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'gcr.io/${_PROJECT_ID}/webhook02', '.']

  # 2️⃣ Docker イメージをプッシュ
  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'gcr.io/${_PROJECT_ID}/webhook02']

  # 3️⃣ Cloud Run にデプロイ（Secret Manager も設定）
  - name: 'gcr.io/cloud-builders/gcloud'
    args:
      - 'run'
      - 'deploy'
      - 'webhook02'
      - '--image'
      - 'gcr.io/${_PROJECT_ID}/webhook02'
      - '--platform'
      - 'managed'
      - '--region'
      - 'asia-northeast1'
      - '--allow-unauthenticated'
      - '--set-secrets'
      - 'LINE_CHANNEL_ACCESS_TOKEN=LINE_CHANNEL_ACCESS_TOKEN:latest'
      - '--set-secrets'
      - 'GEMINI_API_KEY=GEMINI_API_KEY:latest'

substitutions:
  _PROJECT_ID: webhook2sns02-449820
```

---

## **📌 5. ビルド & デプロイ**
```sh
gcloud builds submit --config cloudbuild.yaml
```
✅ **これで `Cloud Build` によって**:
1. `Dockerfile` をもとにコンテナをビルド
2. `Artifact Registry` にコンテナをプッシュ
3. `Cloud Run` にデプロイ
4. `Secret Manager` の値 (`LINE_CHANNEL_ACCESS_TOKEN`, `GEMINI_API_KEY`) を適用  
が一括で実行される。

---

## **📌 6. 動作確認**
### **1️⃣ `Cloud Run` のログ確認**
```sh
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=webhook02" --limit 5 --format="table(timestamp, textPayload)"
```

### **2️⃣ `curl` で Webhook をテスト**
```sh
curl -X POST https://webhook02-rsz34pmfaa-an.a.run.app/webhook \
     -H "Content-Type: application/json" \
     -d '{"events": [{"type": "message", "replyToken": "dummy_reply_token", "message": {"type": "text", "text": "こんにちは"}}]}'
```

---

## **🚀 まとめ**
✅ **Secret Manager を使って API キーを安全に管理**  
✅ **Cloud Build を使って `ビルド & デプロイ` を自動化**  
✅ **Cloud Run で LINE Webhook をホスト**  
✅ **Gemini API で AI 返信を生成**  

🎯 **これで LINE Bot が Cloud Run で動作し、Gemini AI を使った応答が可能になります！** 🚀