1. 项目概述:当混沌实验“失控”之后
在云原生微服务架构下,混沌工程已经成为保障系统韧性的标准实践。Chaos Mesh 作为一款强大的云原生混沌工程平台,让我们能够方便地在 Kubernetes 集群中模拟各类故障,如网络延迟、Pod 被杀、IO 故障等。其核心魅力在于“自动化”:实验创建、故障注入、以及实验结束后的自动恢复。然而,在实际生产或测试环境中,我们偶尔会遇到一个令人头疼的情况——实验明明已经结束或手动停止,但被注入的故障却没有如预期般自动恢复。集群中某个服务依然网络不通,或者某个 Pod 持续处于异常状态,这无异于一场“人造事故”演变成了真实的线上故障。
这种情况我遇到过不止一次。最初以为是偶发现象,但后来发现,其背后往往隐藏着资源残留、控制器异常、网络策略冲突或权限问题等深层次原因。本次分享的主题,正是聚焦于“Chaos Mesh 注入实验后未自动恢复”这一典型问题。我将结合多次实战排查的经验,为你梳理出一套从现象定位到根因分析,再到安全清理的完整方法论。这不仅仅是解决一次故障,更是深入理解 Chaos Mesh 运作机制、Kubernetes 控制循环以及相关资源生命周期的绝佳机会。无论你是正在被此类问题困扰的运维工程师,还是希望深化对混沌工程底层原理理解的开发者,接下来的内容都将提供直接的参考价值。
简单来说,当自动恢复失效,我们的角色就从“混沌实验的设计者”转变为“集群异常的消防员”。目标很明确:第一,快速定位故障残留点,恢复业务;第二,彻底清理异常资源,避免复发;第三,复盘根因,优化运维流程或实验配置。下面,我们就按照这个逻辑,一步步拆解。
2. 故障现象深度解析与初步定位
当接到“实验停了但服务没恢复”的告警时,切忌盲目操作。一套系统化的排查思路能帮你事半功倍。首先,我们需要对故障现象进行精确的“画像”。
2.1 典型未恢复现象枚举
根据我的经验,未自动恢复的表现主要集中在这几个方面,你可以对照检查:
- 网络类故障残留:这是最常见的一类。你使用了
NetworkChaos注入网络延迟、丢包或分区,实验停止后,目标 Pod 之间依然无法正常通信。ping不通或者应用层连接超时。 - Pod 生命周期类故障残留:例如,你使用了
PodChaos中的pod-kill动作,实验停止后,预期的替换 Pod 没有成功启动,或者原 Pod 没有被重建,导致服务副本数不足。 - 压力类故障残留:使用
StressChaos为 Pod 注入 CPU 或内存压力后,实验结束,但 Pod 的resource limit似乎依然被占用,节点负载居高不下,甚至 Pod 本身因为OOMKilled而无法启动。 - IO 类故障残留:通过
IOChaos模拟磁盘 IO 延迟或错误,实验结束后,相关容器的磁盘读写性能依然异常缓慢。 - 内核类故障残留:使用
KernelChaos注入诸如fail_nth等内核级故障后,即使实验删除,系统调用层面的异常行为可能持续。
注意:首先需要确认实验是否真的“已停止”。通过
kubectl get chaos查看实验状态是否为paused或已被删除。有时可能是实验暂停而非删除,故障自然持续。
2.2 第一步:基于 Chaos Mesh 控制面的排查
排查应从 Chaos Mesh 本身开始,这是最直接的路径。
检查 Chaos Mesh 控制器状态:
kubectl get pods -n chaos-mesh确保所有组件,特别是
chaos-controller-manager和chaos-daemon都处于Running状态。如果有CrashLoopBackOff或Error状态的 Pod,需要先查看其日志。kubectl logs -f -n chaos-mesh deployment/chaos-controller-manager --tail=100重点关注日志中是否有权限错误、连接 API Server 失败、或者处理具体实验时的报错(如
failed to apply chaos)。审查实验资源对象: 即使你在 Dashboard 上点击了停止,也要通过命令行确认实验 CRD 对象的状态。
# 查看所有混沌实验 kubectl get chaos -A # 查看某个特定实验的详细定义和状态 kubectl get networkchaos <experiment-name> -n <namespace> -o yaml在输出的 YAML 中,你需要特别关注几个字段:
spec.paused: 是否为true。如果为true,则实验是暂停状态,故障会持续。metadata.deletionTimestamp: 如果这个字段存在,说明资源正在删除中,但可能遇到了阻塞(Finalizer)。status.experiment.phase: 理想状态应为Finished或空。如果是Running但实验理应结束,则有问题。status.conditions: 查看最新的条件信息,可能提示错误原因。
检查 Finalizer(关键步骤): Kubernetes 资源的 Finalizer 是导致资源无法被正常删除、进而阻止控制器执行清理操作的常见原因。查看实验对象的 Finalizer 字段:
kubectl get networkchaos <experiment-name> -n <namespace> -o jsonpath='{.metadata.finalizers}'如果输出类似
["finalizers.chaos-mesh.org"]且资源卡在删除中,这通常是 Chaos Mesh 控制器未能成功执行清理逻辑所致。控制器需要先完成清理(如恢复 iptables 规则),才能移除 Finalizer。如果控制器异常,这个步骤就会挂起。
2.3 第二步:深入目标工作负载与节点
如果控制面看起来正常,那么问题可能出在故障实际注入的目标上。
检查目标 Pod 及其所在节点:
kubectl describe pod <target-pod> -n <namespace>查看 Events 部分,是否有与 chaos-daemon 交互相关的错误。同时,检查 Pod 是否运行在正常的节点上,节点是否
Ready。登录节点进行现场取证(针对网络故障): 对于
NetworkChaos,其本质是通过在目标 Pod 的 Network Namespace 中操作 iptables、tc 等工具来实现的。如果自动恢复失败,这些规则可能残留。- 首先,找到目标 Pod 所在的节点。
- 登录该节点,并找到目标 Pod 的容器 PID 和其网络命名空间。
# 在节点上执行 crictl ps | grep <target-pod-name> # 获取容器ID后,取得其PID crictl inspect <container-id> | grep pid # 进入该容器的网络命名空间 nsenter -n -t <pid> # 现在,你就在Pod的网络空间里了。检查iptables规则和tc qdisc。 iptables-save | grep -i chaos tc qdisc show如果你看到了非预期的、带有
chaos标签的 iptables 规则或 tc 队列,这就是铁证。例如,一个残留的tc filter规则可能导致网络延迟持续。检查 Chaos Daemon 的 Sidecar 注入(针对 IO/Stress 故障): 对于
IOChaos或StressChaos,Chaos Mesh 可能会向目标 Pod 注入一个 sidecar 容器(chaosfs或stress-ng)。实验结束后,这个 sidecar 应该被清理。检查目标 Pod 的容器列表:kubectl get pod <target-pod> -n <namespace> -o jsonpath='{.spec.containers[*].name}'如果列表中仍然包含
chaosfs等容器,说明 sidecar 未被成功移除。
3. 根因分析与分类应对策略
通过上述排查,我们通常能将问题定位到几个具体的场景。下面我将其归纳为几类根因,并给出相应的解决思路。
3.1 控制器异常或资源处理阻塞
这是最可能的原因之一。
- 表现:实验 CRD 对象卡在删除中(有
deletionTimestamp),Finalizer 无法移除。Chaos Controller Manager 日志中有重复的错误信息。 - 根因:
- 控制器 Pod 异常:控制器可能因为内存不足(OOM)、与 Kubernetes API 服务器通信问题而重启或失效。
- 权限问题:Chaos Mesh 的 ServiceAccount 权限(RBAC)不足,无法完成某些清理操作。
- 依赖资源缺失:例如,清理网络规则时需要操作某个已不存在的网络设备。
- 应对策略:
- 首先尝试恢复控制器:重启
chaos-controller-manager的 Pod。kubectl delete pod -n chaos-mesh -l app.kubernetes.io/component=controller-manager - 如果重启后问题依旧,需要检查 RBAC。确保 Chaos Mesh 安装时使用的 ClusterRole 拥有对目标资源(如 pods, networkpolicies)的
update和patch权限。 - 对于卡死的实验对象,可以尝试强制移除 Finalizer(这是一个有风险的操作,需谨慎,并确保后续手动清理):
执行此操作前,务必记录下残留的故障规则(如上述节点取证步骤),因为控制器将不再负责清理,需要你手动处理。kubectl patch networkchaos/<experiment-name> -n <namespace> --type json --patch='[{"op": "remove", "path": "/metadata/finalizers"}]'
- 首先尝试恢复控制器:重启
3.2 节点级故障规则残留
这在网络混沌实验中尤为常见。
- 表现:实验对象已删除,但目标 Pod 网络依然异常。通过
nsenter在 Pod 网络命名空间内查看到残留的 iptables 规则或 tc 配置。 - 根因:
chaos-daemon在节点上执行故障注入和恢复。如果 daemon 进程在处理恢复时崩溃、被杀死,或者与控制器通信中断,就可能留下“孤儿”规则。 - 应对策略:手动清理节点规则。这是本次分享的核心实操部分。
- 对于 iptables 规则:在 Pod 的网络命名空间内,使用
iptables -D <chain> <rule-specification>逐条删除标识为 chaos 的规则。更安全的方法是,先备份再清空特定链。# 在Pod网络命名空间内执行 iptables-save | grep -v chaos > /tmp/iptables.backup iptables-restore < /tmp/iptables.backup # 或者,更精准地删除CHAOS链(如果存在) iptables -F CHAOS_INPUT # 清空链 iptables -X CHAOS_INPUT # 删除链 - 对于 tc (Traffic Control) 规则:使用
tc qdisc del命令删除之前添加的队列规则。# 查看网卡上的qdisc,通常设备是eth0 tc qdisc show dev eth0 # 删除根qdisc(会恢复为默认的pfifo_fast),请根据实际情况替换`handle` tc qdisc del dev eth0 root
- 对于 iptables 规则:在 Pod 的网络命名空间内,使用
3.3 Sidecar 容器残留
- 表现:目标 Pod 的容器数量多于预期,包含
chaosfs等容器。Pod 可能无法正常终止或重建。 - 根因:负责注入和清理 sidecar 的 Kubernetes Mutating Webhook(由 Chaos Mesh 提供)可能未正常工作,或者 Pod 的更新请求被拒绝。
- 应对策略:
- 最直接的方法是重建目标 Pod。
Kubernetes 控制器(如 Deployment)会自动创建一个新的、干净的 Pod。kubectl delete pod <target-pod> -n <namespace> - 如果 Pod 非常重要不能删除,可以尝试手动编辑 Pod 配置,移除 sidecar 容器定义。但注意,直接
kubectl edit一个由 Deployment 管理的 Pod 是无效的,需要修改对应的 Deployment 模板,并让 Pod 滚动更新。
在kubectl edit deployment <deployment-name> -n <namespace>spec.template.spec.containers中,移除与 chaos 相关的容器定义,保存后触发滚动更新。
- 最直接的方法是重建目标 Pod。
3.4 资源竞争或状态不一致
- 表现:多个混沌实验同时作用于同一目标,或者一个实验的恢复过程与另一个实验的注入过程产生竞争。
- 根因:Chaos Mesh 控制器是并发处理事件的,在极端情况下可能产生状态竞争。
- 应对策略:
- 暂停所有针对同一目标的混沌实验。
- 按顺序逐个删除实验,并观察恢复情况。
- 在设计混沌实验时,尽量避免对同一资源对象的复杂、交叉干扰。
4. 标准化清理操作流程与实操脚本
基于以上分析,我总结了一套标准化的清理流程,并附上可操作的脚本片段。请务必按照顺序进行,并在操作前做好备份。
4.1 流程总览
- 确认与记录:确认故障现象,记录受影响的命名空间、Pod、实验名称。
- 检查控制面:检查 Chaos Mesh 组件状态和实验对象状态。
- 尝试优雅恢复:重启控制器,等待其自动恢复。
- 强制移除阻塞:若优雅恢复失败,备份后强制移除实验对象的 Finalizer。
- 手动清理节点:登录节点,清理残留的网络或内核规则。
- 清理工作负载:重建 Pod 或更新工作负载以移除残留 sidecar。
- 验证与复盘:验证业务恢复,并复盘根本原因。
4.2 实操脚本:节点网络规则清理
假设我们已经定位到node-1上的 Podapp-1-abcde存在残留网络规则。
#!/bin/bash # 清理节点上指定Pod的残留Chaos Mesh网络规则 # 使用方法:./cleanup_chaos_residue.sh <node-name> <pod-name> <namespace> NODE=$1 POD=$2 NAMESPACE=$3 if [ -z "$NODE" ] || [ -z "$POD" ] || [ -z "$NAMESPACE" ]; then echo "Usage: $0 <node-name> <pod-name> <namespace>" exit 1 fi echo "=== 开始清理 $NAMESPACE/$POD 在节点 $NODE 上的残留规则 ===" # 1. 获取Pod在节点上的容器ID和PID(通过kubectl debug node或假设你有节点ssh权限) # 这里以通过`crictl`在节点上执行为例。你需要先ssh到目标节点,或者使用kubectl debug node。 # 以下命令需要在目标节点上运行。 # 通过crictl找到容器(注意:节点上需安装crictl) CONTAINER_ID=$(crictl ps --name "$POD" -o json | jq -r '.containers[0].id') if [ -z "$CONTAINER_ID" ] || [ "$CONTAINER_ID" == "null" ]; then echo "错误:未在节点 $NODE 上找到Pod $POD 的容器。" exit 1 fi PID=$(crictl inspect "$CONTAINER_ID" | jq -r '.info.pid') if [ -z "$PID" ] || [ "$PID" == "null" ]; then echo "错误:无法获取容器 $CONTAINER_ID 的PID。" exit 1 fi echo "找到容器PID: $PID" # 2. 进入网络命名空间清理iptables # 创建一个临时脚本来在Pod网络空间内执行 CLEANUP_SCRIPT=$(mktemp) cat > "$CLEANUP_SCRIPT" << 'EOF' #!/bin/sh echo "正在Pod网络命名空间内清理iptables规则..." # 备份当前规则 iptables-save > /tmp/iptables_backup_$(date +%s).rules # 删除所有包含chaos关键词的规则(谨慎!请先确认grep结果) iptables-save | grep -i chaos | while read line; do # 这里简化处理,实际生产环境应更精确地解析和删除每条规则 echo "发现规则: $line" # 示例:对于 -A CHAOS_INPUT 这样的规则,需要更复杂的解析来删除 # 更安全的方式是清空自定义的CHAOS链(如果存在) done # 尝试清空和删除常见的Chaos Mesh链 for chain in CHAOS_INPUT CHAOS_OUTPUT CHAOS_FORWARD; do if iptables -L "$chain" >/dev/null 2>&1; then echo "清空并删除链: $chain" iptables -F "$chain" iptables -X "$chain" fi done echo "iptables清理完成。" echo "正在检查并清理tc qdisc..." # 清理tc规则,通常针对eth0,但可能是其他接口 DEVICE="eth0" if tc qdisc show dev "$DEVICE" | grep -q "chaos"; then echo "发现残留的tc qdisc,正在清理..." # 删除根qdisc,这会恢复默认设置。请确保这是你想要的操作。 tc qdisc del dev "$DEVICE" root 2>/dev/null || echo "删除根qdisc失败,可能已不存在。" fi echo "tc清理完成。" EOF chmod +x "$CLEANUP_SCRIPT" # 3. 在Pod的网络命名空间中执行清理脚本 nsenter -n -t "$PID" -- "$CLEANUP_SCRIPT" # 4. 清理临时文件 rm -f "$CLEANUP_SCRIPT" echo "=== 清理流程执行完毕 ===" echo "**请务必手动验证Pod的网络连接是否恢复。**"重要提示:此脚本为示例模板,直接在生产环境运行前,必须在测试环境验证,并根据你的实际集群环境(容器运行时、网络插件)进行调整。尤其是
tc qdisc del命令,操作不当可能导致网络中断。
4.3 集群级批量检查与清理
对于大规模集群,你可能需要一种批量检查 Chaos 实验残留的方法。
#!/bin/bash # 检查所有命名空间中状态异常的Chaos实验 for chaos_type in networkchaos podchaos stresschaos iochaos kernelchaos; do echo "检查 $chaos_type..." # 查找所有非Finished、非空状态的实验 kubectl get $chaos_type -A -o jsonpath='{range .items[?(@.status.experiment.phase!="Finished" && @.status.experiment.phase)]}{.metadata.namespace}/{.metadata.name}: {.status.experiment.phase}{"\n"}{end}' done # 查找所有带有删除时间戳但未消失的实验(卡在Finalizer) kubectl get chaos -A -o json | jq -r '.items[] | select(.metadata.deletionTimestamp!=null) | "\(.metadata.namespace)/\(.metadata.name) (\(.metadata.deletionTimestamp))"'5. 预防措施与最佳实践
排查和清理是“亡羊补牢”,更重要的是“防患于未然”。根据我的踩坑经验,遵循以下实践能极大降低自动恢复失败的概率:
升级到稳定版本:始终使用 Chaos Mesh 的稳定版本,并关注其 Release Notes 中关于 Bug 修复的部分。社区活跃,很多已知的恢复问题在新版本中已修复。
精细化 RBAC 权限:为 Chaos Mesh 的 ServiceAccount 配置足够但不过度的权限。确保其能够对目标资源(Pod、NetworkPolicy等)进行
create、patch、update、delete操作。权限不足是控制器操作失败的常见原因。设置合理的实验范围与时长:
- 避免“核弹”实验:不要一开始就对核心生产服务进行破坏性极强的实验。从小范围、非关键服务开始。
- 使用
duration和terminationGracePeriodSeconds:为实验设置明确的持续时间,并为 Pod 故障实验设置合理的优雅终止周期,给恢复留出时间窗口。 - 考虑使用
paused属性:先创建paused: true的实验,检查注入目标是否正确,然后再启用。
加强监控与告警:
- 监控 Chaos Mesh 控制器:将
chaos-controller-manager和chaos-daemon的 Pod 状态、日志错误率纳入监控。 - 监控实验状态:对 Chaos 实验 CRD 的状态字段(
status.experiment.phase)进行监控,如果Running状态超过预期时长或出现Failed状态,立即告警。 - 监控目标应用:在注入混沌故障时,同步加强对目标应用关键指标(延迟、错误率、吞吐量)的监控,确保能第一时间感知到“未恢复”的异常。
- 监控 Chaos Mesh 控制器:将
制定并演练应急预案:将本文所述的排查和清理步骤,固化为团队的应急预案。定期进行混沌工程演练,不仅要演练故障注入,也要演练故障恢复(包括自动恢复失败的手动干预)。
在非生产环境充分测试:任何新的混沌实验类型或复杂场景,先在开发或测试集群进行充分验证,观察其注入和恢复的全过程是否平滑,确认无误后再应用于生产环境。
混沌工程的目的不是制造混乱,而是通过受控的实验来建立对系统韧性的信心。当自动恢复失效时,冷静、系统化的排查能力,正是这种信心的最后一道坚实防线。掌握这些手动干预的技能,并不意味着自动化的失败,而是意味着你对系统的理解更深了一层,对生产环境的掌控力更强了一分。每一次成功的“救火”,都是对系统行为和运维流程的一次宝贵复盘,其价值不亚于一次成功的混沌实验本身。