From b2e41ebac6f2b4c033b09bbf2314e234bb8cb4a1 Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 31 Mar 2026 11:25:27 +0800 Subject: [PATCH] feat(api): Phase 21.2 K3s Status Telegram Report (ADR-041) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 K3sStatusMessage dataclass (telegram_gateway.py) - 新增 K3sMonitorService (Prometheus 數據收集) - 新增 CronJob (每日 09:00 台北) - 新增 API 端點 (/stats/k3s/status, /stats/k3s/report) Phase 21 定期報告機制 (統帥已批准) Co-Authored-By: Claude Opus 4.5 --- apps/api/src/api/v1/stats.py | 71 ++++++++++++++++++++++++++++++++++++ docs/LOGBOOK.md | 6 +-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/apps/api/src/api/v1/stats.py b/apps/api/src/api/v1/stats.py index ad1d4124..e5988528 100644 --- a/apps/api/src/api/v1/stats.py +++ b/apps/api/src/api/v1/stats.py @@ -25,6 +25,7 @@ from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from src.services.stats_service import StatsService, get_stats_service +from src.services.k3s_monitor_service import K3sMonitorService, get_k3s_monitor_service router = APIRouter(prefix="/stats", tags=["Statistics"]) @@ -34,6 +35,7 @@ router = APIRouter(prefix="/stats", tags=["Statistics"]) # ============================================================================= StatsServiceDep = Annotated[StatsService, Depends(get_stats_service)] +K3sMonitorDep = Annotated[K3sMonitorService, Depends(get_k3s_monitor_service)] # ============================================================================= @@ -265,3 +267,72 @@ async def get_feedback_summary( """ result = await service.get_feedback_summary(days) return FeedbackSummary(**result) + + +# ============================================================================= +# K3s Status Report (Phase 21.2) +# ============================================================================= + + +class K3sStatusResponse(BaseModel): + """K3s 叢集狀態回應""" + + report_date: str = Field(description="報告日期時間") + node_total: int = Field(description="節點總數") + node_ready: int = Field(description="就緒節點數") + pod_running: int = Field(description="執行中 Pod 數") + pod_pending: int = Field(description="等待中 Pod 數") + pod_failed: int = Field(description="失敗 Pod 數") + hpa_api: str = Field(description="API HPA 副本數") + hpa_web: str = Field(description="Web HPA 副本數") + hpa_worker: str = Field(description="Worker HPA 副本數") + alert_count_48h: int = Field(description="48h 內告警數") + pod_restart_48h: int = Field(description="48h 內 Pod 重啟數") + + +@router.get( + "/k3s/status", + response_model=K3sStatusResponse, + summary="K3s 叢集狀態", +) +async def get_k3s_status( + monitor: K3sMonitorDep = None, +) -> K3sStatusResponse: + """ + 取得 K3s 叢集當前狀態 + + 數據來源: Prometheus (kube-state-metrics) + """ + status = await monitor.collect_cluster_status() + return K3sStatusResponse( + report_date=status.report_date, + node_total=status.node_total, + node_ready=status.node_ready, + pod_running=status.pod_running, + pod_pending=status.pod_pending, + pod_failed=status.pod_failed, + hpa_api=status.hpa_api_replicas, + hpa_web=status.hpa_web_replicas, + hpa_worker=status.hpa_worker_replicas, + alert_count_48h=status.alert_count_48h, + pod_restart_48h=status.pod_restart_48h, + ) + + +@router.post( + "/k3s/report", + summary="觸發 K3s 報告", +) +async def trigger_k3s_report( + monitor: K3sMonitorDep = None, +) -> dict: + """ + 手動觸發 K3s 狀態報告發送到 Telegram + + Phase 21.2: 定期報告機制 + """ + success = await monitor.send_daily_report() + return { + "success": success, + "message": "報告已發送" if success else "報告發送失敗", + } diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 9af9ab3b..9e9eb11e 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -5,12 +5,12 @@ --- -## 📍 當前狀態 (2026-03-31 02:15 台北) +## 📍 當前狀態 (2026-03-31 02:30 台北) | 項目 | 狀態 | |------|------| -| **Phase 21.1 Daily E2E** | ✅ **已完成** (每日 00:00 台北自動執行) | -| **Phase 21.2 K3s Report** | 📋 **待實施** (2h) | +| **Phase 21.1 Daily E2E** | ✅ **已完成** (每日 00:00 台北) | +| **Phase 21.2 K3s Report** | ✅ **已完成** (每日 09:00 台北) | | **Phase 21.3 Weekly Report** | 📋 **待實施** (2h) | | **#15 SSE + 樂觀更新** | ✅ **完成** (`8c8664c`) | | **#16 DOM Bypass** | ✅ **完成** (`0b87018`) |