
Metrics & Monitoring:用數據取代猜測
服務跑得好不好,不能等使用者回報才知道。Metrics(指標)是一組時間序列的數值,描述系統在每個時間點的狀態——CPU 使用率、記憶體、請求數、錯誤率、響應時間。Prometheus 負責收集和儲存這些指標,Grafana 負責視覺化。這兩者搭配起來,就是可觀測性的第一根支柱(metrics、logs、traces 三根支柱中最基礎的)。
架構概覽
flowchart LR App[應用程式\n/metrics] -->|scrape| Prom[Prometheus\n指標收集與儲存] Exp[Exporters\nNode / DB / Nginx] -->|scrape| Prom Prom -->|查詢| Grafana[Grafana\n儀表板視覺化] Prom -->|規則評估| AM[Alertmanager\n告警通知] AM -->|通知| Notify[Slack / Email] Grafana --> Dashboard[Dashboard\n即時監控面板]
架構概覽
flowchart TD App[Application\n/metrics endpoint] -->|scrape| Prometheus[Prometheus\n:9090] NodeExp[Node Exporter\nHost 層級指標] -->|scrape| Prometheus PgExp[pg_exporter\nPostgreSQL 指標] -->|scrape| Prometheus NginxExp[nginx_exporter\nNginx 指標] -->|scrape| Prometheus CAdvisor[cAdvisor\nContainer 指標] -->|scrape| Prometheus Prometheus -->|query| Grafana[Grafana\n:3000] Prometheus -->|evaluate| AlertManager[Alertmanager\n:9093] AlertManager -->|notify| Slack[Slack / Discord] AlertManager -->|notify| Email[Email] Grafana --> Dashboard[Dashboard\nHost / App / DB]
Prometheus 定期 scrape(拉取)各個 exporter 和應用服務的 /metrics endpoint。Grafana 連接 Prometheus 作為 data source 來建立 dashboard。Alertmanager 處理告警通知。
核心概念
-
Pull vs Push 模型:Prometheus 採用 Pull 模型——Prometheus 主動去各個 target 抓取指標,而不是由 target 推送到 Prometheus。好處是 Prometheus 可以知道 target 是否存活(scrape 失敗 = target 可能掛了),也不需要在 target 端設定 Prometheus 的位址。缺點是短暫存活的 job(例如 batch script)來不及被 scrape,需要用 Pushgateway 來處理。
-
Exporter 生態:Prometheus 不直接監控服務,而是透過 Exporter 轉換各種服務的指標為 Prometheus 格式。常用的 Exporter:
node_exporter(Host 的 CPU/Memory/Disk/Network)、cadvisor(Docker 容器的資源使用)、postgres_exporter(PostgreSQL 的連線數/查詢數/cache hit ratio)、nginx-prometheus-exporter(Nginx 的請求數/連線數/upstream 狀態)。只要裝上對應的 Exporter,就能在 Grafana 看到指標,不需要修改應用程式碼。 -
PromQL 查詢語言:Prometheus 用 PromQL 來查詢和聚合指標。常用的函數:
rate()(計算增長率,適合 counter 類型的指標)、increase()(一段時間的增量)、histogram_quantile()(計算百分位數,例如 p99 響應時間)。PromQL 是建立 dashboard 和告警規則的基礎,值得花時間學會基本用法。 -
Dashboard 設計原則:不要把所有指標都塞在一個 dashboard 裡。建議分層:Overview dashboard(整體健康狀態,一眼看出有沒有問題)、Service dashboard(單一服務的詳細指標)、Debug dashboard(排查特定問題用的深入指標)。每個 panel 要有明確的標題和單位,不要讓看 dashboard 的人還要猜這個數字代表什麼。
使用情境
-
日常巡檢:每天早上打開 Grafana Overview dashboard,看 Host 的 CPU/Memory/Disk 是否正常、各服務的 error rate 是否為零、資料庫的連線數和 cache hit ratio 是否在合理範圍。10 秒鐘就能知道系統狀態。
-
效能排查:使用者反映網頁變慢。打開 Grafana 看 API 的 p99 響應時間從 200ms 升到 3000ms、PostgreSQL 的 active connections 從 30 飆到 95(接近 max_connections)、cache hit ratio 從 99% 降到 80%。問題定位:連線數太多導致查詢排隊。解法:調整 PgBouncer pool size。
-
容量規劃:每個月 review 一次 disk usage 趨勢。如果過去 3 個月磁碟使用量每月增長 10GB,現在剩餘 50GB,代表大約 5 個月後要擴容或清理。有了趨勢數據,就可以提前規劃,而不是等到磁碟滿了才緊急處理。
實作範例 / 設定範例
Prometheus + Grafana + Exporters 部署
# docker-compose.monitoring.yml
version: "3.8"
services:
prometheus:
image: prom/prometheus:latest
restart: unless-stopped
ports:
- "127.0.0.1:9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./alert-rules.yml:/etc/prometheus/alert-rules.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=10GB'
grafana:
image: grafana/grafana:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana-data:/var/lib/grafana
node-exporter:
image: prom/node-exporter:latest
restart: unless-stopped
ports:
- "127.0.0.1:9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
volumes:
prometheus-data:
grafana-data:prometheus.yml 設定
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert-rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
# Prometheus 自身指標
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Host 指標
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
# Container 指標
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
# PostgreSQL 指標
- job_name: 'postgresql'
static_configs:
- targets: ['postgres-exporter:9187']
# Nginx 指標
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
# Application 指標(如果應用有 /metrics endpoint)
- job_name: 'api'
metrics_path: /metrics
static_configs:
- targets: ['api:8000']告警規則範例
# alert-rules.yml
groups:
- name: host
rules:
- alert: HighCPU
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
annotations:
summary: "CPU usage > 85% for 5 minutes"
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 20
for: 5m
labels:
severity: critical
annotations:
summary: "Disk space < 20% on {{ $labels.instance }}"
- alert: HighMemory
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 5m
labels:
severity: warning
annotations:
summary: "Memory usage > 90%"
- name: postgresql
rules:
- alert: PostgreSQLDown
expr: pg_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "PostgreSQL is down"
- alert: PostgreSQLHighConnections
expr: sum(pg_stat_activity_count) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL connections > 80"Metrics + Logs:完整的系統監控
Metrics 和 Logs 是系統監控的兩根支柱,各有所長:
| 面向 | Metrics(Prometheus) | Logs(EFK) |
|---|---|---|
| 回答的問題 | 「發生了什麼?嚴重嗎?」 | 「為什麼發生?細節是什麼?」 |
| 資料形式 | 數值(CPU 85%、QPS 1200) | 文字(error trace、request body) |
| 適合場景 | 即時告警、趨勢分析、容量規劃 | 錯誤排查、安全稽核、事件回溯 |
| 保留成本 | 低(時間序列壓縮效率高) | 高(全文索引佔空間) |
典型的監控排查流程:
Grafana 看到 API error rate 飆高(Metrics)
→ 確認是哪個 endpoint、哪個時間段
→ 切到 Kibana 搜尋同時段的 error log(Logs)
→ 找到具體的 error message 和 stack trace
→ 定位問題根因並修復
建議的建置順序:先把 Prometheus + Grafana 架好(本篇),確認 Metrics 收集正常;再架 EFK Stack,把日誌也集中收集;最後設定 Alertmanager 把告警串到 Slack/Discord。三者合起來就是完整的系統監控方案。
常見問題與風險
-
Prometheus 磁碟空間耗盡:Prometheus 的 TSDB 隨時間增長,如果 scrape 的 target 很多、指標很密,資料量增長很快。Prometheus 掛了就沒有監控,變成「監控系統本身沒有被監控」。避免方式:設定
--storage.tsdb.retention.time=30d和--storage.tsdb.retention.size=10GB限制儲存上限。 -
Scrape target 掛了沒發現:Prometheus 配了 scrape job,但 target 的 exporter 沒裝好或掛了,Prometheus 只會顯示
up == 0,如果沒有對應的告警規則,就不會有人知道。避免方式:為每個 job 設定up == 0的告警規則。 -
Dashboard 太多太雜:每個人都建自己的 dashboard,最後 Grafana 裡有 50 個 dashboard 但沒有人維護。避免方式:建立 dashboard 命名規範和分層結構、定期清理不再使用的 dashboard、用 provisioning 把核心 dashboard 寫成 JSON 檔案進 Git。
-
告警疲勞:告警規則設得太敏感(例如 CPU > 50% 就告警),導致每天收到幾十封告警通知,團隊開始忽略所有告警。避免方式:告警閾值要合理(Warning 85%、Critical 95%)、設定
for持續時間避免瞬間尖峰觸發告警、分級處理(Critical 立即通知、Warning 只記錄)。
優點
- Pull 模型讓 target 不需要知道 Prometheus 的存在,部署簡單
- Exporter 生態豐富,幾乎所有常見服務都有現成的 Exporter
- PromQL 強大且靈活,能做複雜的聚合和計算
缺點 / 限制
- Prometheus 單節點有容量上限,超大規模需要 Thanos 或 Cortex
- Pull 模型不適合短暫存活的 job(需要 Pushgateway)
- Grafana 的 dashboard JSON 不太好 review 和版本控管