上篇《用 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 汇合。

整体架构 链接到标题

flowchart LR A["CouchDB 容器
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 让容器发现生效
  • 命名组件时应语义化(如 remotelocal),方便后续扩展
  • loki.writetenant_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]"

总结 链接到标题

这套模式的优势在于:

  1. 复用现有基础设施——Loki 的 Ruler 直接发给已有的 Alertmanager,不需要额外的通知配置
  2. 日志格式无关——无论是 JSON 还是文本日志,Alloy 采集层不需要感知,区别只在 LogQL 规则的写法
  3. 可扩展到任意容器——只需要改 discovery.relabel 的匹配规则 + 新增一个 YAML 规则文件,就可以把任意容器的日志接入告警

对比之前的指标监控方案:

维度 Prometheus + Exporter(指标) Loki Ruler(日志)
发现的问题 服务宕机、资源耗尽 程序逻辑错误、异常堆栈
告警延迟 秒级 秒级(30s 轮询)
配置复杂度
覆盖范围 事先暴露的 metrics 任意 stdout 文本

两者互补,覆盖了"服务器挂了"(指标)和"程序出错了"(日志)两个维度。