登到192.168.1.5那台机器,看看今天白天,大约从早晨到晚上,都卡住了。看看因为什么卡住,为什么卡住而没有杀死那个导致卡死的进程,为什么
证据已经拿到,不需要再跑更多命令了。让我给你一个完整的诊断结论。
诊断结论:192.168.1.5 (fb5, FreeBSD 14.3) 白天卡死原因
一句话结论
不是某个进程把系统卡死了,是系统自己把自己拖死的------物理内存 8GB + swap 10GB 全部耗尽,内核陷入页面回收死循环,连"杀进程"这个动作本身都执行不了。
关键时间线(铁证)
| 时间 | 事件 |
|---|---|
| 00:29 ~ 08:26 | act_runner 正常接 CI 任务,task 618 → 638 连续派发 |
| 08:37 | 最后一条正常内核日志:pid 17780 (node) exited on signal 6 (core dumped) |
| 08:37 ~ 20:37 | 整整 12 小时,syslog/daemon/debug 全部静默------系统已活锁 |
| 20:37 | 你硬重启了机器,内核 boot 信息开始刷出 |
dmesg.today 最后的内核呻吟(这就是死亡现场)
swap_pager: out of swap space
swp_pager_getswapspace(17): failed
swap_pager: out of swap space
swp_pager_getswapspace(15): failed
swp_pager_getswapspace(11): failed
swp_pager_getswapspace(20): failed
swp_pager_getswapspace(6): failed
swp_pager_getswapspace(26): failed
pid 15401 (产物), uid 806: exited on signal 11 (core dumped) ← 段错误
swap_pager: out of swap space
swp_pager_getswapspace(1): failed
swap_pager: out of swap space
swp_pager_getswapspace(30): failed
pid 6860 (racct-hoster-statsd), uid 0, was killed: failed to reclaim memory ← OOM 杀了 1 个
pid 1746 (python3.11), uid 1004, was killed: failed to reclaim memory ← OOM 又杀了 1 个
swap 10GB 全满,物理内存 8GB 也早就没了。
硬件底子(这才是根本矛盾)
- CPU:Intel Celeron J1900 @ 2.0GHz,4 核(Bay Trail 低功耗,性能约等于手机中端芯片)
- 内存:8GB
- 上面跑了什么:
- Gitea + act_runner(CI/CD,不断接 task)
- MongoDB
- devpi(PyPI 镜像)
- nPulse/BVCP VPS 面板 + bhyve 虚拟机 ubuntu1(给 VM 分了 ~3.4GB)
- Clonos VNC 代理、Webmin、多个 Jail
- act_runner 子进程:node 跑 tsc 编译(单吃 1.6GB)+ 8 个 light.py 冒烟测试并发
4 核 CPU 同时扛着 Gitea CI、Mongo、虚拟机、各种服务,CPU 一慢,任务排队,内存就堆上去了,雪崩。
为什么卡死了却没杀掉肇事进程?
这是你问的核心。答案有三层:
1. 杀进程需要调度,调度需要 CPU,CPU 全在内核态转
当 swap_pager 连续返回失败时,FreeBSD 的 page daemon(pagedaemon 内核线程)会疯狂地扫描整个物理内存、尝试压缩 ZFS ARC、尝试换出匿名页。所有 4 个 CPU 都陷在内核态做这件事。用户态的 kill 命令根本得不到时间片执行------你 SSH 上去都敲不动,不是因为 SSH 死了,是 shell 进程排不上 CPU。
2. 杀进程这个动作本身也要分配内存,而内存是零
FreeBSD 的 kill() 系统调用要分配 signal 队列、要唤醒目标进程的 wait channel、可能还要写 core dump。每一步都要 malloc,而 malloc 在内存为零时直接失败或阻塞。想杀进程释放内存,但杀进程这个动作本身就要先有内存------死循环。
3. FreeBSD 没有 Linux 那样的激进 OOM killer + NMI watchdog
- Linux 有
oom_kill_allocating_task,内存分配失败时直接杀掉当前进程,而且 NMI watchdog 能在系统完全卡死时强制 panic 或触发 cycle detection。 - FreeBSD 的 OOM 处理是被动式的:只有当 swap_pager 失败时才偶尔杀一两个进程(从日志看只杀了 2 个:racct-hoster-statsd 和 python3.11),杀不赢不断涌进来的新 CI 任务。
- 也没有配置
kdump/ 没有 crash dump(/var/crash是空的),所以连事后分析的现场都没留下。
历史规律(不是第一次了)
日志里反复出现同样的模式:
- Sep 11:一口气 OOM 杀了 13 个进程,包括 gitea 本身
- Sep 17:又杀了 6 个 node 进程
- Oct 24 23:53:昨晚刚杀过一次(racct-hoster-statsd + python3.11)
- Oct 25 白天:彻底扛不住了,直接死锁
建议的止血措施
- 给 act_runner 限并发 :在 act_runner config.yaml 里把
capacity降到 1~2,别让 CI 任务同时跑十几个 - 限制 ZFS ARC 上限 :
vfs.zfs.arc_max="512M",防止 ARC 吃光 8GB 里的大头 - 给 bhyve 虚拟机减内存:ubuntu1 现在分了 3.4GB,太大了
- 加 swap 或换大内存:8GB 对这台机器的负载来说太小
- 开 watchdog :
kern.watchdog或硬件 watchdog,下次卡死能自动重启而不是等人去按电源
要不要我现在就上去把 ARC 限制和 act_runner 并发先改了?