标签: 网络命名空间

  • 深入混沌工程陷阱排查:无差别 tc 丢包注入引发的 Kubelet 脑裂驱逐风暴与爆炸半径管控实战

    排查过程中,我们遭遇了一次由于混沌工程(Chaos Engineering)实施不当引发的生产级雪崩。原本只是一次常规的 GameDay 演练,目的是向支付网关注入 100% 网络丢包以验证断路器(Circuit Breaker)的降级 SLO,最终却导致该节点被标记为 NotReady,数百个核心 Pod 被强行驱逐,跨可用区的网络流量剧烈抖动。最终结论很明确:在对配置了 hostNetwork: true 的目标 Pod 注入网络故障时,未做端口或目标 IP 级别的精细过滤,直接在物理网卡上挂载了全局的 tc netem 丢包规则,阻断了 Kubelet 与 API Server 的心跳,导致控制平面触发了无差别的脑裂驱逐(Eviction)。

    混沌工程的核心原则是“控制爆炸半径”,而不是在没有消防措施的情况下往机房里扔燃烧弹。这种因为对底层网络命名空间(Network Namespace)和 K8S 节点状态机理解不透彻造成的事故,属于典型的低级失误。

    案发现场:从演练降级到节点失联

    某次核心链路故障演练期间,监控大盘显示支付网关的 99 线延迟如预期般飙升,断路器指标(hystrix_circuit_breaker_open)也正常触发。但演练进行到第 60 秒时,事态失去控制:

    1. Prometheus 疯狂涌出 KubeNodeNotReady 告警。

    2. 该节点上的数十个与演练完全无关的微服务(包括本地缓存 Redis 和日志采集 DaemonSet)连接数全部归零。

    3. K8S 控制平面开始调度新 Pod 到其他节点,引发瞬间的镜像拉取风暴和 CPU 满载(Load Average 瞬间飙升到 80+)。

    登录目标节点,系统自身其实毫无压力,CPU、内存、磁盘 IO 全在低位。但查看 Kubelet 日志,满屏都是触目惊心的超时报错:

    E0914 10:23:45.123456    1234 kubelet_node_status.go:456] Error updating node status, will retry: error getting node "node-pay-01": Get "https://10.0.0.1:6443/api/v1/nodes/node-pay-01": dial tcp 10.0.0.1:6443: i/o timeout
    E0914 10:23:55.124567    1234 kubelet_node_status.go:456] Error updating node status, will retry: error getting node "node-pay-01": Get "https://10.0.0.1:6443/api/v1/nodes/node-pay-01": dial tcp 10.0.0.1:6443: i/o timeout
    

    Kubelet 彻底哑火了。

    底层机制:tc netem 与 hostNetwork 的致命交叉

    为什么对一个“支付网关 Pod”注入网络故障,会干掉整个宿主机的网络?

    排查混沌注入工具(此处使用的是基于 eBPF/tc 封装的底层执行器)的注入记录,并直接在问题节点执行 tc qdisc show 查看流量控制规则:

    $ tc qdisc show dev eth0
    qdisc netem 8001: root refcnt 9 loss 100%
    

    破案了。工具在物理网卡 eth0 的 root 队列规则(qdisc)上,直接挂载了 netem loss 100%。

    在 Kubernetes 环境中,绝大多数 Pod 运行在独立的 Network Namespace 中(由 CNI 分配独立 IP 和 veth pair),对这些 Pod 注入网络故障,tc 规则只会作用于 Pod 对应的 veth 接口或内部的 eth0 上。 然而,由于支付网关需要极高的网络吞吐,运维团队为该 Deployment 配置了 hostNetwork: true。这意味着该 Pod 共享宿主机的 Root Network Namespace。

    当混沌引擎按照常规逻辑查找该 Pod 的网络接口并实施注入时,它拿到的是宿主机的 eth0。更要命的是,注入配置里没有指定任何 Filter(过滤器),直接进行了全局 100% 丢包。

    来看看 K8S 节点状态机的默认时间线:

    1. Kubelet 每 10 秒向 API Server 发送一次心跳(node-status-update-frequency)。

    2. 当 API Server 连续 40 秒(node-monitor-grace-period)收不到心跳,kube-controller-manager 就会将 Node 标记为 NotReady。

    3. 如果节点持续 NotReady 达到 300 秒(Pod 默认的 tolerationSeconds),控制平面将触发驱逐逻辑。由于演练中某些关键有状态 Pod 的容忍度被调整为了 60 秒,驱逐几乎在节点失联后立刻发生。

    拨乱反正:爆炸半径的精细管控

    混沌工程不是盲目的破坏。既然我们要对 hostNetwork 下的特定进程注入故障,就必须在内核网络栈层面实现精确制导。

    1. 废弃无差别的 root qdisc

    切忌直接在 root 节点挂载 netem,必须结合 htb (Hierarchical Token Bucket) 或 prio 建立层级队列,并使用 u32 或 cgroup 过滤器匹配特定流量。

    如果要针对支付网关(监听在 8080 端口,目标下行调用 3306 数据库)进行丢包注入,正确的底层 tc 规则构建路径应该是:

    # 1. 在 eth0 根节点创建 prio 队列(分为 3 个频段,默认走 band 2)
    tc qdisc add dev eth0 root handle 1: prio
    
    # 2. 在 band 1 (handle 1:1) 上挂载 netem 丢包规则
    tc qdisc add dev eth0 parent 1:1 handle 10: netem loss 100%
    
    # 3. 使用 u32 过滤器,仅将发往 3306 端口的流量导向丢包队列 (classid 1:1)
    tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
        match ip dport 3306 0xffff flowid 1:1
    
    # 4. (关键) 显式放行 Kubelet 与 API Server 的通信端口 6443,导向正常队列 (classid 1:2)
    tc filter add dev eth0 protocol ip parent 1:0 prio 0 u32 \
        match ip dport 6443 0xffff flowid 1:2
    

    2. 使用支持白名单的现代混沌工具

    如果在 Kubernetes 中使用 Chaos Mesh 或 Litmus,必须在 YAML 中显式配置目标及绕过规则。现代混沌工具提供了网络级别的高级选择器。例如 Chaos Mesh 的 NetworkChaos:

    apiVersion: chaos-mesh.org/v1alpha1
    kind: NetworkChaos
    metadata:
      name: payment-loss-injection
    spec:
      action: loss
      mode: one
      selector:
        labelSelectors:
          app: payment-gateway
      loss:
        loss: '100'
      # 必须限制方向,并在外部控制器中规避控制面端口
      direction: to
      target:
        selector:
          labelSelectors:
            app: mysql-cluster
    

    如果是针对 hostNetwork,必须开启特定的 BPF 注入模式或在系统级预设 K8S 控制平面的网段白名单(CIDR exclude)。

    避坑总结

    高可用系统的韧性验证,基础是敬畏系统。在生产环境进行故障注入,必须具备“一键终止(Abort Button)”的能力,但如果网络故障直接干掉了节点级别的通信,你的“一键终止”指令连下发到 Chaos Daemon 的通道都会被切断,这就演变成了不可控的灾难。做混沌实验前,先问自己三个问题:目标域到底在哪个 Namespace?底层注入手段会不会产生越权逃逸?如果控制通道断了,实验能不能自动回滚?

    同类问题速查排查清单

    1. 宿主机网络逃逸排查:检查目标业务 Pod 是否配置了 hostNetwork: true。如果是,常规的网络故障注入(如直接作用于网卡的 tc 规则)将污染整个宿主机网络环境。

    2. Kubelet 心跳阻断确认:当节点突然 NotReady,优先检查 API Server 审计日志与问题节点本地 Kubelet 日志,确认 i/o timeout 端口是否为 6443,判断是否为网络层拦截导致。

    3. 残留 tc 规则清理:如果注入工具异常退出导致规则未清理,手动执行 tc qdisc del dev root 恢复网络通信,切忌盲目重启节点。

    4. 混沌控制通道隔离:混沌代理(Chaos Daemon)所在的端口和控制平面(API Server 6443, etcd 2379, kubelet 10250)必须在注入系统的全局白名单(Whitelist/Bypass)中,确保任何情况下的实验可中止性。