Silas2323/fabric-chaos-sentry
GitHub: Silas2323/fabric-chaos-sentry
面向 AI 网络 fabric 的混沌工程项目,通过在 EVPN-VXLAN/Cumulus 环境中主动注入故障来验证监控系统的检测能力和收敛时间。
Stars: 0 | Forks: 0
# fabric-chaos-sentry
一个手动搭建的 EVPN-VXLAN fabric,已运行至接近生产环境的状态,目前正
在为其注入蓄意的故障——以此来验证监控系统能否捕获关键问题。这是
一本运维人员的实战笔记,而不是一个演示仓库:以下每一个数据都是
在本仓库的 `configs/` 所在的实验室中实测的,每一项“故障记录”都是真实的
事件,任何尚未执行的测试都会明确标出。
**核心主张:** AI 训练流量依赖于无损、低延迟的 fabric。
最具破坏性的故障并非完全中断的链路——而是那些让一切看起来都“正常”,
却让集合通信变得极其缓慢的静默配置错误和灰色故障。你无法确信监控系统
能够捕获这些问题,除非你故意注入它们。你同样无法确信你的 fabric 真正
有效,除非你曾无意中将其搞崩溃过。
## 构建内容
拓扑:一个完整的 CLOS 网络——2 个 spine,2 个 leaf,每个 leaf 连接到
每个 spine——基于 Cumulus Linux(使用 NVUE CLI,底层为 FRR 10.x)
在 NVIDIA Air 上构建。四台 Ubuntu 主机连接在 leaf 下方(每个 leaf 两台),
作为 EVPN-VXLAN 租户。
| 节点 | Loopback / Router-ID | ASN | Fabric 端口 |
|---------|-----------------------|----------------|----------------------------------|
| SPINE-1 | 10.0.0.1/32 | 65100 (共享) | swp1→LEAF-1, swp2→LEAF-2 |
| SPINE-2 | 10.0.0.2/32 | 65100 (共享) | swp1→LEAF-1, swp2→LEAF-2 |
| LEAF-1 | 10.0.0.11/32 | 65101 | swp1/2 上行链路; swp10/11 服务器 |
| LEAF-2 | 10.0.0.12/32 | 65102 | swp1/2 上行链路; swp10/11 服务器 |
完整的各节点配置转储位于 [configs/](configs/)。
**BGP unnumbered 底层网络 (RFC 5549)。** 每条面向 fabric 的链路都运行
eBGP,且完全不使用 IPv4 寻址——邻居通过接口命名,会话通过 IPv6
链路本地地址(`fe80::`)下一跳建立,而 IPv4 前缀(即 loopback)则
*通过*该 IPv6 下一跳进行传递。Loopback 通过 `redistribute connected`
进行通告——这也是为什么它们在 BGP 表中携带来源 `?`(不完整)的原因;
`network` 语句是更干净的生产环境模式,而本实验室是有意采取了
这种捷径。ZeroTouch 邻接:在两台 Cumulus 设备之间插入一条链路,会话
就会建立,完全不需要逐链路规划 IP。
**EVPN-VXLAN 叠加网络。** 所有四个节点(包括两个 spine)都启用了
L2VPN EVPN 地址族——spine 永远不会发起 VTEP,但必须
在 leaf 之间中继 EVPN 路由;跳过它们是典型的初级
错误。这两个 leaf 运行实际的 VTEP,源于其 loopback。
VLAN 10 → VNI 10010 和 VLAN 20 → VNI 10020 作为单个广播域
跨越两个 leaf 进行延伸。
**具有 Anycast 网关 + L3VNI 的对称 IRB。** 两个 leaf 为每个
VLAN 运行一个 SVI 作为本地默认网关,共享一个 anycast 网关 MAC
(`44:38:39:ff:00:01`),因此无论主机连接到哪个 leaf,
它都会获得相同的网关身份。跨 VLAN 流量(VLAN 10 ↔ VLAN 20,租户
VRF `TENANT1`)通过 L3VNI `104001` 进行对称路由,
而不是通过一个中央路由器进行发夹式转发——每个 leaf 在本地
路由进入 VXLAN fabric,而远端 leaf 在本地路由出去。
## 测量结果
- **在 FIB 和 BGP 中均已验证的 ECMP。** `10.0.0.12/32`(LEAF-2 的
loopback,从 LEAF-1 观察)解析为两个 `fe80::` 下一跳——每个
分别经由一个 spine——两者都安装在 FIB 中,并且 BGP 在 multipath 下
显示 2 条路径。由于两个 spine 共享 ASN 65100,两个 AS 路径
完全相同,并且无需额外配置即可满足标准 ECMP 条件;
BGP 仍会将一条路径标记为 "best (Older Path)"——那只是
通告的决胜规则,两条路径都会进行转发。设计权衡:
为每个 spine 分配唯一的 ASN(另一种常见的 CLOS 模式)会使得
AS 路径不同,从而需要配置 `bestpath as-path multipath-relax` 来保持
两条路径。
- **约 3 秒的链路故障收敛时间。** 断开一条 leaf↔spine 链路并
测量重新收敛的时间,结果约为 3 秒:unnumbered BGP 会话会随着
链路本身一起断开(没有 IP 邻居可以维持,因此无需等待 hold-timer),
剩下的延迟则是 VM 载波检测延迟。BFD 是推动其达到亚秒级的
已知下一步——参见路线图。
- **具有跳数证据的底层网络连通性。** 双向的 Loopback 到 Loopback
Ping 测试均已验证:相邻节点之间 `ttl=64`,穿过一个 spine 的
leaf 到 leaf 之间为 `ttl=63`——TTL 的差值证实了
实际路径长度,而不仅仅是“Ping 通了”。
- **62 处的对称 IRB 的 TTL 证据。** 连接到不同 leaf 的 VLAN 10 和
VLAN 20 主机之间的 Ping 落在 TTL 62 处 (64 − 2):在入口 leaf 的 SVI 处
减一,在出口 leaf 的 SVI 处减一。这个
两跳的特征就是流量确实通过对称路由穿过了两个 leaf 的本地网关,
而不是通过单台设备进行发夹式转发的证明。
支持这些数字的原始抓包数据位于 [evidence/](evidence/) 中。
## 发生过的故障及其教训
- **以配置作为唯一事实来源,这是惨痛的教训。** NVIDIA Air 的恢复按钮
会在会话出现故障时重置节点的运行状态——包括从未保存的
配置。这曾经导致一次正在进行的构建前功尽弃。解决方法并不是
“小心谨慎”,而是采用与生产环境 network-as-code 相同的策略:
存放在 git 中的配置才是唯一事实来源,而不是设备上当前运行的
内容。`configs/` 中的每个文件都是因为这个而存在的。
- **密码掩码恢复陷阱。** `nv config show -o commands`
会为本地账户打印 `hashed-password '*'`——它掩码了真实的
哈希值而不是显示出来。逐字重播该输出(例如,从保存的转储中恢复)
会将账户密码设置为一个任何输入都无法匹配的值,从而将自己锁在门外。
解决方法:在准备重播配置之前剔除 `aaa`/`user` 行,并且在没有
开启另一个已通过身份验证的会话作为救命稻草的情况下,绝不
触碰身份验证配置。生产环境的类比是 vault 或 TACACS+——凭证完全
存在于配置工件之外,这也是为什么这里的 `configs/`
也将它们清除了。
- **SVI 已创建但未配置地址。** 在 LEAF-1 上,`vlan20` 最终被创建
并绑定了 VRF,但从未分配地址——`ipv4 address` 行在一次混乱的
apply 过程中丢失了。该 SVI 存在,在配置中也显示了出来,但
默默地在罢工:没有可用于路由的地址,没有可用的网关,
也没有报错。它是通过将健康的 `vlan10` 输出与 `vlan20`
进行并排对比发现的——那个正常工作的孪生兄弟让缺失的行变得
一目了然,而仅仅盯着坏掉的那个是永远看不出来的。两个教训:
“对象存在”和“对象可用”是不同的检查点,当你有已知良好的
同胞配置时,对比诊断胜过孤立地分析故障件。
- **重复 VNI 校验捕获。** 使用较旧的方法手动映射了 VLAN 30 → VNI 104001
——而此时 `TENANT1` VRF 声明已经在自动推导相同 VNI 的
L3VNI 管道。`nv config apply` 直接拒绝了重复的声明:手动输入的
配置和推导出的配置都在声明对 VNI 104001 的所有权。教训在于
基于意图的配置模型:当系统从你的声明中推导出对象时,用旧方法手动
放置相同的对象不仅是多余的——它是一种冲突,而在 apply 阶段就能
捕获到它的验证控制平面,正是被拒绝的命令和微妙的崩溃状态之间的
区别。
## 混乱与检测层
上述 fabric 是基础底座。在此之上,`chaos/` 注入
此类 fabric 在实际现场环境中产生的故障,而 `sentry/`
(Prometheus/Grafana/Alertmanager)必须证明它能捕获这些故障——
带有可测量的检测时间,而不是猜测。
```
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ NVIDIA Air lab │ │ Kubernetes cluster │
│ EVPN-VXLAN fabric (BUILT) │ │ (PLANNED — see Roadmap) │
│ SPINE-1/2 ── LEAF-1/2 │ │ Proxmox VMs, kubespray │
│ ▲ │ │ ▲ │
│ │ chaos/fabric/ │ │ │ chaos/k8s/ │
│ │ (Ansible + NVUE) │ │ │ (Chaos Mesh) │
└────────┼────────────────────┘ └────────┼─────────────────────┘
│ node_exporter │ node_exporter
│ (ethtool collector) │ (+ kube-state-metrics)
▼ ▼
┌──────────────────────────────────────────┐
│ sentry/ (Docker Compose) │
│ Prometheus ──► Alertmanager ──► webhook │
│ │ │
│ └──► Grafana (Fabric Overview) │
└──────────────────────────────────────────┘
```
## 仓库布局
```
configs/ # Per-node NVUE config dumps (nv config show -o commands), scrubbed of secrets
docs/ # Cheat sheet, troubleshooting guide, one runbook per chaos scenario
evidence/ # Raw command output backing the "what was measured" claims above
chaos/
fabric/ # Cumulus failure injection (Ansible/NVUE) — 6 scenarios
k8s/ # Chaos Mesh experiments — 4 scenarios
sentry/ # Prometheus + Grafana + Alertmanager (Docker Compose)
```
每个混乱场景文件夹都包含一个注入脚本,一个
`expected-signals.md`(因果链 + 必须触发的确切指标/警报),
以及一个回滚脚本。
## 场景表
| # | 注入的故障 | 预期信号 | 检测时间 | 补救措施 |
|---|------------------|-----------------|----------------|-------------|
| F01 | **PFC 配置错误** — 无损优先级与一个 leaf 端口未绑定 | 邻居上 Pause-frame 速率激增,TC3 上的丢包;`PFCPauseFrameStorm`, `PFCLosslessClassDrops` | 目标 < 60 秒 (实测:TODO) | [Runbook](docs/runbooks/01-pfc-misconfig.md) → [回滚](chaos/fabric/01-pfc-misconfig/rollback.yml) |
| F02 | 链路抖动 (leaf↔spine) | Carrier 变化,BGP 循环,ECMP 路径丢失 | TODO | TODO |
| F03 | BGP 会话重置 | 对端离开 Established 状态,EVPN 路由下降 | TODO | TODO |
| F04 | ECN 阈值失准 | 队列深度高 **且缺少 ECN 标记**,Pause 风暴 | TODO | TODO |
| F05 | MTU 不匹配 | 某一跳丢包,大负载 RDMA 失败但 BGP 正常 | TODO | TODO |
| F06 | 灰色故障(链路性能下降) | 缓慢的 CRC/错误增加,每流不对称 | TODO | TODO |
| K01 | Pod kill | 重启次数,恢复到 Ready 的时间 | TODO | TODO |
| K02 | 节点间的网络延迟 | RTT 偏移,TCP 重传 | TODO | TODO |
| K03 | 网络分区 | 跨节点调用失败,endpoint 频繁变动 | TODO | TODO |
| K04 | 资源匮乏 | CPU 限流,OOMKill,节点压力 | TODO | TODO |
**状态:** F01 已完成脚手架搭建——注入/回滚剧本、警报规则和
Runbook 均已编写——但**从未被运行过**:没有实测的
检测时间,也尚未在实时的 fabric 上进行过演练。它还
针对尚未在基础 fabric 上绑定的 PFC/QoS 配置文件进行注入
(参见路线图),因此首次真正运行需要先完成该层。所有其他
场景都是带有 TODO 文档的存根。K01–K04 还依赖于目前
尚不存在的 Kubernetes 集群。
## 预期工作流(目标状态)
```
# 1. Start the detection stack
cd sentry
docker compose up -d
# Prometheus :9090 │ Grafana :3000 (admin/admin) │ Alertmanager :9093
# 2. Point Prometheus at your lab
# Edit sentry/prometheus/prometheus.yml with your NVIDIA Air mgmt IPs
# and K8s node IPs, then: curl -X POST localhost:9090/-/reload
# 3. Run scenario F01
cd ../chaos/fabric
ansible-playbook -i inventory/hosts.yml 01-pfc-misconfig/inject.yml \
-e target_switch=leaf01 -e target_interface=swp1
# 4. Generate RoCE load, watch Grafana, record time-to-alert, then roll back
ansible-playbook -i inventory/hosts.yml 01-pfc-misconfig/rollback.yml \
-e target_switch=leaf01 -e target_interface=swp1
```
## 设计决策
### 补救措施是 Runbook,而不是自动修复(目前)
每个警报都映射到已记录的 Runbook,而不是自动化的补救措施。这是
有意为之的,并且涉及到影响范围:
1. **错误的自动修复比缓慢的人工修复更糟糕。** 一个自动化执行者
如果为了响应拥塞信号而重写 QoS 配置或翻转链路,一旦发生误报,
将会*制造*出它旨在防止的中断。
在 fabric 中,补救操作(排空一条链路、重置一次会话、重写
PFC/ECN 配置)会降低容量或导致状态动荡——这正是你在由其他原因
(例如,真正的 incast)引起的真正拥塞期间不希望发生的。
2. **必须首先证明检测精度。** 存在混乱场景是为了
测量这种精度:每个的故障都有一个已知的真实基准,因此
误报率和漏报率是可测量的,而不是猜测的。
3. **晋升路径。** 一项补救措施只有在经历了多次混乱测试并幸存下来后,
才能获得自动化资格:Runbook → 由人工触发的脚本化修复(回滚剧本
已经是这样了)→ 带有范围限制(一个端口、一台交换机)和
自动停止条件的受控自动修复。
回滚剧本被有意编写为未来的自动化程序:
它们在执行后会验证状态,并拒绝在多于一台的设备上运行。
### 其他选择
- **1:1 场景→警报映射。** 每个混乱场景都有一个命名警报。
如果某个场景未触发警报,那就是检测差距——这是该项目能产出的
最有价值的发现。
- **15 秒的 scrape 间隔**,以便测量的检测时间反映的是规则延迟,
而不是抓取延迟。
- **注入的配置从不保存到 startup**——重启始终是
有效的最后手段回滚。
- **Recording rules 抽象了平台指标名称**(Pause 计数器因
NIC/驱动程序而异),因此仪表板和警报共享同一个定义。
- **配置存放在 git 中,机密信息已被清除。** `configs/` 保存了每个
节点精确的 `nv config show -o commands` 输出,并移除了
`aaa`/`user` 行——关于为什么这些行很危险(甚至不应提交),
请参阅上文的“密码掩码恢复陷阱”。
## 实验室环境
- **Fabric:** NVIDIA Air —— 2 个 spine / 2 个 leaf Cumulus Linux (NVUE),
EVPN-VXLAN,具有 anycast 网关的对称 IRB。RoCEv2 PFC/ECN QoS
已规划范围,但尚未在基础 fabric 上绑定(路线图)。
- **检测:** Prometheus / Grafana / Alertmanager 通过 Docker Compose
一个 Kubernetes 集群(通过 kubespray 部署的 Proxmox 虚拟机,
使用 Chaos Mesh 进行注入)已计划但尚未构建——参见路线图。
`chaos/k8s/` 场景文档和 `sentry/` 中的 k8s 抓取任务是
提前编写的脚手架。
## 路线图
- [ ] 在 fabric 链路上启用 BFD;重新进行收敛演练,并针对上述约 3 秒的基线
记录前后的数据。
- [*] 在基础 fabric 上绑定实际的 RoCEv2 QoS 层(switch-priority 3 上的 PFC
+ ECN/DCQCN 阈值)——目前只有 F01 混乱场景假定它存在;
而已知良好的配置文件本身尚未构建。
- [ ] 针对真实的 RDMA 负载首次运行 F01 并记录实测的
检测时间;实现 F02–F06。
- [ ] 构建 Kubernetes 集群:通过 kubespray 部署 Proxmox 虚拟机,
Chaos Mesh,kube-state-metrics——然后针对其实现 K01–K04。
- [ ] 来自注入脚本的 Grafana 注释(混乱时间轴叠加层)。
- [ ] 架构图。
- [ ] 平行构建:在 EVE-NG 上使用 Arista + Ansible,作为相同底层/叠加
设计的第二供应商实现——进行多供应商重复验证,
而不是重新验证核心主张。
- [ ] 一旦检测精度得到证实,首次为 F01 引入受控的自动补救措施。
标签:子域名突变, 版权保护, 系统提示词, 自定义请求头