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 gateway | 1156 MB |
| k3s server | 620 MB |
| mysqld | 351 MB |
| dsh | 209 MB |
| n8n | 194 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
效果:
| 指标 | 停之前 | 停之后 |
|---|---|---|
| used | 4.3 Gi | 3.4 Gi |
| free | 260 Mi | 1.2 Gi |
| swap | 1.0 Gi | 173 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 的「服务开关」——以后停服务点一下就行,还能看到省多少内存。
监控的核心是动态识别,不是硬编码
服务开关也是白名单 + 自动发现,新加服务不用改代码。
