From 530da2b1b4657da39bca99ff2c9820d61f1b7188 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 21 Oct 2026 10:51:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(monitoring):=20=E7=9B=91=E6=8E=A7=E7=BC=96?= =?UTF-8?q?=E6=8E=92=E7=BB=9F=E4=B8=80=E5=88=B0=20FruitServer=20compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把原先散落在 D:\fruitshorts\monitoring 的手工栈收编进仓库:prometheus 增加 cAdvisor 与 blackbox 抓取、15 天保留期;grafana 改为 provisioning 自动加载数据源与看板;新增 blackbox/cadvisor 服务;Grafana 管理员密码改走环境变量(必填),不再硬编码 admin/admin。 --- .env.example | 5 + blackbox/blackbox.yml | 11 +++ docker-compose.yml | 41 +++++++- grafana/dashboards/backend-overview.json | 93 +++++++++++++++++++ .../provisioning/dashboards/dashboards.yml | 14 +++ .../provisioning/datasources/prometheus.yml | 14 +++ prometheus.yml | 35 ++++++- 7 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 blackbox/blackbox.yml create mode 100644 grafana/dashboards/backend-overview.json create mode 100644 grafana/provisioning/dashboards/dashboards.yml create mode 100644 grafana/provisioning/datasources/prometheus.yml diff --git a/.env.example b/.env.example index 8b262ac..12b9457 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,11 @@ MEDIA_ROOT_DIR=/data/uploads # false = 两个都关闭(生产若不希望暴露 UI 就设 false,机器可读契约也一并关闭) SWAGGER_ENABLED=true +# ── 监控 ─────────────────────────────────────────────── +# Grafana 管理员密码(必填):3000 端口对外开放,不允许用默认 admin/admin +GRAFANA_ADMIN_USER=admin +GRAFANA_ADMIN_PASSWORD= + # ── 可选:Google Play 商店票据校验(不配则保持 disabled)── STORE_VERIFIER=disabled GOOGLE_PLAY_PACKAGE_NAME= diff --git a/blackbox/blackbox.yml b/blackbox/blackbox.yml new file mode 100644 index 0000000..7e78c60 --- /dev/null +++ b/blackbox/blackbox.yml @@ -0,0 +1,11 @@ +# Author: fruit.com.cn +# blackbox-exporter 探针模块:只探测 HTTP 2xx,跟随重定向 +modules: + http_2xx: + prober: http + timeout: 5s + http: + preferred_ip_protocol: ip4 + valid_http_versions: ["HTTP/1.1", "HTTP/2.0"] + valid_status_codes: [200] + follow_redirects: true diff --git a/docker-compose.yml b/docker-compose.yml index 72c8a1b..27e84d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -149,6 +149,7 @@ services: retries: 3 # ─────────── 监控 ─────────── + # 编排统一在这里:prometheus(应用指标 + 容器指标 + 黑盒探活)+ grafana(自动加载看板) prometheus: image: prom/prometheus:latest container_name: fruitshorts-prometheus @@ -160,6 +161,15 @@ services: - prometheus-data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" + # 15 天保留期,避免磁盘无限增长 + - "--storage.tsdb.retention.time=15d" + - "--web.enable-lifecycle" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9090/-/healthy >/dev/null || exit 1"] + interval: 30s + timeout: 5s + start_period: 20s + retries: 3 grafana: image: grafana/grafana:latest @@ -169,9 +179,36 @@ services: - "3000:3000" volumes: - grafana-data:/var/lib/grafana + # 数据源与看板全部来自仓库,换机器不需要手工配 + - ./grafana/provisioning:/etc/grafana/provisioning:ro + - ./grafana/dashboards:/var/lib/grafana/dashboards:ro environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=admin + GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Grafana 管理员密码必填,见 .env.example} + GF_USERS_ALLOW_SIGN_UP: "false" + TZ: Asia/Shanghai + depends_on: + - prometheus + + # 黑盒探活:从外部探测接口可用性与耗时 + blackbox: + image: prom/blackbox-exporter:latest + container_name: fruitshorts-blackbox + restart: unless-stopped + volumes: + - ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro + + # 容器资源指标(CPU / 内存 / 网络 / 重启),供 Prometheus 抓取 + cadvisor: + image: gcr.io/cadvisor/cadvisor:latest + container_name: fruitshorts-cadvisor + restart: unless-stopped + privileged: true + volumes: + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro volumes: # 命名卷:跟着 compose 项目走,`docker compose down -v` 才会删 diff --git a/grafana/dashboards/backend-overview.json b/grafana/dashboards/backend-overview.json new file mode 100644 index 0000000..016fcb6 --- /dev/null +++ b/grafana/dashboards/backend-overview.json @@ -0,0 +1,93 @@ +{ + "uid": "fruitshorts-backend", + "title": "FruitShorts 后端监控", + "tags": ["fruitshorts", "backend"], + "timezone": "browser", + "editable": true, + "schemaVersion": 39, + "version": 1, + "refresh": "30s", + "time": { "from": "now-6h", "to": "now" }, + "panels": [ + { + "id": 1, + "type": "stat", + "title": "服务探活(1=正常 0=异常)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 0, "y": 0, "w": 8, "h": 6 }, + "options": { + "colorMode": "background", + "graphMode": "none", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false } + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { "type": "value", "options": { "0": { "text": "异常", "color": "red", "index": 0 } } }, + { "type": "value", "options": { "1": { "text": "正常", "color": "green", "index": 1 } } } + ], + "thresholds": { "mode": "absolute", "steps": [{ "color": "red", "value": null }, { "color": "green", "value": 1 }] } + }, + "overrides": [] + }, + "targets": [ + { "refId": "A", "expr": "probe_success{job=\"blackbox\"}", "legendFormat": "{{instance}}" } + ] + }, + { + "id": 2, + "type": "timeseries", + "title": "接口探测耗时(秒)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 8, "y": 0, "w": 16, "h": 6 }, + "fieldConfig": { "defaults": { "unit": "s", "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [ + { "refId": "A", "expr": "probe_duration_seconds{job=\"blackbox\"}", "legendFormat": "{{instance}}" } + ] + }, + { + "id": 3, + "type": "timeseries", + "title": "容器 CPU 使用(核)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 0, "y": 6, "w": 12, "h": 8 }, + "fieldConfig": { "defaults": { "unit": "short", "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [ + { "refId": "A", "expr": "sum(rate(container_cpu_usage_seconds_total{name=~\"fruitshorts-(app-api|admin-api|db|redis|transcode-worker)\"}[1m])) by (name)", "legendFormat": "{{name}}" } + ] + }, + { + "id": 4, + "type": "timeseries", + "title": "容器内存(GiB)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 12, "y": 6, "w": 12, "h": 8 }, + "fieldConfig": { "defaults": { "unit": "bytes", "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [ + { "refId": "A", "expr": "sum(container_memory_working_set_bytes{name=~\"fruitshorts-(app-api|admin-api|db|redis|transcode-worker)\"}) by (name)", "legendFormat": "{{name}}" } + ] + }, + { + "id": 5, + "type": "timeseries", + "title": "容器网络接收(B/s)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 0, "y": 14, "w": 12, "h": 8 }, + "fieldConfig": { "defaults": { "unit": "Bps", "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [ + { "refId": "A", "expr": "sum(rate(container_network_receive_bytes_total{name=~\"fruitshorts-(app-api|admin-api|db|redis|transcode-worker)\"}[1m])) by (name)", "legendFormat": "{{name}}" } + ] + }, + { + "id": 6, + "type": "timeseries", + "title": "Prometheus 抓取目标状态(1=UP)", + "datasource": { "type": "prometheus", "uid": "prometheus" }, + "gridPos": { "x": 12, "y": 14, "w": 12, "h": 8 }, + "fieldConfig": { "defaults": { "unit": "short", "min": 0, "max": 1, "custom": { "lineWidth": 2, "fillOpacity": 10 } }, "overrides": [] }, + "targets": [ + { "refId": "A", "expr": "up", "legendFormat": "{{job}} {{instance}}" } + ] + } + ] +} diff --git a/grafana/provisioning/dashboards/dashboards.yml b/grafana/provisioning/dashboards/dashboards.yml new file mode 100644 index 0000000..5261713 --- /dev/null +++ b/grafana/provisioning/dashboards/dashboards.yml @@ -0,0 +1,14 @@ +# Author: fruit.com.cn +# 看板自动加载:容器内 /var/lib/grafana/dashboards 挂载的是仓库里的 grafana/dashboards +apiVersion: 1 + +providers: + - name: 'FruitShorts' + orgId: 1 + folder: 'FruitShorts' + type: file + disableDeletion: false + updateIntervalSeconds: 30 + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards diff --git a/grafana/provisioning/datasources/prometheus.yml b/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..c1806ba --- /dev/null +++ b/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,14 @@ +# Author: fruit.com.cn +# Grafana 数据源:compose 内网直连 prometheus 服务 +apiVersion: 1 + +datasources: + - name: Prometheus + uid: prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + jsonData: + timeInterval: 15s diff --git a/prometheus.yml b/prometheus.yml index bd743fb..de92269 100644 --- a/prometheus.yml +++ b/prometheus.yml @@ -1,8 +1,13 @@ # Author: fruit.com.cn -# Prometheus 抓取配置:抓取后端双服务的 actuator 指标(服务名走 compose 网络) +# Prometheus 抓取配置:全部走 compose 内网的服务名解析。 +# - 应用指标:Spring Boot actuator +# - 容器指标:cAdvisor +# - 探活/耗时:blackbox-exporter(不依赖后端代码,从外部看接口是否可用) global: scrape_interval: 15s evaluation_interval: 15s + external_labels: + project: fruitshorts scrape_configs: - job_name: "fruitshorts-app-api" @@ -14,3 +19,31 @@ scrape_configs: metrics_path: "/actuator/prometheus" static_configs: - targets: ["admin-api:8080"] + + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + + # 容器 CPU / 内存 / 网络 / 重启次数 + - job_name: "cadvisor" + static_configs: + - targets: ["cadvisor:8080"] + + # 入口探活:healthz 用于存活判定,两个业务接口用于可用性与耗时 + - job_name: "blackbox" + metrics_path: /probe + params: + module: [http_2xx] + static_configs: + - targets: + - http://app-api:8081/healthz + - http://admin-api:8080/healthz + - http://app-api:8081/app/categories + - http://app-api:8081/app/home/feed + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + - source_labels: [__param_target] + target_label: instance + - target_label: __address__ + replacement: blackbox:9115