Skip to content

监控与排错

本文讲怎么看 fleet 健康、怎么定位常见故障、以及路由调优的三个旋钮。健康推导原理见 ../internals/control-server.md;内部互联(iBGP/OSPF)的机制与一致性不变量见 ../internals/internal-routing.md;BGP flap(会话/前缀反复抖动)的检测算法与告警/处置见 ../internals/flap-detection.md。(完整事故案例库存于仓库外的运维叙事档案。)

所有 /api/v1/admin/* 调用需携带 -H "Authorization: Bearer <DN42_CONTROL_ADMIN_TOKEN>",示例省略。

健康监控

控制面持久化每台机器的快照 / 对账 / 应用结果,自动推导五态健康:

状态含义
ok一切正常,实际状态 = 期望状态
stale落后:还没追上最新 generation,或超过 stale_after(默认 900s)没上报
degraded应用失败、上报 degraded、或检测到配置漂移(drift)
down超过 down_after(默认 3600s)完全静默(unknown 除外)
unknown还没收到过任何上报

阈值可配(DN42_CONTROL_HEALTH_STALE_AFTER / _DOWN_AFTER,见 ../reference/configuration.md),读取时叠加,改阈值无需改库。

bash
# 机群概览
curl -s "http://127.0.0.1:8000/api/v1/admin/health"
# 单节点详细健康(最近一次 snapshot/report/apply)
curl -s "http://127.0.0.1:8000/api/v1/admin/nodes/edge1/health"
# 历史事件(排错常用;kind 可选 snapshot / report / apply / reresolve)
curl -s "http://127.0.0.1:8000/api/v1/admin/nodes/edge1/status-events?kind=apply&limit=20"

路由观测

bash
# fleet 路由概览
curl -s "http://127.0.0.1:8000/api/v1/admin/routing/fleet"
# 单节点路由摘要(含 peers 分桶——最优路由来源)
curl -s "http://127.0.0.1:8000/api/v1/admin/nodes/edge1/routing/summary"
# 起源 AS / 前缀翻页 / 时间线
curl -s "http://127.0.0.1:8000/api/v1/admin/nodes/edge1/routing/origins"
curl -s "http://127.0.0.1:8000/api/v1/admin/nodes/edge1/routing/prefixes?limit=1000&offset=0"

peers 分桶(最优路由来源计数)是判断"某节点在不在用 iBGP / 某条会话"的最快信号。明细存索引表 node_route_entries(见 ../reference/database.md)。

互联抖动(flap)观测与处置

对端会话或前缀反复抖动时的处置路径一句话:榜单/ui/fleet/bgp-flaps/ui/fleet/prefix-flaps 找出在抖的会话/前缀)→ 根因定位/ui/fleet/prefix-flaps/localize 区分近端/起源/路径/多源)→ 处置(按对端断开或拒收指定前缀,声明式可撤销)。操作手册见 flap-response.md,检测算法与告警/处置原理见 ../internals/flap-detection.md,Web UI 的抖动页见 web-ui.md

常见故障排查

现象可能原因 / 怎么查
agent 连不上控制面检查 controller_url 可达;常驻模式必须controller_url
新机器注册后没反应在等审批:查 /admin/registrations?status=pending
agent 报 pending-approval 循环已批准但还没 provision;下发 DesiredState 后才能拿 token
健康 stale没追上最新 generation 或太久没上报;看 status-events 最后时间
健康 degraded应用失败或有漂移;看 status-events?kind=apply 的报错
健康 down超过 down_after 完全静默;检查 agent 进程 / 网络
BGP 全断又自愈多半隧道/容器重建触发,1~3 分钟自愈,属正常
WireGuard 握不上手(handshake=0)入站 peer 监听端口没在 wireguard_port_range 内、没对外发布;若对端是动态 IP,等 reresolve(45s)。某接口公钥与其余不一致 = 钥分叉(见 postmortem)
eBGP 会话周期性(~180s)断连 + 导入 0 条MTU 黑洞三联征,测 DF 大包找路径 PMTU(见 postmortem)
某节点比同 AS 节点缺路由多半各节点 internal_topology 不一致、iBGP 没全互联(机制与 checklist 见 internal-routing,事故链见 postmortem)
内网链路延迟异常高(100ms+)跨境节点 WG 拨号方向拨反(见 postmortem)
token 失效(401)过期或被轮换/撤销;agent 401 自愈会重注册,或重新签发更新 identity
想先演练不动机器--plan-only 跑一遍看计划
Docker 部署失败agent 主机能否访问 Docker socket;看 apply-result 的 errors

节点本机排查:

bash
journalctl -u dn42-node-agent@edge1.service -n 120 --no-pager
docker ps --filter label=dn42.managed=true
docker exec dn42-edge1-dn42-wg-gateway-1 wg show
docker exec dn42-edge1-dn42-bird-router-1 birdc show protocols

容器名固定 dn42-<node_id>-dn42-<role>-1

测连通性 / MTU 用 debug-shell 容器router-netns 容器内没有 ping 二进制,对它 docker exec ping 会假阴性;debug-shell 共享 router netns、带 ping/tcpdump/wg/dig(dig @<任播地址> 可直接验证本节点任播 DNS 实例)。DF 大包找 PMTU:ping -M do -s <payload> <目标>

主动拨测(ping/mtr/traceroute)

不登录节点也能从控制面发起一次性拨测:控制面经 WS 门铃把请求下发给节点 agent,agent 在 debug-shell 容器内以 docker exec 流式执行命令、逐块回传,浏览器/终端经 SSE 实时看到输出。debug-shell 共享 router netns,拨测反映节点真实 DN42 路由视角。源码:apps/control-server/app/api/v1/ui/probes.py(端点)、apps/node-agent/agent/probe.py(执行侧);主控制面(TS 面)对应 apps/control-server-ts/src/probeHub.ts / src/probeJobs.ts

bash
# 1) 发起拨测(202 返回 probe_id)
curl -s -X POST "http://127.0.0.1:8000/api/v1/ui/probes" \
  -H "Content-Type: application/json" \
  -d '{"node_id": "edge1", "spec": {"tool": "mtr", "target": "172.20.0.53", "count": 10, "resolve": true}}'
# 2) SSE 实时读输出(先补发已产生的缓冲,再实时推送,done 事件收尾)
curl -sN "http://127.0.0.1:8000/api/v1/ui/probes/<probe_id>/stream"

要点:

  • spec.toolping / mtr / traceroutecount 有界(1–30);resolve 控制逐跳反向 DNS。
  • 目标白名单双端校验target 只接受 DN42 / 私网 / link-local 的 IP 字面量(dn42_common.validate_probe_target,单一事实源),杜绝把节点当作探测任意公网主机的跳板;控制面解析请求体时校验(非法 422),agent 收到事件后 model_validate 再校验一遍(纵深防御)。
  • 逐跳反解mtr -b / traceroute 的逐跳主机名解析走 debug-shell 内只读 resolv.conf 指向的 DN42 递归解析器 172.20.0.53(节点自身只跑权威 DNS,见 packages/dn42_schemas/dn42_schemas/runtime.pyDEBUG_SHELL_RESOLVERS)。
  • 节点无在线 agent → 409;并发拨测超上限 → 429;输出不落库(仅即时,完成后 TTL 回收)。
  • Web UI 有对应拨测页(节点详情「互联」组),见 web-ui.md

路由调优三件套(社区 / 选路)

把"按前缀精调"和"社区"真正用起来的三个旋钮,从粗到细(字段定义见 ../reference/desired-state.md;为什么会话级 local_pref 有 fleet 级爆炸半径,见 postmortem):

旋钮位置作用注意
BgpSessionSpec.link_latency(1-9)会话给该会话学到的路由打 DN42 (64511, 档) 延迟社区(端到端取最差档),propagate 到全网 / looking-glass / 对端可见⚠️ 仅可见性/信令。本 fleet 据此设 local_pref——latency 社区只含 eBGP 链路、不含 fleet 内部跳数,据它选路会让节点弃近就远;选路仍交给默认 hot-potato(IGP 距离已算进内部跳数)
Bird2ConfigSpec.cold_potato_med(默认 50)节点同大区 region 社区的路由 import 时设此 MED(越低越优)→ 优先就近同区域路径跨区域保持 100;改的是 MED(比 local_pref 温和,不越过 AS-path / eBGP-direct)
Bird2ConfigSpec.route_local_pref[{prefix, local_pref}]节点精确匹配的单个前缀bgp_local_pref——精细路由调优只影响该前缀、不波及 session 其余路由(避开 transit peer 的 fleet 级爆炸半径);仅本节点导入侧、不导出;要全 fleet 优先某入口只需在该入口节点配一条

route_local_pref 是"调一个前缀的选路"的正解(替代会话级 local_pref 的粗粒度做法)。例:让 fleet 对 172.20.62.160/27 优先走 pvg2 入口——只在 pvg2 的 base_template.bird.route_local_pref{"prefix": "172.20.62.160/27", "local_pref": 200},经 iBGP 传播即可,且不碰该 session 透传的其余前缀。

bash
# 验证只动目标前缀、不波及透传:
docker exec dn42-pvg2-dn42-bird-router-1 birdc show route for 172.20.62.160/27 all   # local_pref=200
docker exec dn42-pvg2-dn42-bird-router-1 birdc show route protocol <sess> count        # 其余仍 100

Web UI 的路由调优页(节点级 cold_potato_med / route_local_pref + 会话级 link_latency)见 web-ui.md