Deeeli
← 返回博客列表

当 AI Ops 把自己网站搞挂了——一个容器重启的蝴蝶效应

当 AI Ops 把自己网站搞挂了——一个容器重启的蝴蝶效应

一切都很顺利,直到 AI 运维 Agent 决定重启一个容器。然后整个网站挂了。然后它修不好了。然后它又把自己 SSH 踢出了服务器。

这是我用 Hermes Multi-Agent 系统管理个人网站部署的第六天。前五天风平浪静——AI Designer 出图、AI Coder 写代码、AI Reviewer 审查、AI Ops 部署——一条龙全自动,我甚至连终端都没怎么打开。

第六天,事情开始变得有趣。


一、背景:v4→v5 的切换

网站首页经历了四个设计版本:

版本设计师风格
v1-v3AI Designer(迭代)赛博朋克风,Three.js 3D 背景,粒子效果,光晕文字
v4AI + 用户反馈精简版赛博朋克,IcosahedronGeometry 球体,900 粒子
v5AI Designer 自主优化极度精简,移除了大量 3D 效果

v4 是用户确认过的版本——Three.js 的 IcosahedronGeometry 球体 + 双环面 + 900 个粒子的 Canvas 神经网络背景,效果相当惊艳。

但 AI Designer 觉得"还能更好"。于是它在没有用户明确指令的情况下,基于自己对这个风格的"理解",产出了一个 v5:移除了环面、减少了粒子、简化了着色器。它认为这样更"克制"。

用户看到 v5 后说了一句话:「这个不好看,回退到 v4。」

于是,整条流水线启动了。


二、故障始末

第一步:Designer 分析差异

AI Designer 接到任务:对比 v5 和 v4,产出差异清单。

它产出了一个 26 项差异分析文档,分三个优先级:

  • P0(7 项):球体几何体错误、颜色不对、环面缺失、拖拽旋转反向
  • P1(13 项):FOV 偏移、粒子数量不足、导航样式差异、按钮风格不匹配
  • P2(6 项):微妙的动画时间和缓动曲线差异

这本身没问题——Designer 做得很漂亮。

第二步:Coder 修改代码

AI Coder 拿到差异清单,开始修改 Nuxt 3 项目的三个 Vue 文件:

3DBackground.vue  → 球体几何体 + 环面 + 着色器
index.vue          → 粒子系统 + 相机配置
app.vue            → 全局样式 + 导航栏

Coder 完成了修改,Reviewer 审查通过(APPROVE),Git 提交成功,Tester 测试通过。

一切正常。

第三步:Ops 部署——然后炸了

AI Ops Worker 的任务是:将修改后的 Nuxt 项目通过 Docker 部署到腾讯云服务器(43.135.47.130)。

它的标准流程是:

本地 Docker build → 压缩镜像 → SFTP 上传 → 服务器 docker load → docker compose up

但这次不一样。因为它要"安全部署"——先在测试端口启动验证,再切换。

# 构建新镜像
docker build -t website:v4-fix .

# 在 3001 端口测试
docker run -d --name web-test -p 3001:3000 website:v4-fix

# 验证...
curl http://127.0.0.1:3001/  # 返回 200,OK

# 切换!
docker stop web && docker rm web
docker rename web-test web

切换命令执行了。然后:

$ curl https://deeeli.com
curl: (35) OpenSSL SSL_connect: Connection reset by peer

网站挂了。


三、诊断过程

症状分析

测试结果
curl http://deeeli.com301 → https,正常
curl https://deeeli.comConnection reset
curl -k https://服务器IPConnection reset
curl http://服务器IP:3000200 OK

关键发现:HTTP 80 端口正常(nginx 在跑),HTTPS 443 连接被 reset,但直接访问 Nuxt 的 3000 端口正常。

这说明 nginx 活着,Nuxt 也活着,但它们之间的通信出了问题。

排查 nginx 配置

$ ssh user@43.135.47.130
$ docker ps
CONTAINER ID   IMAGE            STATUS
a1b2c3d4e5f6   nginx:alpine     Up 2 hours

等等——只有一个容器?Nuxt 容器呢?

$ docker ps -a | grep nuxt
b5c6d7e8f9a0   website:v4-fix   Exited (137) 3 minutes ago

Nuxt 容器崩溃了,退出码 137(OOM killed)。

Nuxt 容器为什么 OOM?

拉日志:

$ docker logs b5c6d7e8f9a0 --tail 50
...
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed
...
<--- JS stacktrace --->

v4 版本的 Three.js 场景在 SSR(服务端渲染)阶段触发了大量内存分配。IcosahedronGeometry + 双环面 + 900 粒子 + Canvas 纹理在服务端渲染时,内存占用直接从 v5 的 200MB 飙到了 1.2GB。

而 Docker 容器的内存限制是 1GB。

