外观
监控与排错
本文讲怎么看 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.tool取ping/mtr/traceroute;count有界(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.py的DEBUG_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 # 其余仍 100Web UI 的路由调优页(节点级 cold_potato_med / route_local_pref + 会话级 link_latency)见 web-ui.md。