在香港服务器Debian 10系统上用 Prometheus + Loki 补齐 SaaS 平台“监控盲区”——黑盒合成监控、租户维度观测与日志收集体系全流程落地(含踩坑与参数细节)

香港服务器的业务监控面板一片绿,P95 延迟、CPU、内存都“健康”。偏偏客户打来电话,说香港区 SaaS 控制台间歇性 502,且只发生在租户 A、B 的管理页。更诡异的是,我们的 Prometheus 没有任何告警触发,Loki 也没有搜到对应的错误堆栈。
这就是“监控盲区”——系统认为一切正常,用户却已经崩溃。那一刻我意识到,我们的指标与日志体系只覆盖了“服务器与容器层面的自我感觉”,却没有从用户路径和租户维度做合成监控与精细观测。接下来两天,我在香港机房搭了把“梯子”:在 Debian 10 上把 Prometheus + Blackbox + Pushgateway + Loki/Promtail 系列拉起来,补齐了从黑盒合成监控、白盒业务指标到集中化日志的闭环。下面是我完整的落地过程、踩坑与参数细节。
1. 场景与约束
1.1 目标
- 补齐 SaaS 平台“用户路径/租户维度”的监控盲区
- 合成监控(黑盒)+ 应用/系统指标(白盒)+ 日志索引(Loki)三位一体
- 告警要分层:SLO/SLA、租户级、依赖链路级(DNS、TLS、Upstream)
1.2 环境与硬件(香港机房)
| 角色 | 型号/规格 | 关键参数 | 用途 |
|---|---|---|---|
| 监控主机 | 2U 单机(AMD EPYC 7402P) | 24C/48T,128GB RAM,2×1.92TB NVMe(RAID1),2×10GbE | Prometheus + Alertmanager + Loki(单机版) + Grafana |
| 应用节点(x6) | 1U(Intel Silver 4310) | 12C/24T,64GB RAM,1×960GB SSD,2×10GbE | Docker/K8s 工作负载 |
| 边界节点 | 硬件防火墙 + LVS | 10GbE,上连两家 ISP | 四层转发 + eBGP |
- OS:Debian 10 (Buster),内核 4.19 系(默认)
- 容器:Docker CE(生产用 K8s 亦可,本文以 Docker 演示)
1.3 网络与端口矩阵
| 组件 | 端口 | 方向 | 说明 |
|---|---|---|---|
| Prometheus | 9090 | Intranet | Web/UI & API |
| Alertmanager | 9093 | Intranet | 告警路由 |
| node_exporter | 9100 | Prometheus→节点 | 主机指标 |
| blackbox_exporter | 9115 | Prometheus→黑盒 | HTTP/TCP/DNS 检查 |
| Pushgateway | 9091 | 应用→Pushgateway→Prom | 短任务指标 |
| Loki | 3100 | Promtail→Loki | 日志写入/查询 API |
| Promtail | 9080 | 本地 | 采集端本地 HTTP(可闭) |
2. 监控盲区的成因:不是 Prometheus 不行,是我们没问对问题
症状回放:
- 只有部分租户的“管理页”502,公共首页正常
- 容器、节点层指标均正常,无 CPU/内存异常
- 无核心告警触发,日志侧也未检索到对应 5xx
复盘定位:
- 黑盒合成监控缺失:我们只抓 Node/容器白盒指标,缺“从用户角度访问指定租户路径”的探测。
- 租户维度缺失:日志标签只有 service、pod,没有 tenant。
- 短任务与边缘依赖盲点:部分异步作业/三方依赖“偶发错”未被指标化(需要 Pushgateway 与黑盒探测 DNS/TLS/TCP)。
- 日志采集在容器边界断档:某些容器只输出到 stdout,日志轮转策略与 Promtail pipeline 不匹配,导致检索不到异常段。
补救路线图:
黑盒(Blackbox)补用户路径 → 白盒(Prom + Exporter + Pushgateway)补业务与短任务指标 → Loki/Promtail 统一日志收集并加租户标签 → Alert 分层(SLO、租户、依赖节点)
3. 系统基线(Debian 10 上的那些“必须做”)
# 基本工具
apt-get update && apt-get install -y jq curl chrony netcat-traditional
# 时间同步(Loki/Prometheus 对时序非常敏感)
systemctl enable chrony && systemctl restart chrony
# 文件句柄 / 网络连接跟随
cat >/etc/security/limits.d/monitoring.conf <<'EOF'
* soft nofile 1048576
* hard nofile 1048576
* soft nproc 65535
* hard nproc 65535
EOF
# conntrack 调优(黑盒/高并发探测容易打满)
cat >/etc/sysctl.d/99-monitoring.conf <<'EOF'
net.netfilter.nf_conntrack_max=262144
net.core.somaxconn=4096
fs.file-max=2097152
vm.max_map_count=262144
EOF
sysctl --system
Debian 10 上 Docker 默认沿用 iptables(legacy),如果你切到 nftables,要确认 Docker 与防火墙规则一致,否则会导致 Promtail/Loki/Blackbox 访问异常(我就被这个坑过一次)。
4. Prometheus 与 Exporter 体系
4.1 安装(以二进制为例)
useradd --no-create-home --shell /sbin/nologin prometheus
mkdir -p /etc/prometheus /var/lib/prometheus
# 放置 prometheus 与 promtool 到 /usr/local/bin(略)
chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus
systemd 单元:
# /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=45d \
--storage.tsdb.wal-compression \
--web.enable-lifecycle
Restart=on-failure
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
4.2 Node / Blackbox / Pushgateway
# node_exporter(每台节点)
useradd -r -s /sbin/nologin nodeusr
# 放置二进制 /usr/local/bin/node_exporter
cat >/etc/systemd/system/node_exporter.service <<'EOF'
[Unit]
Description=Node Exporter
[Service]
User=nodeusr
ExecStart=/usr/local/bin/node_exporter \
--collector.processes \
--collector.systemd \
--collector.tcpstat
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now node_exporter
blackbox_exporter:
# /etc/blackbox_exporter/config.yml
modules:
http_tenant:
prober: http
timeout: 10s
http:
method: GET
headers:
X-Tenant-Id: "TENANT_PLACEHOLDER"
valid_status_codes: [200,302]
no_follow_redirects: false
tcp_443:
prober: tcp
timeout: 5s
dns_public:
prober: dns
timeout: 5s
dns:
query_name: "saas.example.hk"
query_type: "A"
valid_rcodes: ["NOERROR"]
Pushgateway(短任务/批处理指标):
- 应用中向 http://pushgw:9091/metrics/job/<job>/instance/<id> 推送
- Prometheus 定期抓取 Pushgateway,避免短任务指标丢失
4.3 Prometheus 抓取与重标签
# /etc/prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
dc: hk
cluster: prod
scrape_configs:
- job_name: 'nodes'
static_configs:
- targets: ['app01:9100','app02:9100','monitor:9100']
relabel_configs:
- source_labels: [__address__]
regex: "(.*):9100"
target_label: instance
replacement: "$1"
- job_name: 'blackbox-tenant'
metrics_path: /probe
params:
module: [http_tenant]
static_configs:
- targets:
- https://saas.example.hk/tenant/a/admin
- https://saas.example.hk/tenant/b/admin
labels:
tenant: "a" # 自定义标签
- labels:
tenant: "b"
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox:9115
- source_labels: [tenant]
target_label: X-Tenant-Id # 传入 blackbox headers
- source_labels: [__param_target]
target_label: instance
- job_name: 'pushgateway'
static_configs:
- targets: ['pushgw:9091']
要点:用 relabel_configs 把租户信息、探测目标透传到 Blackbox,并把 X-Tenant-Id 注入 HTTP 头;这样合成监控就真的“走了用户路径”。
5. Alertmanager:分层路由 + 降噪
# /etc/alertmanager/alertmanager.yml
global:
resolve_timeout: 5m
route:
receiver: 'default'
group_by: ['alertname','tenant']
group_wait: 30s
group_interval: 5m
repeat_interval: 2h
routes:
- matchers:
- severity="critical"
receiver: 'oncall'
- matchers:
- tenant=~".+"
receiver: 'tenant_owner'
receivers:
- name: 'default'
webhook_configs:
- url: 'http://ops-webhook:9000/alerts'
- name: 'oncall'
pagerduty_configs:
- routing_key: '<pd-key>'
- name: 'tenant_owner'
email_configs:
- to: 'owner@tenant.example'
SLO/Burn-rate 告警(示例 PromQL):
# /etc/prometheus/rules/slo.yml
groups:
- name: slo
rules:
- alert: SaaSAvailabilityHighBurn
expr: |
(sum(rate(blackbox_probe_success{job="blackbox-tenant"}[5m]) == 0)
/
sum(rate(blackbox_probe_success{job="blackbox-tenant"}[5m])))
> (1-0.999) * 14.4
for: 5m
labels:
severity: critical
annotations:
summary: "租户可用性快速消耗(5m 窗口)"
description: "tenant={{ $labels.tenant }}, target={{ $labels.instance }}"
6. Loki + Promtail:日志收集体系
6.1 Loki(单机,boltdb-shipper + filesystem)
# /etc/loki/config.yml
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
schema_config:
configs:
- from: 2024-01-01
store: boltdb-shipper
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
max_global_streams_per_user: 500000
max_label_names_per_series: 30
compactor:
working_directory: /var/lib/loki/compactor
shared_store: filesystem
compaction_interval: 5m
table_manager:
retention_deletes_enabled: true
retention_period: 360h # 15 天
经验:Loki 的“标签基数”是第一大坑。租户维度要做,但避免把 request_id、user_id 这类高基数字段放到 label;放到日志体里即可。
6.2 Promtail(容器与文件双通道)
# /etc/promtail/config.yml
server:
http_listen_port: 9080
clients:
- url: http://loki:3100/loki/api/v1/push
positions:
filename: /var/lib/promtail/positions.yaml
scrape_configs:
- job_name: system
static_configs:
- targets: [localhost]
labels:
job: syslog
host: ${HOSTNAME}
__path__: /var/log/syslog
- job_name: docker
docker_sd_configs:
- host: "unix:///var/run/docker.sock"
refresh_interval: 30s
relabel_configs:
- source_labels: ['__meta_docker_container_name']
target_label: 'container'
regex: '/(.*)'
- source_labels: ['__meta_docker_container_label_com_example_tenant']
target_label: 'tenant'
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: 'service'
pipeline_stages:
- docker: {}
- json:
expressions:
level: level
msg: message
trace: trace
- labels:
level:
- drop:
source: level
expression: "debug" # 降噪
做法:通过 Docker label 注入 tenant,例如 --label com.example.tenant=a。这在 Loki 中就能按租户聚合了。
LogQL 查询范例
最近 10 分钟租户 a 的 5xx:
{job="docker", tenant="a"} |= "HTTP" |~ " 5\\d\\d " | line_format "{{.msg}}"
API 网关 TLS 握手错误:
{service="edge-gw"} |= "tls: handshake failure"
7. 仪表盘与可观测性“闭环”
| 类别 | 仪表盘/面板 | 关键图表 | 价值 |
|---|---|---|---|
| 黑盒 | 租户可用性 | blackbox_probe_success、RTT、TLS |
直接感知用户路径 |
| 白盒 | 节点/容器健康 | CPU/Mem/IO/GC/线程 | 快速归因资源瓶颈 |
| 白盒 | 业务指标 | QPS、P95/P99、错误率 | 与 SLO 对齐 |
| 日志 | 错误热力图 | per-tenant 5xx、trace 关联 | 定位租户与调用链 |
| 依赖 | DNS/TCP 探测 | 解析耗时、TCP 连接失败 | 排查边缘依赖故障 |
我在 Grafana 里把黑盒、白盒与日志面板交叉链接(面板变量包括 tenant),一键切换租户视图,值班人员的“平均定位时间”从 20 分钟降到 5 分钟以内。
8. 存储与保留策略(容量核算)
| 组件 | 吞吐/基数假设 | 保留期 | 估算容量 | 备注 |
| Prometheus | 400k series @ 15s | 45 天 | ≈ 1.2–1.6TB | 开 WAL 压缩 |
| Loki | 1.5k msg/s(平均) | 15 天 | ≈ 1.8TB | JSON 压缩率 3–6x |
| Pushgateway | 低 | 按 Prometheus | 忽略 | 仅短任务 |
经验:Prometheus 的高基数来自 label 组合(如 per-pod、per-path)。合成监控要克制目标数量;租户黑盒尽量覆盖关键路径,不要无度扩张。
9. 典型告警清单(精炼版)
| 告警 | 规则/阈值 | 作用 |
|---|---|---|
| SaaSAvailabilityHighBurn | 上文 Burn-rate | SLO 快速消耗 |
| TenantHttpFailuresBurst | rate(probe_http_status_code{=~"5.."}[5m]) > 0 |
租户级 5xx |
| UpstreamTcpConnectErrors | increase(probe_failed_due_to_tcp_[5m]) > 10 |
上游 TCP 抖动 |
| DNSFailure | increase(probe_dns_lookup_time_seconds_count{rcode!="NOERROR"}[5m]) > 0 |
DNS 解析异常 |
| LokiIngestionBacklog | rate(loki_distributor_lines_dropped_total[5m]) > 100 |
日志丢弃 |
| PromHighChurn | changes(scrape_samples_post_metric_relabeling[10m]) > 5e6 |
高 churn 预警 |
10. 真实踩坑与修复过程
iptables/nftables 冲突
现象:Blackbox 无法探测外网,Loki 接收偶发超时。
根因:Docker 使用 iptables(legacy),宿主切换 nft 后 NAT 规则不生效。
处理:回退到 iptables-legacy 或统一切到 iptables-nft 并重建 DOCKER 链。
Loki “entry out of order”
现象:Promtail 写入被拒。
根因:部分应用容器时间漂移(NTP 未启用),日志时间戳乱序。
处理:统一 chrony;临时放宽 max_out_of_order_time: 5m,恢复后调回默认。
Prometheus WAL 膨胀
现象:磁盘写放大,I/O 飙高。
根因:黑盒目标过多 + 指标高 churn。
处理:精简黑盒目标、延长 scrape_interval(极端从 10s→15s/30s)、开启 --storage.tsdb.wal-compression。
日志标签爆炸
现象:Loki 查询变慢,索引剧增。
根因:把 request_id 当 label。
处理:移入日志体,保留 tenant/service/level 等低基数标签;promtail drop 降噪。
Pushgateway 反模式
现象:短任务清理不及时,旧时间序列残留。
处理:业务侧 push --expire 或调用完成后 DELETE,并在 Prometheus 端加 honor_labels 策略与 metric_relabel_configs 做 TTL。
11. 一键校验(可复制执行的健康检查清单)
# 黑盒:租户 a
curl -fsS "http://blackbox:9115/probe?target=https://saas.example.hk/tenant/a/admin&module=http_tenant" \
-H "X-Tenant-Id: a" | grep -E 'probe_success|probe_duration_seconds'
# Prometheus 抓取现状
curl -s http://prom:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, health: .health, lastError: .lastError}' | head
# Loki 写入/查询
curl -s -G http://loki:3100/loki/api/v1/query --data-urlencode 'query={job="docker"} |= "error"' | jq '.data.stats'
12. 最终结构回放(ASCII 拓扑)
[User/Browser]
|
(Edge/LB)───DNS──► Blackbox(http/dns/tcp)
| ▲
▼ │
[App Nodes: Docker]──Promtail──► Loki
▲ ▲
node_exporter Grafana
│ │
└────────► Prometheus ◄───┘
▲
Pushgateway(短任务)
13. 凌晨 3:06,第一次“正确的红色”
体系落地后的第一个凌晨,黑盒面板出现漂亮的红色告警:仅租户 B 的 /admin 在香港电信出口 RTT 拉长并伴随 5xx 抖动。我顺着告警跳转到日志,Loki 里清清楚楚:上游 TLS 握手超时集中发生在某 ISP 段。我们把边界策略切换到双线路优选并限流重试,5 分钟内恢复。
那一刻我意识到:红色并不可怕,沉默才可怕。把用户路径、租户维度与日志闭环接起来后,香港服务器不再“自我感觉良好”,而是对用户体验负责。
14. 附录:关键配置汇总(可直接落地)
- prometheus.yml(含 blackbox + pushgateway)
- alertmanager.yml(分层路由)
- blackbox_exporter/config.yml(http_tenant/dns/tcp 模块)
- loki/config.yml(boltdb-shipper + retention)
- promtail/config.yml(docker_sd + JSON pipeline)
- systemd 单元(prometheus/node_exporter/blackbox/loki/promtail)
如果你也在香港/海外机房跑 Debian 10 的 SaaS,我建议先从一个关键租户路径的黑盒做起,配好 X-Tenant-Id 与租户标签,一天之内你就能看到“沉默处的波纹”。