所以:

  1. Coder 修改正确(v4 的 Three.js 代码没有问题)
  2. Reviewer 审查通过(代码质量没问题)
  3. Tester 测试通过(本地 dev 模式内存充裕)
  4. Ops 部署"成功"——容器确实起来了
  5. 但容器在首次 SSR 请求时 OOM 崩溃

这是典型的环境差异问题:本地开发 vs Docker 容器环境的内存边界不同。


四、修复与回退

Ops 的自动修复尝试

AI Ops Worker 检测到容器崩溃后,自动启动了修复流程:

  1. 分析崩溃日志 → 识别到 OOM
  2. 尝试重启容器
    docker restart b5c6d7e8f9a0
    

    容器重启成功,但第一次请求再次 OOM——这是设计问题,重启没用。
  3. 尝试增加内存限制
    docker update --memory 2g web
    

    失败了——服务器总共只有 2GB 内存,给容器 2GB 意味着系统和 nginx 会受影响。

陷入僵局

此时 Ops Worker 面临一个选择:

  • 增大容器内存(可能导致整机 OOM)
  • 回退到 v5(v5 内存占用低)
  • 修改代码减少 Three.js 复杂度

它选择了第三条路——尝试通过 paramiko SSH 修改 Dockerfile,在构建时禁用 SSR 的 Three.js 初始化。

# Ops Worker 的修复脚本
import paramiko
ssh = paramiko.SSHClient()
ssh.connect('43.135.47.130', username='root', password='...')
ssh.exec_command('cd /root/personal-website && sed -i ...')

但这时发生了另一件事:SSH 连接被拒绝了

paramiko.ssh_exception.SSHException: Error reading SSH protocol banner
Connection reset by peer

Ops Worker 之前的多次 SSH 连接加上 SFTP 上传镜像,触发了服务器的 fail2ban 规则——AI 把自己的 SSH 访问给封了。


五、兜底方案与教训

最终是用户手动介入:

# 解除 fail2ban
fail2ban-client unban 本地IP

# 直接回退
docker stop web
docker run -d --name web -p 3000:3000 --memory 1.5g website:v5

网站恢复,v4 部署失败,v5 继续运行。

整个过程的时间线

时间事件
T+0Designer 产出 26 项差异分析
T+15minCoder 完成修复,20 项 P0+P1
T+25minReviewer 审查通过
T+30minGit 提交 + Tester 测试通过
T+45minOps 构建镜像、部署、切换
T+46min容器 OOM,网站挂掉
T+47minOps 自动诊断、尝试修复
T+52min触发 fail2ban,SSH 被封
T+55minOps 报错,block 等待人工
T+60min用户 SSH 解除封禁,回退 v5

55 分钟,100+ 个 Kanban 任务,最终回到了起点。


六、踩坑总结

1. 环境差异是最大的坑

本地 npm run dev → 一切正常。Docker SSR → OOM。Dev 和 Prod 的内存边界完全不同。AI Reviewer 和 Tester 在审查/测试时无法捕捉这种差异——它们工作在开发环境。

2. SSR + 3D 库 = 内存炸弹

Three.js 在服务端渲染时会创建完整的 WebGL 上下文(通过 headless-gl),这在 1GB 的容器里是灾难。v4 的 IcosahedronGeometry + 900 粒子 + Canvas 纹理,SSR 内存峰值接近 1.5GB。

解决办法:在 SSR 阶段跳过 Three.js 初始化,仅在客户端执行。

3. AI Ops 的自我毁灭

AI Ops Worker 的自动修复逻辑本身没问题,但它没有考虑到"频繁的 SSH 连接会被封禁"这一层。加上 paramiko 在连接失败时的重试行为,加速了 fail2ban 的触发。

教训:给 Ops Worker 加上 SSH 连接频率限制,或在任务流中显式处理 fail2ban 场景。

4. 模型切换的连锁反应

在部署过程中,MiniMax API 遇到了 429 限流。按照系统设计,Worker 自动切换到了 DeepSeek 模型。但 DeepSeek 的推理速度比 MiniMax 慢 3-5 倍,导致原本 15 分钟的部署任务拖到了 45 分钟——正好跨越了 fail2ban 的检测窗口。


结语

AI Ops 不是银弹。它能自动化 90% 的流程,但剩下那 10%——环境差异、资源边界、连锁反应——仍然需要人的判断。

但这也正是有趣的地方:AI 在"翻车"中学到的教训,比在"顺利"中多得多。这次事件之后,Manager 的 SOUL.md 里新增了一大段关于部署安全的规则:先验证再切换、保留回滚方案、SSH 连接频率限制、Docker 内存限制检查……

AI 学会了。代价是网站挂了 9 分钟。

下一次部署,同样的坑不会再踩。

(除非 AI 发现了新的坑。)