如何在Ubuntu系统的香港服务器中部署Prometheus监控直播推流节点的健康状态?

周五晚上 10 点,我坐在香港葵涌的机房里,客服在工单里说:“主播反馈间歇性卡顿,RTMP 推流不稳,偶发 1~2 秒掉帧。”我盯着几台推流节点的 nload 和 htop,直觉告诉我问题不在平均带宽,而在“瞬时抖动”和“会话层健康”。临时的 curl 和 ffmpeg -re 压测不够系统,于是我决定当天夜里把 Prometheus 系监控拉起来——把每一次 RTMP 握手、每一次会话的比特率波动、每一次 TCP 重传,都留痕成可回放的证据。这篇文章就是那晚到第二天清晨的完整实操记录与复盘。
1. 场景与目标
业务背景:香港(HK)机房承载国内外主播 RTMP 推流,边缘节点使用 SRS(也可替换 Nginx-RTMP),上游转发到主控 CDN。问题集中在瞬时卡顿和会话不稳定。
监控目标:
- 节点维度:CPU/内存/磁盘/网卡、队列丢包、TCP 重传、连接数、文件句柄、conntrack 使用率。
- 会话维度:RTMP 在线发布数、每路流入比特率、音视频时延、推流成功率、握手失败率、断开原因。
- 网络维度:端口连通(1935/TCP、API/HTTP)、对外丢包/时延的黑盒探测。
- 告警策略:会话数突降、比特率突降/波动过大、节点负载异常、端口不可达、时钟漂移、磁盘写放大(TSDB 压力)。
2. 基础环境与参数(我现场用到的)
| 项 | 参数 |
|---|---|
| OS | Ubuntu Server 22.04 LTS(5.15 内核) |
| 服务器 | 2×Intel Xeon Silver 4210R / 64G RAM / 2×960G NVMe(RAID1) |
| 网卡 | 2×10GbE(Bonding LACP,MTU 9000,内网直连汇聚) |
| 带宽 | BGP 线路(HK 出口),突发 8–10Gbps |
| Prometheus | 2.x(建议 ≥2.45,命令行基本一致) |
| Node Exporter | 1.x(建议 ≥1.6) |
| Blackbox Exporter | 0.2x(建议 ≥0.25) |
| Alertmanager | 0.2x |
| 可选 | Grafana 10.x |
| 推流服务 | SRS 5.x(启用 HTTP API),或 Nginx-RTMP(需扩展统计接口) |
注:版本号非强绑定,保持在近两年稳定大版本即可。
3. 拓扑与命名规划
[主播端] --RTMP--> [HK 推流节点A/B/C ...] --内部--> [主控/CDN]
| ^
v |
Node/Blackbox/SRS Exporters |
| |
[Prometheus] --(Alertmanager)--> 值班群/电话/飞书
\--(Grafana)--> 看板
命名约定(强烈建议统一):
- 机房:hk01
- 节点:hk01-live-rtmp-01/02/...
- 标签:region="hk", role="rtmp-edge", env="prod"
4. 系统准备与调优
4.1 基础包与时钟
sudo apt update && sudo apt install -y chrony curl wget tar jq ufw
sudo timedatectl set-timezone Asia/Hong_Kong
sudo systemctl enable --now chrony
4.2 内核与网络参数(/etc/sysctl.d/99-live.conf)
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_congestion_control = bbr
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 10000 65535
net.netfilter.nf_conntrack_max = 262144
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
fs.file-max = 2097152
sudo sysctl --system
4.3 文件句柄与进程限制(/etc/security/limits.d/99-live.conf)
- soft nofile 1048576
- hard nofile 1048576
4.4 UFW 端口策略(最小开放)
sudo ufw allow 22/tcp
sudo ufw allow 9090/tcp # Prometheus
sudo ufw allow 9100/tcp # node_exporter
sudo ufw allow 9115/tcp # blackbox_exporter
sudo ufw allow 9093/tcp # Alertmanager
sudo ufw allow 1935/tcp # RTMP
sudo ufw allow 1985/tcp # SRS HTTP API
sudo ufw enable
5. 安装 Prometheus(主控机)
我习惯把 Prometheus 放在独立节点(NVMe + 充足 IOPS),避免采集与存储互相干扰。
5.1 用户与目录
sudo useradd -M -s /usr/sbin/nologin prometheus
sudo mkdir -p /opt/prometheus/{data,conf,bin}
sudo chown -R prometheus: /opt/prometheus
5.2 下载与放置(以 x86_64 为例)
VER=2.52.0 # 举例
wget https://github.com/prometheus/prometheus/releases/download/v${VER}/prometheus-${VER}.linux-amd64.tar.gz
tar xzf prometheus-${VER}.linux-amd64.tar.gz
sudo cp prometheus-${VER}.linux-amd64/{prometheus,promtool} /opt/prometheus/bin/
sudo cp -r prometheus-${VER}.linux-amd64/{console_libraries,consoles} /opt/prometheus/
5.3 配置 /opt/prometheus/conf/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
region: "hk"
cluster: "hk01"
scrape_configs:
# 1) 采集 Prometheus 自身
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
# 2) Node Exporter(推流节点)
- job_name: 'node'
scrape_interval: 15s
static_configs:
- targets:
- '10.0.10.11:9100' # hk01-live-rtmp-01
- '10.0.10.12:9100' # hk01-live-rtmp-02
labels:
role: 'rtmp-edge'
env: 'prod'
# 3) Blackbox:探测 RTMP 端口连通
- job_name: 'blackbox_rtmp'
metrics_path: /probe
params:
module: [tcp_connect]
static_configs:
- targets:
- '10.0.10.11:1935'
- '10.0.10.12:1935'
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: '10.0.0.5:9115' # blackbox_exporter 地址
# 4) SRS 自定义 Exporter(见后文)
- job_name: 'srs_exporter'
scrape_interval: 15s
static_configs:
- targets:
- '10.0.10.11:9200'
- '10.0.10.12:9200'
labels:
role: 'rtmp-edge'
5.4 Systemd 服务 /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus Server
After=network-online.target
Wants=network-online.target
[Service]
User=prometheus
Group=prometheus
ExecStart=/opt/prometheus/bin/prometheus \
--config.file=/opt/prometheus/conf/prometheus.yml \
--storage.tsdb.path=/opt/prometheus/data \
--storage.tsdb.retention.time=15d \
--web.listen-address=:9090 \
--web.enable-lifecycle
Restart=always
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
启动:
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
/opt/prometheus/bin/promtool check config /opt/prometheus/conf/prometheus.yml
6. 安装 Exporters(推流节点)
6.1 Node Exporter
sudo useradd -M -s /usr/sbin/nologin nodeexp
VER=1.7.0
wget https://github.com/prometheus/node_exporter/releases/download/v${VER}/node_exporter-${VER}.linux-amd64.tar.gz
tar xzf node_exporter-${VER}.linux-amd64.tar.gz
sudo cp node_exporter-${VER}.linux-amd64/node_exporter /usr/local/bin/
/etc/systemd/system/node_exporter.service
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=nodeexp
ExecStart=/usr/local/bin/node_exporter \
--collector.conntrack \
--collector.netstat \
--collector.filesystem.ignored-mount-points=^/(dev|proc|sys|run)($|/)
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
6.2 Blackbox Exporter(可在 Prometheus 主机或旁路主机)
useradd -M -s /usr/sbin/nologin blackbox
VER=0.25.0
wget https://github.com/prometheus/blackbox_exporter/releases/download/v${VER}/blackbox_exporter-${VER}.linux-amd64.tar.gz
tar xzf blackbox_exporter-${VER}.linux-amd64.tar.gz
sudo cp blackbox_exporter-${VER}.linux-amd64/blackbox_exporter /usr/local/bin/
sudo mkdir -p /etc/blackbox
/etc/blackbox/blackbox.yml
modules:
tcp_connect:
prober: tcp
timeout: 3s
http_2xx:
prober: http
timeout: 5s
http:
method: GET
valid_http_versions: [ "HTTP/1.1", "HTTP/2" ]
preferred_ip_protocol: "ip4"
/etc/systemd/system/blackbox_exporter.service
[Unit]
Description=Blackbox Exporter
After=network.target
[Service]
User=blackbox
ExecStart=/usr/local/bin/blackbox_exporter --config.file=/etc/blackbox/blackbox.yml --web.listen-address=:9115
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now blackbox_exporter
6.3 SRS 启用 API(或 Nginx-RTMP 统计)
SRS(推荐,天生有 API)在 srs.conf 中确保:
http_api {
enabled on;
listen 1985;
}
stats {
network on;
}
重启 SRS 后,curl http://127.0.0.1:1985/api/v1/streams 能返回 JSON。
如果你用 Nginx-RTMP,需要额外模块或脚本输出统计(例如 Lua/OpenResty 自制接口),思路与 SRS 类似:提供当前发布流列表和比特率。
6.4 自定义 SRS Exporter(Python)
设计目标:把当前发布数、各路比特率、会话状态转换成 Prometheus 指标。
安装依赖:
sudo apt install -y python3-pip
pip3 install flask prometheus_client requests
/opt/srs_exporter/srs_exporter.py
#!/usr/bin/env python3
import os, time, requests
from flask import Flask, Response
from prometheus_client import Gauge, generate_latest, CollectorRegistry, CONTENT_TYPE_LATEST
SRS_API = os.environ.get("SRS_API", "http://127.0.0.1:1985/api/v1/streams")
TIMEOUT = float(os.environ.get("SRS_TIMEOUT", "2.0"))
app = Flask(__name__)
registry = CollectorRegistry()
g_streams = Gauge('srs_active_streams', 'Active RTMP streams', ['server'], registry=registry)
g_bitrate_video = Gauge('srs_stream_bitrate_video_bps', 'Video bitrate (bps)', ['stream', 'vhost', 'app'], registry=registry)
g_bitrate_audio = Gauge('srs_stream_bitrate_audio_bps', 'Audio bitrate (bps)', ['stream', 'vhost', 'app'], registry=registry)
g_publishers = Gauge('srs_stream_publishers', 'Publishers(=1 if publishing)', ['stream', 'vhost', 'app'], registry=registry)
def fetch_srs():
r = requests.get(SRS_API, timeout=TIMEOUT)
r.raise_for_status()
return r.json()
@app.route('/metrics')
def metrics():
try:
data = fetch_srs()
server = 'hk01'
# 重置(避免指标残留)
for m in [g_streams, g_bitrate_video, g_bitrate_audio, g_publishers]:
m._metrics.clear()
total = 0
items = data.get('streams', [])
for s in items:
if s.get('publish', {}).get('active'):
total += 1
vhost = s.get('vhost', 'default')
app = s.get('app', 'live')
name = s.get('name', 'unknown')
# bitrate 可能在 kbits 字段,视 SRS 版本略有不同
v_bps = int(float(s.get('kbps', {}).get('video', 0)) * 1000)
a_bps = int(float(s.get('kbps', {}).get('audio', 0)) * 1000)
g_bitrate_video.labels(name, vhost, app).set(v_bps)
g_bitrate_audio.labels(name, vhost, app).set(a_bps)
g_publishers.labels(name, vhost, app).set(1)
g_streams.labels(server).set(total)
except Exception:
# 拉取失败时,给 0/空,避免数据挂死
pass
return Response(generate_latest(registry), mimetype=CONTENT_TYPE_LATEST)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9200)
Systemd:
sudo mkdir -p /opt/srs_exporter
sudo cp srs_exporter.py /opt/srs_exporter/
sudo bash -c 'cat >/etc/systemd/system/srs_exporter.service' <<'EOF'
[Unit]
Description=SRS Prometheus Exporter
After=network.target
[Service]
User=nobody
WorkingDirectory=/opt/srs_exporter
Environment=SRS_API=http://127.0.0.1:1985/api/v1/streams
ExecStart=/usr/bin/python3 /opt/srs_exporter/srs_exporter.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now srs_exporter
7. Alertmanager(路由告警)
安装略,核心是 路由 与 接收器(邮件、Webhook、飞书/钉钉)。示例 /etc/alertmanager/alertmanager.yml:
route:
receiver: 'ops'
group_by: ['alertname', 'instance']
group_wait: 30s
group_interval: 5m
repeat_interval: 2h
receivers:
- name: 'ops'
webhook_configs:
- url: 'https://your-webhook.example.com/alert'
Prometheus 侧关联:
alerting:
alertmanagers:
- static_configs:
- targets: ['10.0.0.5:9093']
8. 告警规则(直播关注项)
/opt/prometheus/conf/rules-live.yml:
groups:
- name: live-edge
rules:
- alert: NodeDown
expr: up{job="node"} == 0
for: 1m
labels: { severity: critical }
annotations:
summary: "Node down: {{ $labels.instance }}"
- alert: HighConntrack
expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8
for: 5m
labels: { severity: warning }
annotations:
summary: "Conntrack high on {{ $labels.instance }}"
- alert: RTMPPortDown
expr: probe_success{job="blackbox_rtmp"} == 0
for: 30s
labels: { severity: critical }
annotations:
summary: "RTMP port not reachable: {{ $labels.instance }}"
- alert: StreamsDrop
expr: (max_over_time(srs_active_streams[5m]) - srs_active_streams) > 5
for: 2m
labels: { severity: critical }
annotations:
summary: "Active streams sudden drop on {{ $labels.server }}"
- alert: BitrateLow
expr: avg_over_time(srs_stream_bitrate_video_bps[2m]) < 300000 and srs_stream_publishers == 1
for: 2m
labels: { severity: warning }
annotations:
summary: "Video bitrate low: {{ $labels.stream }} ({{ humanize $value }}bps)"
在 prometheus.yml 引用:
rule_files:
- /opt/prometheus/conf/rules-live.yml
应用:
curl -X POST http://127.0.0.1:9090/-/reload
9. 快速验证与压测
9.1 黑盒连通性
curl -s 'http://10.0.0.5:9115/probe?module=tcp_connect&target=10.0.10.11:1935'
应看到 probe_success 1。
9.2 推流仿真(本机或压测机)
ffmpeg -re -stream_loop -1 -i demo.mp4 -c copy -f flv rtmp://10.0.10.11/live/test001
观察:
srs_active_streams{server="hk01"}
srs_stream_bitrate_video_bps{stream="test001"}
9.3 Prometheus TSDB 健康
打开 http://<prometheus>:9090/targets 和 http://<prometheus>:9090/rules,确认任务绿色、规则已加载。
10. Grafana 面板(选做但强烈建议)
我常用的几个图:
- 主看板:活跃流数、过去 24h 峰值、各节点会话分布、端口可达率。
- 会话质量:单流比特率曲线(视频/音频)、发布状态切换次数、断流次数按小时分布。
- 节点健康:CPU/负载、网卡丢包/错误、TCP 重传率、conntrack 使用率、磁盘写入(TSDB 压力)。
- 小技巧:对 srs_stream_bitrate_video_bps 做 irate() 或 avg_over_time() 平滑,减少锯齿;对流名加 regex 变量,现场定位更快。
11. 端口与组件速查表
| 组件 | 端口 | 说明 |
|---|---|---|
| Prometheus | 9090/TCP | Web & API |
| Alertmanager | 9093/TCP | 告警路由 |
| Node Exporter | 9100/TCP | 节点指标 |
| Blackbox Exporter | 9115/TCP | 黑盒探测 |
| SRS API | 1985/TCP | 流状态 JSON |
| SRS Exporter | 9200/TCP | 自定义指标 |
| RTMP | 1935/TCP | 推流入口 |
12. 我踩过的坑与现场解法
时钟漂移导致“比特率突降误报”
现象:Exporter 和 Prometheus 时钟差 >1s,avg_over_time 不稳定。
解法:全栈启用 chrony,Prometheus/Exporter/NTP 上游同源;Grafana 面板对齐到整点窗口。
conntrack 爆表引起握手失败
现象:峰值时 RTMP 建连失败率升高,dmesg 有 nf_conntrack: table full。
解法:增大 nf_conntrack_max,优化 tcp_fin_timeout,确认 NAT 行为(若有)不放大会话数。
TSDB 写压过大导致面板卡顿
现象:15s 间隔、标签爆炸(流名高基数),Prometheus CPU 飙升。
解法:控制维度:对“流级”指标只采样关键节点(或落地到远端存储如 VictoriaMetrics/Thanos),将面向告警的指标聚合成 Recording Rules;降低保留至 7–15 天。
Blackbox 探测与业务竞争
现象:过多探测导致 1935/TCP SYN 突增。
解法:使用 TCP 探测但减少 targets(或延长间隔),优先在同一二层内侧探测,避免经公网绕行。
SRS API 版本差异
现象:不同小版本 kbps 字段位置有差异。
解法:Exporter 里做容错(见示例 get('kbps', {}).get('video',0)),并把异常转化为 0,避免脏数据残留。
UFW 漏放 1985 导致指标空白
现象:Exporter 抓不到数据,但本机 curl localhost:1985 正常。
解法:Exporter 与 SRS 同机无须对外开放 1985,只需 127.0.0.1,Exporter 对外暴露 9200;Prometheus 只拉 9200。
13. 进一步优化技巧
Recording Rules(预计算)
将常用计算落地,提升查询性能:
groups:
- name: records
rules:
- record: job:srs_active_streams:sum
expr: sum(srs_active_streams) by (server)
- record: stream:video_bitrate_kbps
expr: srs_stream_bitrate_video_bps / 1000
分层采集与分片
流级指标只在边缘机(小 Prometheus)采,remote_write 到主 Prometheus;主控做聚合与告警。
网络细节
- 绑定网卡中断到 CPU(irqbalance 调整),RSS/ RPS 配置;
- 适度开启 GRO/LRO,观察 RTMP 时延(SRT 更敏感)。
安全
Prometheus/Alertmanager 前加 Nginx 反代 + Basic Auth + IP 白名单;Exporter 仅在内网暴露。
14. 运行与应急手册(我自己的“值班手卡”)
端口健康:
promtool tsdb status 看写入压力;ss -s 看活跃连接;ethtool -S 看网卡丢包;conntrack -S 看表占用。
会话突降排查:
先看 srs_active_streams 与 probe_success,再查 dmesg 是否有丢包/NIC 错误、chronyc sources 是否抖动、top 是否 IO wait 飙升。
快速止血:
降采样(把 scrape_interval 暂调 30s),临时关闭流级指标采集或减少目标,保证主看板与告警在线。
15. 成本与容量估算(我当晚的粗算)
| 项 | 估算 |
|---|---|
| 指标时间序列(活跃 2 台边缘、每台 2–3k 时序) | ~6k series |
| 15 天保留 + NVMe | 50–100GB(视流级标签波动) |
| CPU(Prometheus) | 2–4 vCPU 足够 |
| 内存 | 8–16GB |
| 导致卡顿的主要风险 | 高基数标签、瞬时写入峰值、磁盘 IOPS |
凌晨三点,Grafana 的“会话质量”面板终于稳定下来了:srs_active_streams 的阶梯折线贴着 400+,偶有锯齿但不再“悬崖”。BitrateLow 告警偶尔闪一下,点进历史曲线,能看到对应 RTMP 会话的比特率在 23:41 到 23:43 下降了 40%,紧接着 tcp_retrans_segs_total 也抬头——是那根上联链路在抖。我把链路端口换到了另一张万兆模块,曲线立刻平滑。客户在群里发了句“稳定了,感谢”,我关掉了最后一盏机柜门的指示灯。
那一夜让我再次确认:监控不是堆面板,而是让每一次异常都留下可验证的证据链。Prometheus 没有魔法,但它把“感觉”变成了“数据”,把“争论”变成了“事实”。如果你也在香港或其他边缘机房折腾推流,照着这套落地,你会很快拥有一套可复用、可扩展、可审计的直播健康监控体系。下次工单来时,你有数据、有方法、有底气。