Files
awoooi/docs/runbooks/RUNBOOK-OLLAMA-FAILOVER.md

264 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RUNBOOK-OLLAMA-FAILOVER.md
# Ollama 容災監控 Runbook
# 2026-04-26 P2.3 by Claude Sonnet 4.6 (tool-expert)
# 2026-06-04 Codex 更新ADR-110 現行順序為 GCP-A → GCP-B → 111 local → Gemini111 經 110:11437 Nginx proxy 進入,不直接由 K8s Pod 打 111。
# 對應告警規則: ops/monitoring/ollama_health_rules.yaml
# 對應 Dashboard: ops/monitoring/grafana/dashboards/ollama_failover.json
---
## 現行拓樸與一鍵診斷
現行 AI 推理順序:
| 順位 | Provider | 入口 |
|------|----------|------|
| 1 | GCP-A | `http://192.168.0.110:11435` |
| 2 | GCP-B | `http://192.168.0.110:11436` |
| 3 | 111 local fallback | `http://192.168.0.110:11437``192.168.0.111:11434` |
| 4 | Gemini | 只在三層 Ollama 都不可用後作最後備援 |
111 是 Mac / LaunchAgent 管理的 local fallback不要用舊的 `systemctl` / `nvidia-smi` 手順判斷它。若 `11437``502`,優先分辨是 110 proxy 本身壞掉,還是 110 到 111 的 LAN/ARP 不通。
只讀診斷:
```bash
bash scripts/ops/ollama111-fallback-proxy-diagnose.sh
```
常見判讀:
| 證據 | 判讀 | 行動 |
|------|------|------|
| `ollama_gcp_a=up``ollama_gcp_b=up`,但 `ollama_local=down` | 第三層 fallback 單點故障,核心 AI lane 仍可用 | 不要改 provider order先修 111 主機或 LAN |
| 110 `ip neigh 192.168.0.111 INCOMPLETE` | 110 找不到 111 MAC通常是 111 關機、睡眠、Wi-Fi/LAN 斷線或 IP 漂移 | 恢復 111 電源/網路,再重跑診斷 |
| 110 direct `192.168.0.111:11434` OK`127.0.0.1:11437` 502 | 110 Nginx proxy 配置或程序異常 | 檢查 `110-ollama-proxy.conf` 與 Nginx reload |
| SSH 到 `ollama-111-gpu` OK`127.0.0.1:11434` 失敗 | 111 本機 Ollama 或 allowlist LaunchAgent 異常 | 檢查 `com.momo.ollama111-allow-proxy` 與 Ollama |
---
## Grafana Dashboard 使用說明
Dashboard 路徑:`Ollama 容災監控`uid: `ollama-failover-p23`
匯入方式Grafana UI → Dashboards → Import → Upload JSON file → 選 `ops/monitoring/grafana/dashboards/ollama_failover.json`
### Panel 1 — Ollama 可用性 (Stat)
**看什麼**`up{job=~"ollama_gcp_a|ollama_gcp_b|ollama_local|ollama_111"}` × 100顯示每個 Ollama provider endpoint 的 scrape 存活狀態。
| 顏色 | 意義 |
|------|------|
| 綠色 100% | Prometheus 探測正常,主機在線 |
| 黃色 | 部分 endpoint 離線,系統應進入容災 |
| 紅色 0% | Ollama provider pool 全離線,高風險 |
**注意**:此面板反映 Prometheus scrape 狀態,需要 scrape job 命名對齊 `ollama_gcp_a` / `ollama_gcp_b` / `ollama_local`
設定檔位於 `ops/monitoring/generated/prometheus-scrape-generated.yaml`
---
### Panel 2 — 推理延遲 P50 / P99 (Time Series)
**看什麼**:推理延遲分位數。
| 門檻 | 含義 |
|------|------|
| < 10s (P50) | HEALTHY — 正常使用 111 |
| 1030s (P50) | SLOW — 系統已切至 Gemini |
| > 30s (P99) | DEGRADED — 應觸發 failover |
**⚠️ BACKLOG 警告**`ollama_inference_duration_seconds_bucket` 尚未在 API 暴露(需在 `_check_inference()` 加 Histogram.observe())。
面板顯示 "No Data" 是正常的,等 backlog 補完後啟用。
---
### Panel 3 — AI Provider 路由分布 (Pie Chart)
**看什麼**:過去 5 分鐘各 provider 被選中的請求比例。
| 分布 | 意義 |
|------|------|
| ollama / ollama_gcp_a 佔 >90% | 正常GCP-A 健康 |
| ollama_gcp_b 佔多數 | GCP-A SLOW/DEGRADED/OFFLINE容災到 GCP-B |
| ollama_local 出現 | GCP-A/B 均不可用,容災到 111 local |
| gemini 佔多數 | Ollama provider pool 全部不可用,使用付費備援 |
| 全部 nemotron/claude | 極端情況,所有主力 provider 失敗 |
---
### Panel 4 — Failover / Recovery 觸發次數 (Bar Chart)
**看什麼**:每小時 failover和 recovery的觸發次數。
| 模式 | 意義 |
|------|------|
| 兩條都接近 0 | 正常111 穩定運行中 |
| 橘色上升後綠色跟上 | Auto recovery 正常:切出後又切回 |
| 橘色上升,綠色不動 | **`OllamaRecoveryStuck` alert**,見下方 runbook |
| 橘色持續高頻(>5/h | **`OllamaFailoverFrequent` alert**111 不穩定 |
---
## Alert Runbook
### `OllamaInstanceDown` — Ollama 主機離線
**觸發條件**`up{job=~"ollama_gcp_a|ollama_gcp_b|ollama_local|ollama_111"} == 0` 持續 2 分鐘。
**影響評估**
- 系統應依序切至 GCP-B / 111 local / Gemini查 Panel 3 與 `/api/v1/health` 確認)
- 查 Panel 4 是否有 Failover 計數上升
**排查步驟**
```bash
# 步驟 1跑完整只讀診斷先分辨是 110 proxy、111 LAN、或 111 本機服務
bash scripts/ops/ollama111-fallback-proxy-diagnose.sh
# 步驟 2若 110 到 111 是 No route to host / ip neigh INCOMPLETE
# 代表 111 主機或網路不可達;先恢復 111 電源/網路,不要重啟 API。
# 步驟 3若 SSH 到 111 可行,再查本機 Ollama 與 LaunchAgent
ssh ollama-111-gpu 'curl -sS -m 5 http://127.0.0.1:11434/api/tags'
ssh ollama-111-gpu 'launchctl print gui/501/com.momo.ollama111-allow-proxy | head -80'
```
**恢復確認**
`/api/v1/health``ollama_local.status=up`,且 `scripts/ops/ollama111-fallback-proxy-diagnose.sh` 中 110 `proxy_11437` 回 HTTP 200。
---
### `OllamaFailoverFrequent` — Failover 頻率過高
**觸發條件**`rate(ollama_failover_triggered_total[1h]) > 5` 持續 10 分鐘(每小時超過 5 次切換)。
**影響評估**
- 服務本身仍可用Gemini 在接手)
- 但 Gemini 配額消耗加速,有觸發 `GeminiQuotaApproaching` 的風險
**排查步驟**
```bash
# 步驟 1確認 111 近況(反覆 OFFLINE/HEALTHY 之間跳動?)
bash scripts/ops/ollama111-fallback-proxy-diagnose.sh
# 步驟 2查 API log 找 failover 原因
kubectl logs -n awoooi-prod deploy/api --since=30m | grep "ollama_failover_triggered"
# 步驟 3查推理延遲是否長期在 SLOW 邊界?)
kubectl logs -n awoooi-prod deploy/api --since=30m | grep "ollama_health_checked"
# 步驟 4如果是 111 本機 Ollama 問題,需依 LaunchAgent runbook 執行維護;不可直接用 Linux systemctl 手順。
```
---
### `OllamaRecoveryStuck` — Auto Recovery 停滯
**觸發條件**`ollama_health_status{host="111"} == 1 AND ollama_current_primary_is_ollama == 0` 持續 5 分鐘。
111 已 HEALTHY 但路由仍走 Gemini
**影響評估**
- API 功能正常Gemini 在服務)
- 但 Gemini 配額持續消耗111 GPU 資源浪費
**排查步驟**
```bash
# 步驟 1確認 OllamaAutoRecoveryService 是否在運行
kubectl logs -n awoooi-prod deploy/api --since=10m | grep "ollama_auto_recovery"
# 步驟 2查 recovery service 狀態
kubectl logs -n awoooi-prod deploy/api --since=10m | grep -E "ollama_auto_recovery_started|ollama_auto_recovery_stopped|ollama_auto_recovery_loop_error"
# 步驟 3查 current_primary Redis key
kubectl exec -n awoooi-prod deploy/api -- python -c "
import asyncio
from src.core.redis_client import get_redis
async def check():
r = get_redis()
val = await r.get('ollama:current_primary')
print('current_primary:', val)
asyncio.run(check())
"
# 步驟 4如果 recovery service 掛了,重啟 API pod會重新啟動 lifespan
kubectl rollout restart deployment/api -n awoooi-prod
kubectl rollout status deployment/api -n awoooi-prod
```
---
### `GeminiQuotaApproaching` — Gemini 配額 >80%
**觸發條件**`gemini_daily_call_count / gemini_daily_quota > 0.8` 持續 5 分鐘。
**注意**`gemini_daily_quota` 來自 `settings.GEMINI_DAILY_QUOTA`(預設 1000
`gemini_daily_call_count` 從 Redis key `ollama:gemini_daily_count:{YYYY-MM-DD}` 讀取並刷新 Gauge。
**影響評估**
- 當日 Gemini 配額即將耗盡
- 耗盡後系統會自動切至 188 CPU-only 備援qwen2.5:7b-instruct速度較慢
**行動步驟**
```bash
# 步驟 1確認當日 Gemini 使用量
kubectl exec -n awoooi-prod deploy/api -- python -c "
import asyncio, datetime
from src.core.redis_client import get_redis
async def check():
r = get_redis()
today = datetime.date.today().isoformat()
val = await r.get(f'ollama:gemini_daily_count:{today}')
print(f'gemini_daily_count[{today}]:', val)
asyncio.run(check())
"
# 步驟 2確認 111 是否能快速恢復(讓流量切回 Ollama
bash scripts/ops/ollama111-fallback-proxy-diagnose.sh
# 步驟 3如需增加配額修改 settings
# k8s/awoooi-prod/04-configmap.yaml.patch-* 找 GEMINI_DAILY_QUOTA
# 改完後 kubectl apply + rollout restart
# 步驟 4緊急手動重置計數謹慎使用只在確認誤計時才用
# kubectl exec -n awoooi-prod deploy/api -- redis-cli DEL "ollama:gemini_daily_count:$(date +%Y-%m-%d)"
```
---
## Metric 清單
| Metric | 類型 | 狀態 | 說明 |
|--------|------|------|------|
| `up{job="ollama_gcp_a"}` | Gauge | ✅ 現有 | Prometheus scrape 存活 |
| `up{job="ollama_gcp_b"}` | Gauge | ✅ 現有 | Prometheus scrape 存活 |
| `up{job="ollama_local"}` | Gauge | ✅ 現有 | Prometheus scrape 存活 |
| `ollama_failover_triggered_total` | Counter | ✅ P2.3 補入 | failover 切換次數labels: from_provider, to_provider |
| `ollama_recovery_triggered_total` | Counter | ✅ P2.3 補入 | recovery 切回次數labels: from_provider |
| `ollama_health_status{host}` | Gauge | ✅ P2.3 補入 | 健康狀態 1=healthy, 0=not_healthy |
| `ollama_current_primary_is_ollama` | Gauge | ✅ P2.3 補入 | 1=primary 是 ollama, 0=failover 中 |
| `ai_router_selected_provider_total` | Counter | ✅ P2.3 補入 | AI router 選擇次數labels: provider |
| `gemini_daily_call_count` | Gauge | ✅ P2.3 補入 | 今日 Gemini 呼叫次數 |
| `gemini_daily_quota` | Gauge | ✅ P2.3 補入 | Gemini 每日配額 |
| `ollama_inference_duration_seconds` | Histogram | ⏳ BACKLOG | 推理延遲分布,需在 `_check_inference()` 加 observe |
| `post_execution_verification_total` | Counter | ⏳ BACKLOG | Verifier 執行次數,需 auto_repair_service.py 補入 |
| `post_execution_verification_failed_total` | Counter | ⏳ BACKLOG | Verifier 失敗次數,需 auto_repair_service.py 補入 |
## Backlog 補完指引
### `ollama_inference_duration_seconds`
`apps/api/src/services/ollama_health_monitor.py``_check_inference()` 方法結尾,加:
```python
from src.core.metrics import OLLAMA_INFERENCE_DURATION # 需先在 metrics.py 加 Histogram
OLLAMA_INFERENCE_DURATION.labels(host=host_label).observe(latency_ms / 1000)
```
### `post_execution_verification_*`
`apps/api/src/services/auto_repair_service.py` 的 verifier 路徑,加 Counter inc()。
需先確認 verifier 執行點grep `post_execution``verif` 找入口)。