前言 链接到标题
PostgreSQL 是生产环境最常用的关系型数据库之一。当服务挂了、连接爆了、死锁了,需要第一时间感知。
单纯靠 Prometheus 指标可以告诉你「连接数超了」,但说不出原因;单纯靠日志可以告诉你「too many connections」,但没有量化趋势。
本文的方案是 指标 + 日志协同监控,形成完整可观测性闭环:
- 指标告警 → 感知异常(量变)
- 日志告警 → 定位根因(质变)
- 统一通知 → 飞书即时推送
架构 链接到标题
下面是完整的数据流:
monkey:5432] --> PE[postgres-exporter
monkey:9187] PE --> PM[Prometheus
robin:9090] PM --> AM[Alertmanager
robin:9093] PG -->|Docker logs| AL[Alloy
monkey:12346] AL --> LK[Loki
robin:3100] LK -->|Loki Ruler LogQL| AM AM --> AT[alert-transformer
robin:9091] AT --> OC[OpenClaw
rivo:18789] OC --> FS[飞书] subgraph 指标路径 PE PM end subgraph 日志路径 AL LK end subgraph 通知路径 AM AT OC FS end
两条路径独立采集、独立告警,最终汇总到同一个通知链路。
一、部署环境 链接到标题
| 主机 | IP | 角色 |
|---|---|---|
| monkey | 192.168.0.73 | PostgreSQL 17 + postgres-exporter + Alloy |
| robin | 192.168.0.81 | Prometheus + Loki + Alertmanager |
PostgreSQL 版本 17.9,部署在 Docker 中(m.daocloud.io/docker.io/postgres:17-alpine),已开启 WAL 逻辑复制用于上游业务。
二、指标监控 — postgres_exporter + Prometheus 链接到标题
部署 postgres-exporter 链接到标题
在 monkey 上新建目录和 docker-compose:
# /opt/postgres-exporter/docker-compose.yaml
services:
postgres-exporter:
image: m.daocloud.io/docker.io/prometheuscommunity/postgres-exporter:latest
container_name: postgres-exporter
restart: always
network_mode: host
environment:
- DATA_SOURCE_NAME=postgresql://postgres:password@localhost:5432/postgres?sslmode=disable
command:
- '--web.listen-address=:9187'
关键点:
network_mode: host— 直接通过 localhost 连接 PG,绕开 Docker 桥接网络DATA_SOURCE_NAME— 数据库连接串,密码用实际值
启动:
docker compose up -d
# 验证
curl -s http://localhost:9187/metrics | grep pg_up
pg_up 1
输出约 1200+ 个 pg_ 指标,覆盖连接数、锁、死锁、事务、缓存命中率、复制延迟、数据库大小等。
Prometheus 抓取配置 链接到标题
新增 job:
# monitor/prometheus/prometheus.yml
- job_name: postgres_monkey
static_configs:
- targets:
- 192.168.0.73:9187
labels:
hostname: monkey
Prometheus 告警规则 链接到标题
# monitor/prometheus/alerts.yml
- alert: PostgresDown
expr: pg_up{server="localhost:5432"} == 0
for: 1m
labels:
severity: critical
- alert: PostgresConnectionUsageHigh
expr: sum by(server) (pg_stat_activity_count{datname!~"template.*"})
/ pg_settings_max_connections * 100 > 85
for: 5m
labels:
severity: warning
- alert: PostgresDeadlocksDetected
expr: rate(pg_stat_database_deadlocks[5m]) > 0
for: 1m
labels:
severity: warning
三、日志监控 — Alloy + Loki + LogQL 链接到标题
部署 Alloy 链接到标题
在 monkey 上新开目录,部署独立的 Alloy 实例专门采集 postgres 日志:
# /opt/alloy-postgres/docker-compose.yaml
services:
alloy:
image: m.daocloud.io/docker.io/grafana/alloy:latest
container_name: alloy-postgres
restart: always
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config.alloy:/etc/alloy/config.alloy
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12346
Alloy 配置详解 链接到标题
// 发现 Docker 容器
discovery.docker "postgres" {
host = "unix:///var/run/docker.sock"
refresh_interval = "5s"
}
// 只保留 postgres17 容器,重命名为 container 标签
discovery.relabel "postgres" {
targets = discovery.docker.postgres.targets
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(postgres17)$"
target_label = "container"
}
}
// 采集 Docker 日志
loki.source.docker "postgres" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.postgres.output
forward_to = [loki.process.postgres.receiver]
refresh_interval = "5s"
}
// 解析纯文本日志格式,提取 level 标签
loki.process "postgres" {
stage.regex {
expression = "(?P<ts>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\w+) \\[(?P<pid>\\d+)\\] (?P<level>\\w+):\\s+(?P<msg>.*)"
}
stage.labels {
values = {
level = "",
container = "",
}
}
forward_to = [loki.write.robin.receiver]
}
// 发送到中央 Loki
loki.write "robin" {
endpoint {
url = "http://192.168.0.81:3100/loki/api/v1/push"
tenant_id = "tenant1"
}
}
三个坑 链接到标题
-
正则必须有 capture group
// 错误:$1 会是整个匹配值,label 变成 "/postgres17" regex = "/postgres17$" // 正确:用 () 捕获纯名称 "postgres17" regex = "/(postgres17)$" -
PostgreSQL jsonlog 在 Docker 中不可用
尝试过
log_destination=jsonlog,但 PG 的 jsonlog 仅在logging_collector=on时输出 JSON,开启后日志写入文件而非 stderr,Docker 采集不到。最终使用stage.regex解析传统纯文本格式。 -
Alloy 端口冲突
monkey 与 robin 会在同一 subnet,如果 robin Alloy 用了 :12345,monkey 这个实例改用 :12346 避免冲突。
Loki 日志告警 — LogQL 规则 链接到标题
# /opt/monitor/loki-data/rules/tenant1/postgres.yaml
groups:
- name: postgres_monkey_errors
interval: 30s
rules:
- alert: PostgresFatalError
expr: |
count_over_time({container="postgres17"}
|~ "FATAL|PANIC" [5m]) > 0
for: 1m
labels:
severity: critical
- alert: PostgresErrorMessage
expr: |
count_over_time({container="postgres17"}
|~ "ERROR" [5m]) > 2
for: 2m
labels:
severity: warning
- alert: PostgresConnectionLimit
expr: |
count_over_time({container="postgres17"}
|~ "too many connections" [5m]) > 0
labels:
severity: critical
- alert: PostgresDeadlock
expr: |
count_over_time({container="postgres17"}
|~ "deadlock detected" [5m]) > 0
labels:
severity: warning
- alert: PostgresOOMKill
expr: |
count_over_time({container="postgres17"}
|~ "[Oo]ut of memory|OOM|killing process" [5m]) > 0
labels:
severity: critical
注意权限:Loki API 需要 X-Scope-OrgID header,查询和写入都要带上。
四、告警通知链路 链接到标题
- Alertmanager (
robin:9093) — 接收 Prometheus 和 Loki Ruler 推送的告警 - alert-transformer (
robin:9091) — 格式转换、去重、分级路由 - OpenClaw (
rivo:18789) — AI Agent Gateway,最终推送到飞书群
五、验证与维护 链接到标题
验证命令 链接到标题
# 检查 exporter 是否抓取到 PG
ssh robin "curl -s 'http://localhost:9090/api/v1/query?query=pg_up'"
# 检查 Loki 中 postgres 日志
ssh robin "curl -s -G -H 'X-Scope-OrgID: tenant1' \
'http://localhost:3100/loki/api/v1/query_range' \
--data-urlencode 'query={container=\"postgres17\"}' \
--data-urlencode 'limit=3'"
# 查看 Loki ruler 加载的规则
ssh robin "curl -s -H 'X-Scope-OrgID: tenant1' \
http://localhost:3100/loki/api/v1/rules"
# 查看 Prometheus 告警状态
ssh robin "curl -s http://localhost:9090/api/v1/alerts"
查看 Alloy 状态 链接到标题
# alloy 日志
ssh monkey "docker logs alloy-postgres --tail 20"
# 检查链路:Loki 是否有指标过来
ssh robin "curl -s -G -H 'X-Scope-OrgID: tenant1' \
'http://localhost:3100/loki/api/v1/query' \
--data-urlencode 'query=count_over_time({container=\"postgres17\"}[5m])'"
日常巡检 链接到标题
- 指标告警 → 关注连接数使用率、死锁速率、事务回滚率
- 日志告警 → 关注 ERROR/FATAL 级别日志、连接限流、OOM
- 对比两套告警触发时间差,调整阈值使日志告警比指标告警更早触发
六、总结 链接到标题
| 维度 | 指标告警 | 日志告警 |
|---|---|---|
| 监控工具 | Prometheus + postgres-exporter | Loki + LogQL |
| 覆盖场景 | 连接数/缓存/事务/复制/磁盘 | FATAL/ERROR/deadlock/OOM |
| 优势 | 量化、趋势、历史对比 | 文本匹配、根因分析 |
| 局限 | 只能看数值,看不清原因 | 容易被大量 LOG 淹没 |
最佳实践:两条腿走路。指标做第一道防线,日志做第二道深度分析。
完整文件清单 链接到标题
| 文件 | 位置 |
|---|---|
docker-compose.yaml |
monkey:/opt/postgres-exporter/ |
config.alloy |
monkey:/opt/alloy-postgres/ |
docker-compose.yaml |
monkey:/opt/alloy-postgres/ |
prometheus.yml |
robin:/opt/monitor/prometheus/ |
alerts.yml |
robin:/opt/monitor/prometheus/ |
postgres.yaml |
robin:/opt/monitor/loki-data/rules/tenant1/ |
本文的实现全部开源可复现,完整配置见 github.com/MarshalW/devops。