前面的监控系列文章:
- CouchDB 日志告警 → Alloy + Loki + LogQL
- Windmill 日志告警 → Alloy + Loki + LogQL
- 告警通知到飞书 → Alertmanager → transformer → OpenClaw
- MinIO 指标告警 → 内置 metrics + 一行环境变量
- Qdrant 指标告警(本篇)→ 内置 metrics + API key 认证
这篇和 MinIO 一样是"内置 metrics 路线",但 Qdrant 没有像 MinIO 那样提供 MINIO_PROMETHEUS_AUTH_TYPE=public 的一键开关,API key 认证需要在 Prometheus 侧用 bearer_token_file 解决。
Qdrant 内置 metrics 链接到标题
Qdrant 从 v1.10 起在 API 端口 :6333 暴露 Prometheus 兼容的 metrics 端点:
GET http://<qdrant-host>:6333/metrics
默认需要 API key 认证。直接访问会返回 401 Unauthorized:
$ curl -i http://localhost:6333/metrics
HTTP/1.1 401 Unauthorized
content-type: text/plain
{"status":{"error":"Unauthorized"}}
带 API key 后正常返回 200:
$ curl -H 'api-key: <YOUR_QDRANT_API_KEY>' http://localhost:6333/metrics
# HELP app_info information about qdrant server
# TYPE app_info gauge
app_info{name="qdrant",version="1.16.3"} 1
...
不需要额外部署
qdrant-exporter——社区 exporter 只是对内置端点的薄包装。
配置步骤 链接到标题
1. 准备 API key 文件 链接到标题
API key 不能明文写进 prometheus.yml(会进 git 仓库)。仓库内单独存一个文件,并加入 .gitignore:
# 仓库目录
echo '<YOUR_QDRANT_API_KEY>' > monitor/prometheus/qdrant_api_key
chmod 600 monitor/prometheus/qdrant_api_key
.gitignore 追加:
monitor/prometheus/qdrant_api_key
2. Prometheus 添加 scrape job 链接到标题
在 prometheus.yml 的 scrape_configs 末尾新增:
- job_name: qdrant
metrics_path: /metrics
scheme: http
bearer_token_file: /etc/prometheus/qdrant_api_key
static_configs:
- targets:
- <qdrant-host>:6333
labels:
hostname: <qdrant-host>
部署到监控服务器时,API key 文件放在 prometheus 容器挂载的卷目录下,路径为 /etc/prometheus/qdrant_api_key(对应仓库的 monitor/prometheus/qdrant_api_key)。
3. 部署与重启 链接到标题
# 同步到监控服务器
scp monitor/prometheus/prometheus.yml <monitor-host>:/tmp/
scp monitor/prometheus/alerts.yml <monitor-host>:/tmp/
scp monitor/prometheus/qdrant_api_key <monitor-host>:/tmp/
ssh <monitor-host> "
sudo cp /tmp/prometheus.yml /opt/monitor/prometheus/ &&
sudo cp /tmp/alerts.yml /opt/monitor/prometheus/ &&
sudo cp /tmp/qdrant_api_key /opt/monitor/prometheus/ &&
sudo chown 65534:65534 /opt/monitor/prometheus/qdrant_api_key &&
sudo chmod 600 /opt/monitor/prometheus/qdrant_api_key &&
cd /opt/monitor && sudo docker compose restart prometheus
"
chown 65534:65534不可省略,原因见第六章"踩坑 3"。
暴露的指标 链接到标题
始终暴露的指标(v1.16+) 链接到标题
无 collection / 无流量时也存在的指标:
| 类别 | 指标 | 用途 |
|---|---|---|
| 应用元信息 | app_info{version} |
标识版本 |
| 恢复模式 | app_status_recovery_mode |
0/1 标志 |
| 集群 | cluster_enabled |
是否启用集群 |
| 集合容量 | collections_total |
集合数量 |
| 集合容量 | collections_vector_total |
向量总数 |
| 副本 | collection_active_replicas_min/max |
活跃副本上下限 |
| 副本 | collection_dead_replicas |
失活副本 |
| 内存 | memory_active_bytes |
活跃内存 |
| 内存 | memory_resident_bytes |
常驻内存 |
| 内存 | memory_allocated_bytes |
已分配内存 |
| 内存 | memory_metadata_bytes |
元数据占用 |
| 内存 | memory_retained_bytes |
保留内存 |
| 进程 | process_*(标准 Prometheus 指标) |
进程级资源 |
按需暴露的指标 链接到标题
有 collection 或有流量时才出现:
| 指标 | 含义 |
|---|---|
rest_responses_total{status} |
REST 响应计数 |
rest_responses_duration_seconds_bucket |
REST 响应延迟分桶 |
grpc_responses_total{status} |
gRPC 响应计数 |
segments_total |
段数量 |
pending_optimization_operations |
待优化操作数 |
按需暴露的指标在无 collection 时不存在,Prometheus 表达式中涉及除法的部分需要
or vector(0)/vector(1)兼容空集,否则空集除法会得到NaN,不触发阈值告警(实际安全,但日志会有 warning)。
告警规则 链接到标题
在 alerts.yml 新增 qdrant_alerts 分组,共 11 条:
| 类别 | 告警名 | 触发条件 | 持续 | 严重度 |
|---|---|---|---|---|
| 可用性 | QdrantDown | up{job="qdrant"} == 0 |
1m | critical |
| 可用性 | QdrantRecoveryMode | app_status_recovery_mode == 1 |
1m | warning |
| 性能 | QdrantHighErrorRate | REST 错误率 > 5% | 5m | warning |
| 性能 | QdrantGRPCErrorRate | gRPC 错误率 > 5% | 5m | warning |
| 性能 | QdrantHighAPILatency | REST P99 > 1s | 5m | warning |
| 容量 | QdrantCollectionsCount | collections_total > 100 |
10m | warning |
| 容量 | QdrantVectorsTotal | 总向量数 > 5e7 | 10m | warning |
| 容量 | QdrantSegmentsHigh | segments_total > 5000 |
10m | warning |
| 性能 | QdrantOptimizerStuck | 待优化操作 > 1000 | 15m | warning |
| 资源 | QdrantHighResidentMemory | 常驻内存 > 4 GB | 10m | warning |
| 资源 | QdrantHighActiveMemory | 活跃内存 > 6 GB | 10m | warning |
完整 YAML:
- name: qdrant_alerts
interval: 30s
rules:
- alert: QdrantDown
expr: up{job="qdrant"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Qdrant 已宕机"
description: "Qdrant 服务已停止响应超过 1 分钟"
- alert: QdrantRecoveryMode
expr: app_status_recovery_mode{job="qdrant"} == 1
for: 1m
labels:
severity: warning
annotations:
summary: "Qdrant 处于恢复模式"
description: "当前处于 recovery mode,写入可能被拒绝"
- alert: QdrantHighErrorRate
expr: |
(
sum by(hostname) (rate(rest_responses_total{job="qdrant",status="error"}[5m]))
or vector(0)
)
/
(
sum by(hostname) (rate(rest_responses_total{job="qdrant"}[5m]))
or vector(1)
)
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "Qdrant REST API 错误率过高"
description: "5 分钟内 REST API 错误率 {{ $value | printf \"%.2f\" }}%"
- alert: QdrantGRPCErrorRate
expr: |
(
sum by(hostname) (rate(grpc_responses_total{job="qdrant",status!~"ok|Ok|OK"}[5m]))
or vector(0)
)
/
(
sum by(hostname) (rate(grpc_responses_total{job="qdrant"}[5m]))
or vector(1)
)
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "Qdrant gRPC 错误率过高"
description: "5 分钟内 gRPC 错误率 {{ $value | printf \"%.2f\" }}%"
- alert: QdrantHighAPILatency
expr: histogram_quantile(0.99, sum by(le, hostname) (rate(rest_responses_duration_seconds_bucket{job="qdrant"}[5m]))) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "Qdrant REST API P99 延迟过高"
description: "P99 延迟: {{ $value | printf \"%.2f\" }}s"
- alert: QdrantCollectionsCount
expr: collections_total{job="qdrant"} > 100
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant 集合数超阈值"
description: "当前集合数: {{ $value }}(阈值 100)"
- alert: QdrantVectorsTotal
expr: sum by(hostname) (collections_vector_total{job="qdrant"}) > 50000000
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant 向量总数超阈值"
description: "总向量数: {{ $value | printf \"%.0f\" }}(阈值 5e7)"
- alert: QdrantSegmentsHigh
expr: segments_total{job="qdrant"} > 5000
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant Segments 数量过高"
description: "段数: {{ $value }}(阈值 5000)"
- alert: QdrantOptimizerStuck
expr: max by(hostname) (pending_optimization_operations{job="qdrant"}) > 1000
for: 15m
labels:
severity: warning
annotations:
summary: "Qdrant 优化器疑似卡住"
description: "挂起的优化操作: {{ $value }}"
- alert: QdrantHighResidentMemory
expr: memory_resident_bytes{job="qdrant"} > 4 * 1024 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant 常驻内存超过 4GB"
description: "memory_resident_bytes: {{ $value | humanize1024 }}B"
- alert: QdrantHighActiveMemory
expr: memory_active_bytes{job="qdrant"} > 6 * 1024 * 1024 * 1024
for: 10m
labels:
severity: warning
annotations:
summary: "Qdrant 活跃内存超过 6GB"
description: "memory_active_bytes: {{ $value | humanize1024 }}B"
踩坑记录 链接到标题
坑 1:/metrics 端点 401
链接到标题
现象:Prometheus target 状态 down,lastError: 401 Unauthorized。
原因:Qdrant 默认对 /metrics 端点也启用 API key 认证,没有 api-key header 会被拒。
解决:在 prometheus.yml 用 bearer_token_file 传 API key(不要写在 yml 里):
- job_name: qdrant
bearer_token_file: /etc/prometheus/qdrant_api_key
...
调试时可以直接 curl 验证:
# 复现
$ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:6333/metrics
401
# 验证修复
$ curl -s -o /dev/null -w '%{http_code}\n' -H 'api-key: <KEY>' http://localhost:6333/metrics
200
坑 2:v1.16 指标去掉了 qdrant_ 前缀
链接到标题
现象:按老版本教程写的 qdrant_collections_total 表达式在 Prometheus 里查不到数据。
原因:Qdrant 1.16 起对指标命名空间做了调整——把 qdrant_ 前缀去掉了:
| 老版本(≤ 1.10) | 新版本(≥ 1.16) |
|---|---|
qdrant_collections_total |
collections_total |
qdrant_rest_responses_total |
rest_responses_total |
qdrant_memory_active_bytes |
memory_active_bytes |
qdrant_segments_total |
segments_total |
解决:先用 curl 实际拉一次 /metrics,把所有 qdrant_* 替换成无前缀的版本:
curl -H 'api-key: <KEY>' http://localhost:6333/metrics \
| grep -E '^[a-z]' | awk '{print $1}' | sed 's/{.*//' | sort -u
执行后会得到 20 个无前缀指标名,按此修改告警表达式。
坑 3:bearer_token_file 权限被拒
链接到标题
现象:scrape target 仍然 down,但 lastError 变成了:
unable to read authorization credentials:
unable to read file /etc/prometheus/qdrant_api_key:
open /etc/prometheus/qdrant_api_key: permission denied
诊断:Prometheus 容器以 nobody 用户运行(uid=65534),而 scp 上去的 API key 文件 owner 默认是 root:600,nobody 读不到。
$ ssh <monitor-host> "sudo docker exec prometheus id"
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
$ ssh <monitor-host> "sudo docker exec prometheus cat /etc/prometheus/qdrant_api_key"
cat: can't open '/etc/prometheus/qdrant_api_key': Permission denied
解决:chown 65534:65534,mode 保留 600 即可(不用放宽到 644 暴露给其他用户):
sudo chown 65534:65534 /opt/monitor/prometheus/qdrant_api_key
改完后容器内能正常读取,target 状态变 up:
$ ssh <monitor-host> "sudo docker exec prometheus cat /etc/prometheus/qdrant_api_key"
<YOUR_QDRANT_API_KEY>
这个坑是 Prometheus 容器化的常见问题——容器内进程以
nobody运行,宿主机上的文件必须 chown 到65534。MinIO 的MINIO_PROMETHEUS_AUTH_TYPE=public一行配置绕过了认证,所以没踩到;Qdrant 没有等价的开关,必须解决文件权限。
验证清单 链接到标题
部署完成后,按以下顺序验证:
# 1. promtool 校验配置语法
ssh <monitor-host> "sudo docker exec prometheus promtool check config /etc/prometheus/prometheus.yml"
# 预期:SUCCESS, 36 rules found
# 2. target health
ssh <monitor-host> "curl -s http://localhost:9090/api/v1/targets \
| python3 -c \"import sys,json; d=json.load(sys.stdin); \
[print(t['labels'].get('job'), t['health']) \
for t in d['data']['activeTargets'] if t['labels'].get('job')=='qdrant']\""
# 预期:qdrant up
# 3. up 指标
ssh <monitor-host> "curl -s 'http://localhost:9090/api/v1/query?query=up{job=\"qdrant\"}'"
# 预期:value = 1
# 4. 关键业务指标
ssh <monitor-host> "curl -s 'http://localhost:9090/api/v1/query?query=collections_total{job=\"qdrant\"}'"
ssh <monitor-host> "curl -s 'http://localhost:9090/api/v1/query?query=memory_resident_bytes{job=\"qdrant\"}'"
# 5. 规则加载数
ssh <monitor-host> "curl -s http://localhost:9090/api/v1/rules \
| python3 -c \"import sys,json; d=json.load(sys.stdin); \
[print(g['name'], len(g['rules'])) \
for g in d['data']['groups'] if g['name']=='qdrant_alerts']\""
# 预期:qdrant_alerts 11
# 6. 无 active 告警
ssh <monitor-host> "curl -s http://localhost:9090/api/v1/alerts \
| python3 -c \"import sys,json; d=json.load(sys.stdin); \
print('Active Qdrant alerts:', len([a for a in d['data']['alerts'] if a['labels'].get('job')=='qdrant']))\""
# 预期:0
总结 链接到标题
Qdrant 接入 Prometheus 的核心步骤:
- 仓库内
monitor/prometheus/qdrant_api_key存 API key(.gitignore忽略) prometheus.yml加qdrantjob,用bearer_token_file引用alerts.yml加 11 条qdrant_alerts规则- 部署到监控服务器时
chown 65534:65534解决容器 nobody 权限
和 MinIO 的对比:
| 项 | MinIO | Qdrant |
|---|---|---|
| metrics 端点开启方式 | 环境变量一行 | 默认开启 |
| API key | 无 | 必须用 bearer_token_file |
| 容器权限坑 | 无 | 必须 chown 65534 |
| 指标前缀 | minio_* |
无前缀(v1.16+) |
| exporter | 不需要 | 不需要 |
整体上 Qdrant 接入的工作量比 MinIO 多 30%——主要是 API key 认证和容器权限的额外配置。值得多花这点时间,因为 Qdrant 在 RAG、推荐、搜索场景里通常是核心依赖,宕机或性能退化直接影响业务。