一次由Jenkins流水线中不当Docker清理引发的服务器超时事件复盘
1. 事件背景
jenkins-worker-01
是我司核心的CI/CD构建节点,负责执行多个项目的Jenkins Pipeline任务。为了保持节点环境整洁,多数流水线在构建结束后都包含一个标准的“清理工作区”阶段,该阶段会自动删除本次构建产生的悬空Docker镜像(dangling images)。
某日下午,开发团队在一个长期未成功构建的项目 project-zeus
上触发了一次新的构建。流水线在执行到最后的清理阶段时,Jenkins的Web界面显示该阶段长时间处于“运行中”状态,无任何日志输出。
该清理阶段的核心逻辑如下 (摘自 Jenkinsfile
):
1 | stage('Cleanup Workspace') { |
由于该项目及其他项目在 jenkins-worker-01
节点上累积了数月的构建失败产物,本次清理任务需要处理的悬空镜像数量预估超过了500个。
2. 故障现象
-
Jenkins任务卡死:
project-zeus
流水线的清理阶段长时间运行不结束。 -
节点响应迟钝:尝试通过SSH登录
jenkins-worker-01
节点排查问题,发现终端响应极其缓慢。 -
核心服务无响应:在节点上执行任何
systemctl
命令(如systemctl status jenkins-agent
),均返回超时错误:1
Failed to activate service 'org.freedesktop.systemd1': timed out
-
监控系统告警:监控平台显示该节点的 磁盘I/O Wait 指标(%wa)一度超过90%,多个关键进程处于
D
状态(不可中断睡眠)。
3. 排查与处理过程
3.1 初步诊断
通过 top
和 iotop
命令艰难地观察到,dockerd
进程正在进行疯狂的磁盘读写,I/O负载极高。确认问题是由于Docker的大规模删除操作引发了I/O性能瓶颈。
3.2 尝试优雅重启
判断系统已无法通过常规手段恢复,首先尝试了标准的 reboot
命令。
- 现象:终端返回了关机广播信息后,SSH连接挂起,服务器无任何后续响应。
- 结果:10分钟后,节点依然存活(可ping通),但Jenkins Agent已断开,所有服务均不可用。
reboot
的优雅关机流程被阻塞。
3.3 实施强制重启
确定 systemd
自身已被I/O问题“冻结”,无法完成关机任务。作为最终手段:
- 通过云服务商的VNC控制台登录服务器。
- 执行强制重启命令
reboot -f
。 - 系统立即重启,并在启动后执行了文件系统检查(
fsck
)。随后,节点恢复正常,并重新与Jenkins主节点建立连接。
4. 根本原因分析 (RCA)
本次故障的根源在于自动化脚本中一个看似无害却极其危险的设计。
4.1 直接原因
systemctl
命令超时是因为它无法与 systemd
(PID 1) 进程正常通信。systemd
因等待磁盘I/O完成而被内核挂起,无法响应任何D-Bus请求。
4.2 根本原因
Jenkins流水线中的 replaceAll('\n', ' ')
代码,将所有悬空镜像的ID拼接成一个巨大的、由空格分隔的字符串。最终执行的 docker rmi ${danglingImages}
命令等同于在shell中执行 docker rmi ID1 ID2 ID3 ... ID500+
。这个单次命令要求Docker守护进程在短时间内完成数百个镜像的删除操作,瞬间产生的海量I/O请求压垮了服务器的磁盘子系统。
4.3 连锁反应详解
- 批量指令:Jenkins脚本构造并执行了一个超长的
docker rmi
命令。 - I/O风暴:
dockerd
接收指令后,开始并行或快速串行地删除大量镜像层,引发I/O风暴。 systemd
阻塞:systemd
自身的常规I/O操作(如写日志)被阻塞在拥堵的队列中,导致进程进入D
状态。- 优雅重启失败:
reboot
命令依赖systemd
来协调关机,而已被阻塞的systemd
无法执行此任务,导致关机流程死锁。 - 强制重启成功:
reboot -f
命令绕过了systemd
,直接向内核发送重启指令,因此能够成功执行。
5. 解决方案与改进措施
为了根除此类隐患,必须从优化自动化脚本和规范流程入手。
5.1 短期措施:修正存在问题的Pipeline脚本
立即审计所有 Jenkinsfile
,将上述危险的清理逻辑替换为安全、可控的迭代式清理。
【修正后的安全清理阶段 (Groovy)】
1 | stage('Safe Cleanup Workspace') { |
核心改进:通过将ID列表分割成数组,并使用 for
循环和 sleep
,将集中的I/O压力有效分散,从根本上避免了I/O风暴。
5.2 长期建议:架构与流程优化
- 代码审查流程:将 CI/CD 流水线脚本 (
Jenkinsfile
等) 纳入代码审查 (Code Review) 范围,重点关注其中与系统资源交互(如文件、网络、进程操作)相关的部分。 - 节点存储升级:评估并推动将核心构建节点的Docker工作目录迁移至SSD,以大幅提升I/O承载能力。
- 专用清理任务:创建一个独立的、专用的Jenkins维护任务,使用上述安全脚本,在所有节点的业务低峰期(如每日凌晨)统一执行清理,而不是在每个业务流水线后执行。
6. 总结
本次事件是一次深刻的教训,它表明自动化脚本中的“效率陷阱” 同样致命。一个为了便捷而设计的“一句话命令”,在特定条件下会成为导致系统崩溃的元凶。所有运维和开发人员必须认识到,自动化工具在放大效率的同时,也在放大错误的破坏力。对底层系统(尤其是I/O)的敬畏之心,是构建稳定可靠自动化体系的基石。