VPS 变慢与自救实录 —— 从内存爆满到「服务开关」

今天 VPS 突然变慢,Project Dashboard 和 DSH 都打不开。排查下来,是一次典型的「小内存 VPS 被塞爆」事件。这篇文章完整记录从发现问题、定位根因、应急恢复,到最终做出一套「服务开关」的过程。

一、现象:VPS 全线变慢

最先的征兆很简单:

  • 打开 VPS Monitor,页面要等 9-12 秒才出数据
  • Project Dashboard(3004)打不开
  • DSH(3081)打不开

一开始以为是某个服务挂了,结果排查下来,问题比想象的大 —— 整台 VPS 在 swap。

二、定位:三条命令看清全貌

2.1 系统负载和内存

uptime
free -h

输出:

load average: 0.81, 1.66, 1.35
Mem:   5.8Gi total, 4.3Gi used, 260Mi free
Swap:  2.0Gi total, 1.0Gi used

关键信号:物理内存几乎满了(free 只剩 260MB),swap 用了一半。系统开始把内存换到磁盘,一切变慢。

2.2 谁在吃内存

ps aux --sort=-rss | head -15

排行:

进程内存
openclaw gateway1156 MB
k3s server620 MB
mysqld351 MB
dsh209 MB
n8n194 MB
… 还有 4 个 mysqld各 150-180 MB

根因:5.8GB 内存的 VPS 上,跑着 K8s + 5 个 MySQL + openclaw + n8n + DSH + 一堆容器 —— 严重超载。

三、应急:停掉 k3s,省下 1.2GB

k3s(轻量 Kubernetes)里跑着两套 WordPress + dashboard + traefik,但它其实没在用。决定临时停掉。

# 停 k3s(临时,可恢复)
systemctl stop k3s

# 清理残留的 containerd-shim
pkill -f "rancher/k3s/data"

# 清理 k3s 残留的 mysqld(cgroup 是 kubepods 的)
for pid in $(ps aux | grep "[m]ysqld" | awk '{print $2}'); do
  cg=$(cat /proc/$pid/cgroup | head -1)
  echo "$cg" | grep -q kubepods && kill $pid
done

效果:

指标停之前停之后
used4.3 Gi3.4 Gi
free260 Mi1.2 Gi
swap1.0 Gi173 Mi

共释放约 1.2GB。确认不用后,彻底禁用自启:

systemctl disable k3s

四、第二个坑:Nginx 悄悄挂了

内存缓解后,Project Dashboard 还是打不开。查下去发现:

systemctl status nginx
# × nginx.service - failed
# nginx: [emerg] bind() to 100.99.4.120:3081 failed (99: Unknown error)

根因:VPS 之前重启时,Nginx 启动比某个占 3081 端口的服务(DSH)晚,bind() 失败 → Nginx 启动失败,之后再没起来。

于是所有经过 Nginx 的入口(3004 Project Dashboard、3081 DSH)全打不开 —— 但后端服务其实都是好的。

修复:

systemctl start nginx

教训:后端 200 ≠ 能访问。Nginx 挂了,所有反代入口全断。

五、长期方案:VPS Monitor 加「服务开关」

每次手动 SSH 停服务太麻烦。既然我有 VPS Monitor,就给它加一个「服务开关」卡片:

  • 自动发现可控服务(Docker / systemd / PM2)
  • 一键启停(带二次确认)
  • 显示内存占用(停之前知道能省多少)
  • 高危保护(critical 服务禁止停,如数据库、网关)

5.1 白名单配置

config/services-control.json:

{
  "services": [
    { "id": "k3s",           "type": "systemd", "risk": "medium",   "forbidStop": false },
    { "id": "n8n",           "type": "docker",  "risk": "medium",   "forbidStop": false },
    { "id": "excalidraw",    "type": "docker",  "risk": "low",      "forbidStop": false },
    { "id": "mysql-project", "type": "docker",  "risk": "critical", "forbidStop": true  },
    { "id": "one-api",       "type": "docker",  "risk": "critical", "forbidStop": true  },
    { "id": "vps-monitor",   "type": "pm2",     "risk": "critical", "forbidStop": true  }
  ]
}

设计原则:

  • 白名单制 —— 只允许操作明确列出的服务,防止误杀
  • 风险分级 —— low 直接停,medium 二次确认,critical 禁止停
  • 禁止停自己 —— vps-monitor 不能停(会自杀)

5.2 API + 前端

API /api/service-control:GET 返回服务状态 + 内存,POST 执行启停。前端组件每 15 秒刷新,显示:

🟢 n8n              [docker]  running · 271 MB   [中] [■停止]
🟢 excalidraw       [docker]  running · 5 MB     [低] [■停止]
⚪ k3s              [systemd] inactive           [中] [▶启动]
🔒 mysql-project    [docker]  running · 272 MB   保护
🔒 one-api          [docker]  running · 88 MB    保护

实际操作(从日志看)也生效了:

[2026-10-01T07:52:55Z] stop docker:excalidraw
[2026-10-01T07:52:59Z] stop docker:deploy-arcreel-1
[2026-10-01T07:53:06Z] stop docker:wp-app-practice

六、教训总结

小内存 VPS 不要堆太多服务
5.8GB 跑 K8s + 5×MySQL + 一堆 Node 服务 = 必然 swap。要么升级内存,要么砍服务。

swap 是性能杀手
一旦数据库(MySQL)被换到 swap,所有查询都会变慢。

Nginx 挂了你可能不知道
后端服务全好,但反代入口全断。加 Restart=on-failure 防下次重启又挂:

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
Restart=on-failure
RestartSec=5s

顺手把「运维」变成「工具」
这次手动停 k3s,直接催生了 VPS Monitor 的「服务开关」——以后停服务点一下就行,还能看到省多少内存。

监控的核心是动态识别,不是硬编码
服务开关也是白名单 + 自动发现,新加服务不用改代码。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注