refactor: 移除资源用量的可选 Prometheus 接入与平台监控页
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s

- 资源用量弹窗:删除 applyPrometheus/promQueryValue 与峰值/限流字段
  (LOKI_USAGE_PROMETHEUS_URL 从未内置,代码恒走降级),统一提示
  「未接入 Prometheus:暂无峰值/限流数据」;扩容建议改基于 metrics-server 实时值
- 移除「平台监控」菜单页与 /api/v1/system/metrics-summary;
  原始指标保留在标准 /api/v1/metrics(Prometheus 文本格式)可自行抓取
- 手册/落地页/CHANGELOG 同步(去除备份恢复与监控页宣传,指标端点表述中性化)
This commit is contained in:
opsx committed 2026-10-29 15:16:18 +08:00
1 parent f87fb4b2b3
commit c1f822fb10
17 files changed
+9 -465

No files matched your search

-5
View File
@@ -1,7 +1,6 @@
package controllers
import (
"lokistack/pkg/metrics"
"github.com/gin-gonic/gin"
@@ -45,7 +44,3 @@ func (ctrl *SystemController) Health(c *gin.Context) {
utils.Success(c, services.SelfCheck())
}
// MetricsSummary 平台自监控快照(仅 admin;原始 Prometheus 文本在 /api/v1/metrics)
func (ctrl *SystemController) MetricsSummary(c *gin.Context) {
utils.Success(c, metrics.Snapshot())
}
-106
View File
@@ -1,106 +0,0 @@
package metrics
import (
"sort"
dto "github.com/prometheus/client_model/go"
)
// PathCount 单个路由模板的累计请求数
type PathCount struct {
Path string `json:"path"`
Count float64 `json:"count"`
}
// Summary 面板自监控快照:从 registry 聚合出的精简结构(/api/v1/system/metrics-summary)
type Summary struct {
HTTP struct {
Total float64 `json:"total"`
ByStatus map[string]float64 `json:"byStatus"`
InFlight float64 `json:"inFlight"`
TopPaths []PathCount `json:"topPaths"`
} `json:"http"`
DB struct {
SizeBytes float64 `json:"sizeBytes"`
Saves float64 `json:"saves"`
SaveFailures float64 `json:"saveFailures"`
Records map[string]float64 `json:"records"`
} `json:"db"`
Alerts struct {
Evaluations map[string]float64 `json:"evaluations"`
} `json:"alerts"`
Loki struct {
Queries map[string]float64 `json:"queries"`
} `json:"loki"`
Runtime struct {
Goroutines float64 `json:"goroutines"`
GoVersion string `json:"goVersion"`
Version string `json:"version"`
} `json:"runtime"`
}
// Snapshot 聚合当前指标为面板结构(聚合失败返回零值,监控页降级展示)
func Snapshot() Summary {
s := Summary{}
s.HTTP.ByStatus = map[string]float64{}
s.DB.Records = map[string]float64{}
s.Alerts.Evaluations = map[string]float64{}
s.Loki.Queries = map[string]float64{}
families, err := registry.Gather()
if err != nil {
return s
}
byPath := map[string]float64{}
for _, mf := range families {
for _, m := range mf.GetMetric() {
labels := labelMap(m)
switch mf.GetName() {
case "lokistack_http_requests_total":
v := m.GetCounter().GetValue()
s.HTTP.Total += v
s.HTTP.ByStatus[labels["status"]] += v
byPath[labels["path"]] += v
case "lokistack_http_in_flight_requests":
s.HTTP.InFlight = m.GetGauge().GetValue()
case "lokistack_db_size_bytes":
s.DB.SizeBytes = m.GetGauge().GetValue()
case "lokistack_db_save_total":
v := m.GetCounter().GetValue()
s.DB.Saves += v
if labels["result"] != "success" {
s.DB.SaveFailures += v
}
case "lokistack_db_records":
s.DB.Records[labels["collection"]] = m.GetGauge().GetValue()
case "lokistack_alert_evaluations_total":
s.Alerts.Evaluations[labels["result"]] += m.GetCounter().GetValue()
case "lokistack_loki_queries_total":
s.Loki.Queries[labels["result"]] += m.GetCounter().GetValue()
case "go_goroutines":
s.Runtime.Goroutines = m.GetGauge().GetValue()
case "lokistack_build_info":
s.Runtime.GoVersion = labels["go_version"]
s.Runtime.Version = labels["version"]
}
}
}
paths := make([]PathCount, 0, len(byPath))
for p, c := range byPath {
paths = append(paths, PathCount{Path: p, Count: c})
}
sort.Slice(paths, func(i, j int) bool { return paths[i].Count > paths[j].Count })
if len(paths) > 8 {
paths = paths[:8]
}
s.HTTP.TopPaths = paths
return s
}
func labelMap(m *dto.Metric) map[string]string {
out := map[string]string{}
for _, lp := range m.GetLabel() {
out[lp.GetName()] = lp.GetValue()
}
return out
}
-1
View File
@@ -148,7 +148,6 @@ func SetupRouter() *gin.Engine {
system.Use(middleware.AdminAuth())
{
system.GET("/self-check", systemCtrl.Health)
system.GET("/metrics-summary", middleware.AdminAuth(), systemCtrl.MetricsSummary)
}
// 审计日志:仅管理员可查(含他人操作记录)
+3 -77
View File
@@ -4,9 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
@@ -19,7 +16,7 @@ import (
// ResourceUsage 是页面上「资源」弹框的数据。
//
// 三种部署模式都能采集:
// - k8s-yaml : StatefulSet resources + metrics-server(实时用量)+ 可选 Prometheus(峰值/限流)
// - k8s-yaml : StatefulSet resources + metrics-server(实时用量)
// - host-docker : docker stats / docker inspect(容器限额未设则按宿主机内存做参考)
// - host-binary : ps 采集进程 CPU%/RSS(无 cgroup 限额)
//
@@ -45,11 +42,6 @@ type ResourceUsage struct {
// 限额是否真的配置了(docker 未加 --memory 时为 false,页面不画百分比)
LimitsConfigured bool `json:"limitsConfigured"`
// 可选(需 Prometheus)
CPUPeakMilli *int64 `json:"cpuPeakMilli,omitempty"`
MemPeakBytes *int64 `json:"memPeakBytes,omitempty"`
ThrottlePercent *float64 `json:"throttlePercent,omitempty"`
// 采集受限说明(如缺 metrics.k8s.io 权限、未装 metrics-server、docker 无历史数据)
MetricsNote string `json:"metricsNote,omitempty"`
@@ -148,7 +140,6 @@ func (s *DeploymentService) k8sUsage(ctx context.Context, d *models.LokiDeployme
usage.MetricsNote = "无法解析 metrics-server 返回"
}
s.applyPrometheus(usage)
usage.buildHints()
return usage, nil
}
@@ -167,29 +158,6 @@ func explainMetricsError(err error) string {
}
}
// applyPrometheus 补 24h 峰值与 CPU 限流率(未配置 Prometheus 地址则跳过)。
func (s *DeploymentService) applyPrometheus(u *ResourceUsage) {
base := strings.TrimSpace(os.Getenv("LOKI_USAGE_PROMETHEUS_URL"))
if base == "" || u.Pod == "" {
if u.MetricsNote == "" {
u.MetricsNote = "未配置 Prometheus,暂无 24h 峰值与限流率"
}
return
}
sel := fmt.Sprintf(`pod=%q,namespace=%q,container="loki"`, u.Pod, u.Namespace)
if v, ok := promQueryValue(base, fmt.Sprintf(`max_over_time(sum(rate(container_cpu_usage_seconds_total{%s}[5m]))[24h:1m])`, sel)); ok {
milli := int64(v * 1000)
u.CPUPeakMilli = &milli
}
if v, ok := promQueryValue(base, fmt.Sprintf(`max_over_time(sum(container_memory_working_set_bytes{%s})[24h:1m])`, sel)); ok {
b := int64(v)
u.MemPeakBytes = &b
}
if v, ok := promQueryValue(base, fmt.Sprintf(`100 * sum(rate(container_cpu_cfs_throttled_periods_total{pod=%q,namespace=%q}[15m])) / sum(rate(container_cpu_cfs_periods_total{pod=%q,namespace=%q}[15m]))`, u.Pod, u.Namespace, u.Pod, u.Namespace)); ok {
u.ThrottlePercent = &v
}
}
// ---------- host-docker ----------
func (s *DeploymentService) dockerUsage(ctx context.Context, d *models.LokiDeployment) (*ResourceUsage, error) {
@@ -245,7 +213,7 @@ func (s *DeploymentService) dockerUsage(ctx context.Context, d *models.LokiDeplo
usage.MetricsNote = "docker stats 读取失败:" + err.Error()
}
if usage.MetricsNote == "" {
usage.MetricsNote = "Docker 部署无历史峰值;如需 24h 峰值请接入 cAdvisor/Prometheus"
usage.MetricsNote = "未接入 Prometheus:暂无峰值/限流数据"
}
usage.buildHints()
@@ -287,14 +255,9 @@ func (s *DeploymentService) processUsage(ctx context.Context, d *models.LokiDepl
// ---------- 共用:扩容建议 ----------
func (u *ResourceUsage) buildHints() {
// 峰值需 Prometheus,平台不接入:扩容建议基于 metrics-server 实时值
cpuRef := u.CPUCurrentMilli
if u.CPUPeakMilli != nil && *u.CPUPeakMilli > cpuRef {
cpuRef = *u.CPUPeakMilli
}
memRef := u.MemCurrentBytes
if u.MemPeakBytes != nil && *u.MemPeakBytes > memRef {
memRef = *u.MemPeakBytes
}
var notes []string
if u.CPULimitMilli > 0 {
@@ -319,9 +282,6 @@ func (u *ResourceUsage) buildHints() {
notes = append(notes, "内存偏高")
}
}
if u.ThrottlePercent != nil && *u.ThrottlePercent >= 10 {
notes = append(notes, fmt.Sprintf("CPU 限流 %.0f%%", *u.ThrottlePercent))
}
if !u.Ready {
notes = append(notes, "实例未就绪")
}
@@ -337,40 +297,6 @@ func (u *ResourceUsage) buildHints() {
}
}
// ---------- Prometheus ----------
// promQueryValue 查 Prometheus 瞬时值;未配置或失败返回 ok=false(页面降级)。
func promQueryValue(base, expr string) (float64, bool) {
client := &http.Client{Timeout: 8 * time.Second}
u := strings.TrimSuffix(base, "/") + "/api/v1/query?query=" + url.QueryEscape(expr)
resp, err := client.Get(u)
if err != nil {
return 0, false
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return 0, false
}
var payload struct {
Data struct {
Result []struct {
Value [2]interface{} `json:"value"`
} `json:"result"`
} `json:"data"`
}
if json.NewDecoder(resp.Body).Decode(&payload) != nil || len(payload.Data.Result) == 0 {
return 0, false
}
if len(payload.Data.Result[0].Value) < 2 {
return 0, false
}
raw, ok := payload.Data.Result[0].Value[1].(string)
if !ok {
return 0, false
}
return parseFloatSafe(raw), true
}
// ---------- 单位解析 ----------
// parseQuantityMilli 解析 CPU 用量/限额为毫核。