Boce-Site · 自托管拨测监控平台
前言:为什么自托管拨测
boce.com 是国内老牌的商业拨测平台,HTTP / DNS / PING / TCPing / 劫持 / 被墙检测样样齐全,按波点计费。
问题在于:
- 平台 UI 是公版,告警格式没法定制,群里来一长串原始 JSON,10 条 URL 就是 6000+ 字符,眼睛都看花了
- 波点余额监控没有,经常到第三天发现「余额不足,已停止拨测」,业务告警全断
- 多项目隔离没有,一个公司一套配置,谁都能改别人的 URL
- 数据不能导出,历史曲线只能用它的图表看,没法和我们自己的 Grafana 拼起来
- 告警通道单一,只有邮件 / 钉钉,没有 TG / 飞书
于是就有了 boce-site —— 把 boce.com 的 API 包成自托管的小工具:
- 玻璃态 Web 控制台,UI 自己写
- Prometheus 抓取端点,直接并入现有监控体系
- 周期任务调度,APScheduler 跑 12 种检测类型
- 告警通道分发,TG + 飞书 webhook,自己定义紧凑格式
全部跑在一个 Docker 容器里,SQLite 存数据,没有 MySQL、没有 Redis、没有 worker 节点。
架构:单容器装下所有
1 | ┌───────────────────────────── boce-site 容器 ─────────────────────────────┐ |
单容器的设计取舍:
- Web + Scheduler + Prometheus exporter 全在同一个 Flask 进程,APScheduler 用 BackgroundScheduler 起在主线程
- SQLite 单文件,跟容器一起打包,
docker-compose up起来就能用,不需要外挂存储 - 整容器内存占用 < 200MB,1 核 1G 机器就能跑
核心能力
🎯 玻璃态 Dashboard · 10s 自动刷新
打开 / 第一眼看到的是:
- 实时余额(波点余额 / 已消耗 / 预计可用天数)
- 日均消耗(过去 7 天平均)
- 监控 URL 总数 / 异常数
- 最近一轮结果表格(按 URL 分行,单元格按节点 + 探测类型切片)
每 10 秒自动拉一次 /api/state,不用刷新页面。
⚡ 手动拨测 · 单条 + 批量
单条:点列表里的「⚡ 拨测」按钮 → 后端立刻调 boce.com task/create → 1s 轮询 ≤ 30s → 进度条贴在卡片顶部 → 完成时按节点切片显示详情。
批量:多选勾选 → 「⚡ 拨测选中」→ 后端守护线程批量跑 → 前端 1.5s 轮询 /api/probes/batch/<id> → 进度条按 URL 切片 → 完成时逐行解锁「⚡ 拨测」按钮。
1 | URL 上勾选 N 种 probe_type → 每个类型一个线程 |
进度条跨页面保留 —— 即使中途切到 /config 或 /channels 页面,再切回 Dashboard 进度条还在(DB 字段 + SSR data-* 恢复)。
⏰ 周期任务 · 三种调度
每个 URL 独立 APScheduler job,三种调度方式:
| 类型 | 用途 | 示例 |
|---|---|---|
interval |
固定间隔 | interval=300 → 每 5 分钟一次 |
cron |
Cron 表达式 | cron="0 */1 * * *" → 每整点一次 |
bigweek |
ISO 奇数周 | bigweek=86400 → 隔一周(ISO week 奇数)执行一次 |
新增 URL 的 next_run_at 默认 = now + interval(不会立即补跑一次,避免刚加监控就把余额烧光)。
🧬 12 种检测类型 · 多类型并行
| Type | 标签 | 同步/异步 | 类型专属参数 | 每次波点 |
|---|---|---|---|---|
http |
HTTP | 异步 | method / post_data / headers |
1 / 节点 |
dns |
DNS | 异步 | dns_type / dns_ns_server |
1 / 节点 |
ping |
PING | 异步 | — | 1 / 节点 |
tcping |
TCPing | 异步 | — | 1 / 节点 |
hijack |
劫持检测 | 异步 | hijack_except_domain |
10 / 节点 |
ipv6 |
IPv6 | 异步 | — | 100 / 域名 |
pollute |
DNS 污染 | 同步 | — | 10 / 域名 |
wall |
被墙检测 | 同步 | — | 10 / 域名 |
qq |
QQ 拦截 | 同步 | — | 10 / 域名 |
wechat |
微信拦截 | 同步 | — | 10 / 域名 |
blacklist |
备案黑名单 | 同步 | — | 10 / 域名 |
ssl |
SSL 证书 | 同步 | — | 10 / 域名 |
同步类型(is_async=False):create_task 直接 inline 返回结果,省一轮 30s 轮询 —— 这种类型大部分是”域名级”检测(DNS 污染 / 被墙 / QQ 拦截),不需要按节点切片。
每个 URL 可以同时勾选 N 种类型,并行执行。例:给一个生产 API 配置 HTTP + DNS + PING + 劫持检测 + SSL 证书,一次任务 5 种维度全覆盖。
🗺️ 多区域拨测
- 默认 6 个南方节点(广东电信 / 广东联通 / 广东移动 / 福建电信 / 杭州电信 / 上海联通)
- 节点自定义选择(按省份 + ISP 精确匹配)
- 节点列表存在
regions表里,可在线增删
📊 Prometheus 集成 · /metrics 端点
容器暴露 /metrics,从 SQLite 读后转成 Prometheus text exposition 格式:
1 | # HELP probe_success Result of probe (1 = success, 0 = failure) |
labels 包含:
probe_type— http / dns / ping / hijack …source—periodic/manualtag— 用户自定义标签(prod / staging / 灰度 …)url— 完整 URLnode_name— 拨测节点
容器重启不丢历史 —— 所有数据在 SQLite 里,restart 一下 Prometheus 还能继续抓同一份数据。
📡 告警通道 · TG + 飞书
每渠道独立 alert_cron(默认 0 */1 * * * 每小时一次),到点遍历所有 active URL 取最新结果。
告警格式从 6000+ 字符压到 1500 字符(10 URL 场景):
1 | 🚨 拨测告警 · 2026-08-24 12:34:56 · 3 个 URL 异常 |
压缩要点:
- 类型标签中文化:
劫持检测 / DNS 污染 / 被墙检测 / 备案黑名单 / SSL 证书 - 健康节点 ≤3 直接展开,>3 折叠为「N 节点正常(折叠)」
- 延迟整数 ms、单空格分隔、无
<pre>块 - TG 走 HTML,飞书走 markdown,两套模板各自紧凑
🔐 鉴权 · admin + viewer 双角色
admin账户:完整权限(ADMIN_USER/ADMIN_PASSWORD)viewer账户:只读(VIEWER_USER/VIEWER_PASSWORD,留空 = 不启用)- 服务端拦截任何非 GET/HEAD/OPTIONS 请求(
/api/*返 403 JSON,普通路径返 403 HTML) - UI 层把所有按钮
disabled+ 加viewer-modebody class 用 CSS 置灰 - 默认收起侧边栏,禁用任何编辑 / 拨测 / 删除
- 服务端拦截任何非 GET/HEAD/OPTIONS 请求(
给老板 / 外部审计 / 客户演示监控数据用,一个 viewer 账号搞定,任何写操作服务端硬拒绝。
快速开始
1 | git clone <repo> ~/data/boce-site |
打开 http://localhost:9191/,用配置的账号登录。
| 入口 | URL |
|---|---|
| Dashboard | http://localhost:9191/ |
| 监控配置 | http://localhost:9191/config |
| 告警通道 | http://localhost:9191/channels |
| 登录 | http://localhost:9191/login |
| Prometheus 抓取 | http://localhost:9191/metrics |
| 实时余额 API | curl http://localhost:9191/api/quota |
| 单条手动拨测 | curl -X POST -H 'Content-Type: application/json' -d '{"url_id":N}' http://localhost:9191/probe/now |
| 批量手动拨测 | curl -X POST -H 'Content-Type: application/json' -d '{"url_ids":[1,2,3]}' http://localhost:9191/urls/probe-batch → 返回 {"ok":true,"batch_id":"<uuid>"} |
| 批量进度查询 | curl http://localhost:9191/api/probes/batch/<batch_id> |
技术栈
- Web:Flask 3.x + 原生 Jinja2 模板(无前后端分离,玻璃态 CSS 直接写在模板里)
- 调度:APScheduler 3.x(BackgroundScheduler 跑在 Flask 主线程)
- 存储:SQLite 3(单文件,200MB 内存可支撑 1000+ URL × 12 类型 × 6 节点历史)
- 指标导出:
prometheus-client - 告警图(可选):WeasyPrint + pypdfium2 + Pillow(把告警 HTML 转 PNG 发群)
- 容器化:单个 Docker 镜像,docker-compose 一行起
依赖列表(requirements.txt):
1 | flask>=3.0,<4.0 |
不是什么
跟上一篇文章 Consul Register Manager 一样,先把边界说清楚:
- ❌ 不是 boce.com 替代品:底层拨测还是调
task/create+task/info,自托管只解决 UI / 告警 / 集成 - ❌ 不是 Prometheus 替代品:自己不存时序数据,只暴露
/metrics给外部 Prometheus 抓 - ❌ 不是商业级 SLA 平台:单容器 SQLite,几十万 URL 量级需要换 Postgres + 多 worker
- ❌ 不是 DNS 权威服务器:不做权威解析,只监测权威解析是否被篡改 / 污染
- ❌ 不是 APM:不追踪应用层调用链,只做”端到端可达性”
总结成一句话
把商业拨测平台的”标准 UI + 固定告警”换成”自定义玻璃态控制台 + 紧凑告警格式 + Prometheus 原生集成”,单容器装下所有,5 分钟跑起来。

