上篇《用 Prometheus 监控 Docker CouchDB 实例》介绍了用 couchdb-exporter 采集 CouchDB 的运行指标(存活状态、连接数等),通过 Prometheus + Alertmanager 实现指标级告警。
但这个方案有一个盲区:CouchDB 的 Erlang 进程错误不会体现在 Prometheus 指标上。比如 _users 数据库缺失导致的持续报错,exporter 的指标一切正常,只有 docker logs 才能看到 [error]。需要一个日志级别的告警渠道来填补这个空白。
方案选型:为什么是 Loki 链接到标题
已有基础设施:
- Prometheus + Alertmanager 已就绪
- 告警链路已打通到飞书(通过 alert-transformer + OpenClaw)
新增 Loki 不需要 Elasticsearch 集群,只需:
- Alloy:Grafana 出品的统一采集器,采集 CouchDB 容器日志并推送到 Loki
- Loki:日志存储 + Ruler(内建 LogQL 告警引擎)
Loki 的 Ruler 可以直接将日志告警发送到现有的 Alertmanager,复用已有的通知链路,指标告警和日志告警在 Alertmanager 汇合。
整体架构 链接到标题
stdout 日志"] --> B["Alloy
loki.source.docker"] B --> C["Loki
日志存储 + Ruler"] C --> D["Alertmanager
告警去重/路由"] D --> E["alert-transformer
格式化"] E --> F["OpenClaw
18789"] F --> G["飞书通知"] H["Prometheus
指标告警"] --> D
Alloy 部署 链接到标题
Alloy 是一个 Grafana 出品的统一采集器,这里只用它采集 Docker 容器日志。
docker-compose.yaml 链接到标题
services:
alloy:
image: m.daocloud.io/docker.io/grafana/alloy:v1.14.1
container_name: alloy
restart: unless-stopped
ports:
- 12345:12345
volumes:
- ./config.alloy:/etc/alloy/config.alloy:ro
- /var/run/docker.sock:/var/run/docker.sock
command:
- run
- --server.http.listen-addr=0.0.0.0:12345
- --storage.path=/var/lib/alloy/data
- /etc/alloy/config.alloy
config.alloy 链接到标题
// 发现所有 Docker 容器
discovery.docker "local" {
host = "unix:///var/run/docker.sock"
refresh_interval = "5s"
}
discovery.relabel "local" {
targets = discovery.docker.local.targets
// 给 couchdb 容器打上 container=couchdb 标签
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/couchdb$"
target_label = "container"
replacement = "couchdb"
}
}
loki.source.docker "local" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.local.output
forward_to = [loki.process.local.receiver]
refresh_interval = "5s"
}
// 日志处理管道:保留容器标签
loki.process "local" {
stage.labels {
values = {
container = "",
}
}
forward_to = [loki.write.remote.receiver]
}
// 推送到 Loki
loki.write "remote" {
endpoint {
url = "http://loki.example.com:3100/loki/api/v1/push"
tenant_id = "my-tenant"
}
}
为什么没有
stage.json? CouchDB 的日志是 Erlang 文本格式([error] 2026-05-29T09:35:49Z ...),不是结构化 JSON。stage.json会静默解析失败但不影响日志通过,所以这里干脆省略,直接在 LogQL 中用文本匹配。
配置要点:
- 挂载
/var/run/docker.sock让容器发现生效 - 命名组件时应语义化(如
remote、local),方便后续扩展 loki.write的tenant_id需要与 Loki 服务端配置一致
Loki Ruler 告警规则 链接到标题
启用 Ruler 链接到标题
Loki 默认以单体模式运行,Ruler 默认未开启告警推送。需要在 loki-config.yaml 中启用:
ruler:
enable_api: true
enable_alertmanager_v2: true
alertmanager_url: http://alertmanager:9093
poll_interval: 30s
storage:
type: local
local:
directory: /loki/rules
关键参数:
enable_alertmanager_v2: true启用 v2 API 发送告警(兼容新版 Alertmanager)alertmanager_url指向现有 Alertmanager 地址poll_interval: 30s规则文件变更后自动加载的间隔
规则文件 链接到标题
Loki Ruler 的规则文件按 /<rules_dir>/<tenant_id>/<rule_group>.yaml 存放。新建 couchdb.yaml:
groups:
- name: couchdb_errors
interval: 30s
rules:
- alert: CouchDBError
expr: |
count_over_time({container="couchdb"}
|~ "\\[error\\]" [5m]) > 0
labels:
severity: warning
annotations:
summary: "CouchDB 运行错误"
description: "CouchDB 产生 Erlang ERROR 日志"
- alert: CouchDBManyErrors
expr: |
count_over_time({container="couchdb"}
|~ "\\[error\\]" [5m]) > 5
for: 10m
labels:
severity: critical
annotations:
summary: "CouchDB 持续错误"
description: "CouchDB 最近持续产生错误,需人工介入"
LogQL 语法要点 链接到标题
| 日志格式 | 匹配方式 | 适用场景 |
|---|---|---|
| JSON 结构化 | | json | level = "ERROR" |
Nginx JSON、Node.js 结构化日志 |
| 纯文本 | |~ "\\\\[error\\\\]" |
CouchDB Erlang 日志、Syslog |
|~ 是 LogQL 的行过滤正则匹配,等同于 grep。对于 CouchDB 这种非 JSON 日志,这是最简单的匹配方式。
规则文件权限注意:Loki 容器内以 uid 10001 运行,规则文件所在目录需要
chown 10001:10001确保可读。
Alertmanager 配置 链接到标题
现有的 Alertmanager 配置不需要修改,Loki Ruler 的 alertmanager_url 指向即可。以下是完整配置(约 15 行):
global:
resolve_timeout: 5m
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'openclaw'
receivers:
- name: 'openclaw'
webhook_configs:
- url: 'http://alert-transformer:9091/alertmanager'
send_resolved: true
alert-transformer 是一个轻量级服务,负责将 Alertmanager 的 webhook 请求格式化后转发到 OpenClaw,最终推送到飞书。这条链路在指标告警中已经验证过,日志告警直接复用,不需要新增路由规则。
验证方法 链接到标题
# 1. 检查 couchdb 标签是否已录入 Loki
curl -s -H "X-Scope-OrgID: my-tenant" \
"http://loki.example.com:3100/loki/api/v1/label/container/values" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['data'])"
# 2. 检查 Ruler 规则是否加载成功
curl -s -H "X-Scope-OrgID: my-tenant" \
"http://loki.example.com:3100/loki/api/v1/rules" | grep "CouchDB"
# 3. 直接查询 CouchDB 错误日志
curl -s -H "X-Scope-OrgID: my-tenant" \
"http://loki.example.com:3100/loki/api/v1/query_range?\
query=%7Bcontainer%3D%22couchdb%22%7D%20%7C~%20%22%5C%5Berror%5C%5D%22&limit=3"
# 4. 确认 Alertmanager 收到告警
curl -s "http://alertmanager:9093/api/v2/alerts" \
| python3 -c "import sys,json;d=json.load(sys.stdin);\
[print(a['labels']['alertname']) for a in d]"
总结 链接到标题
这套模式的优势在于:
- 复用现有基础设施——Loki 的 Ruler 直接发给已有的 Alertmanager,不需要额外的通知配置
- 日志格式无关——无论是 JSON 还是文本日志,Alloy 采集层不需要感知,区别只在 LogQL 规则的写法
- 可扩展到任意容器——只需要改
discovery.relabel的匹配规则 + 新增一个 YAML 规则文件,就可以把任意容器的日志接入告警
对比之前的指标监控方案:
| 维度 | Prometheus + Exporter(指标) | Loki Ruler(日志) |
|---|---|---|
| 发现的问题 | 服务宕机、资源耗尽 | 程序逻辑错误、异常堆栈 |
| 告警延迟 | 秒级 | 秒级(30s 轮询) |
| 配置复杂度 | 低 | 低 |
| 覆盖范围 | 事先暴露的 metrics | 任意 stdout 文本 |
两者互补,覆盖了"服务器挂了"(指标)和"程序出错了"(日志)两个维度。