截至2026年08月,本文基于 Docker 26+/k3s v1.30+ 环境验证,适配 Ubuntu 22.04 LTS 及衍生发行版。
说真的,NemoClaw 在嵌入式设备上跑起来确实香,但只要内存卡在临界点附近,镜像推送这一步就容易「翻车」——轻则推送卡住,重则直接 OOM 崩溃。这一篇就把我自己排查过程中整理出来的方法系统讲一遍,建议收藏。
一、现象:沙箱镜像推一半就崩
执行 nemoclaw onboard 或 openshell sandbox push 时,镜像推送中途卡住或直接报错:
ERROR: failed to push image: signal: killed
Docker OutOfMemoryError: cannot allocate memory
沙箱镜像压缩后约 2.4 GB,解压推送时 Docker daemon、k3s 和 OpenShell Gateway 并行运行,内存需求叠加。在少于 8 GB RAM 的机器上,合计内存占用极易触发系统 OOM Killer。
真实案例:某嵌入式开发者在 NVIDIA Jetson Orin NX(16GB RAM)上部署 NemoClaw 时,引导初期一切顺利,但在镜像推送阶段频繁出现 signal: killed 错误。排查发现 k3s 守护进程默认占用约 4 GB 内存,加上 Docker daemon 和 OpenShell Gateway 的内存需求,峰值内存使用逼近物理上限,触发 OOM Killer 的概率极高。
知识点:Jetson Orin NX 16GB 这个配置看似充裕,但 NemoClaw 全栈跑起来后,16GB 并不算「富裕」。这是很多新手容易踩的坑——只看物理容量,不看运行时实际占用。
二、根因拆解:为什么偏偏在推送时 OOM?
我把可能的原因列成清单,建议对照排查:
- 物理内存不足:机器总 RAM < 8 GB,且无可用 swap。
- Swap 未配置或不足:即使总内存接近 8 GB,峰值时段无可用内存缓冲。
- 其他进程占用内存:k3s、Docker daemon 本身已占用大量 RAM。
- cgroup 内存限制过严:Docker daemon 的
--memory 限制导致推送时无法申请足够空间。
- 内核参数设置不当:
vm.swappiness 过高等导致 swap 使用策略不合理。
- 镜像层解压峰值:沙箱镜像解压过程中,解压程序本身也需要消耗临时内存。
额外补充(2026 年环境差异):Docker 26+ 与 k3s v1.30+ 默认采用 cgroup v2 进行资源隔离。在 cgroup v2 下,systemd-oomd 可能比传统 OOM Killer 更早介入并杀死进程——这也是为什么部分用户看到进程「莫名其妙」被杀,却没有任何 signal: killed 日志,因为 kill 是 systemd-oomd 发起的,而不是内核 OOM。
三、排查与解决:四个步骤搞定
Step 0:30 秒快速自检
在深度操作之前,先用一行命令组合确认环境:
docker info 2>/dev/null | grep -i cgroup && \
crictl info 2>/dev/null | grep -i cgroup && \
cat /sys/fs/cgroup/memory.max 2>/dev/null
- 看到
cgroupfs 或 systemd 字样,说明当前 cgroup driver 已生效
- 看到具体数值(如
8589934592 表示 8 GiB),则是当前内存上限
Step 1:看清内存与 swap 现状
free -h
swapon --show
df -h /
重点关注 Mem: 行 total 值和 Swap: 行。若 swap 为 0 或 total < 8 GB,进入 Step 2。
free -h 输出解读(这张表新手建议截图保存):
| 字段 | 含义 | 关注点 |
total | 物理内存总量 | 是否 < 8 GB |
available | 可用内存 | 接近 0 时说明内存紧张 |
Swap total | swap 总大小 | 为 0 表示未配置 swap |
Swap free | swap 可用量 | 若 used > 0 说明已在使用 swap |
Step 2:配置 8 GB Swap + 调整 swappiness
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
确认 swap 已启用:
swapon --show
free -h
进阶:优化 swap 使用策略
调整 vm.swappiness 参数,控制系统使用 swap 的积极性:
# 查看当前值
cat /proc/sys/vm/swappiness
# 临时调整为 10(推荐值)
sudo sysctl vm.swappiness=10
# 永久生效
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
注意:配置 swap 会带来性能开销(SSD 写入损耗 + 访问延迟),可避免硬性 OOM。但对于 Jetson Orin 等嵌入式设备,不要把 swap 当生产环境的主要内存来源,仅作为故障恢复的缓冲层。建议采购时优先选择 8 GB 以上 RAM 的嵌入式模组,从根本上避免内存压力。
Step 3:清理后台内存大户
接 Step 3 原文:
ps aux --sort=-%mem | head -10
输出会按内存占用从高到低排序。常见的几个「内存巨兽」:
| 进程 | 常见内存占用 | 临时处理方式 |
k3s server | 3~5 GB | sudo systemctl stop k3s(推送完后再启动) |
containerd | 数百 MB | 同 k3s 一并停止 |
dockerd | 数百 MB | 不要直接停,会影响推送 |
openshell-gateway | 数百 MB | sudo systemctl stop openshell-gateway |
操作示例(仅推送前临时执行):
# 记录原始状态
systemctl is-active k3s openshell-gateway docker > /tmp/services.bak
# 停止非必要服务
sudo systemctl stop k3s
sudo systemctl stop openshell-gateway
# 执行推送
nemoclaw onboard
# 推送完成后恢复
sudo systemctl start k3s
sudo systemctl start openshell-gateway
加分项:可以用 systemd-run --scope -p MemoryMax=6G 包裹推送命令,给整个过程限定内存上限,避免把整个系统拖死。
Step 4:收紧(或放宽)cgroup 内存限制
情况 A:cgroup v2 下系统主动限制过严
检查当前生效的 memory limit:
cat /sys/fs/cgroup/memory.max 2>/dev/null
# 或
systemctl show docker.service -p MemoryMax
如果看到一个明显偏小的数值(例如 4G),可临时解除限制:
sudo systemctl set-property docker.service MemoryMax=infinity
sudo systemctl daemon-reload
sudo systemctl restart docker
永久写在 /etc/systemd/system/docker.service.d/override.conf:
[Service]
MemoryMax=infinity
情况 B:Docker daemon 自带的内存限制过严
编辑 /etc/docker/daemon.json:
{
"default-ulimits": {
"memlock": {
"Name": "memlock",
"Hard": -1,
"Soft": -1
或针对具体容器指定:
docker run --memory=8g --memory-swap=12g ...
四、预防:部署前就把 OOM 摁死
排查只是事后补救,真正的工程化做法是把检查前置。下面这套做法我自己一直在用,亲测能省掉 80% 的「半夜被叫醒修机器」。
1. 部署前资源评估脚本
新建 /usr/local/bin/nemoclaw-precheck.sh:
#!/bin/bash
set -e
TOTAL_MEM=$(free -g | awk '/^Mem:/ {print $2}')
SWAP_TOTAL=$(free -g | awk '/^Swap:/ {print $2}')
if [ "$TOTAL_MEM" -lt 8 ]; then
echo "[FAIL] 物理内存 < 8 GB,建议升级硬件或配置 swap"
exit 1
fi
if [ "$SWAP_TOTAL" -lt 4 ]; then
echo "[WARN] Swap < 4 GB,建议至少配置 8 GB swap"
fi
DISK_FREE=$(df -BG / | awk 'NR==2 {print $4}' | tr -d 'G')
if [ "$DISK_FREE" -lt 10 ]; then
echo "[WARN] 根分区空闲 < 10 GB,swap 文件可能创建失败"
fi
echo "[OK] 资源评估通过"
赋权后 ./nemoclaw-precheck.sh,通过再开始部署。
2. 推送过程内存监控
推送期间另开一个终端:
watch -n 2 'free -h && echo "---" && ps aux --sort=-%mem | head -5'
把 available 列和最占内存的几个进程盯紧。看到 available 接近 0 就该暂停排查,不要等到 OOM 了才反应。
3. systemd-oomd 行为调优(可选)
如果你使用 Ubuntu 22.04+ 且启用了 systemd-oomd,建议调整策略避免误杀:
sudo systemctl edit systemd-oomd.service
写入:
[Service]
Environment=SYSTEMD_OOM_POLICY=continue
continue 表示内存紧张时只警告、不杀进程,适合调试阶段。稳定后改回 kill 即可恢复默认保护。
五、FAQ:实战中最常被问到的几个问题
Q1:加了 swap 会不会把 SSD 写坏?
会,但没那么可怕。SSD 有写入寿命,频繁 swap 会消耗 P/E 周期。嵌入式场景下的缓解手段:①选用工业级 SSD;②降低 vm.swappiness;③监控 Media_Wearout_Indicator 或 Percentage Used 指标。
Q2:能不能直接升级 RAM,不加 swap?
能,这是最稳妥的方案。但 Jetson Orin NX、Orin Nano 等模组的 RAM 是板载 LPDDR5,无法后期扩展——这点务必采购时就确认配置。Orin NX 8GB / 16GB 是两种不同 SKU,出厂即定,没法升配。
Q3:推送失败除了 OOM 还可能是什么原因?
常见还有三类:①磁盘空间不足(df -h 看一眼);②镜像仓库认证失败(docker login 检查 token);③网络 MTU 不匹配(嵌入式设备 USB 网卡常见)。看报错关键词先分桶:看到 cannot allocate memory 走本文流程;看到 unauthorized 走认证流程;看到 i/o timeout 走网络流程。
Q4:Jetson Orin Nano 4GB 能不能跑 NemoClaw?
4GB 是真的不够,全程配 swap 也很吃力。建议至少从 8GB SKU 起步,或者考虑 Orin NX 16GB 一步到位。
Q5:推送过程卡住不动,是不是一定 OOM?
不一定。卡住也可能是网络问题,可以用 docker stats 看一下容器实际资源使用,如果内存还没打满,多半是镜像仓库网络挂了。
六、写在最后
OOM 这种故障,本质上是「资源与需求不匹配」的工程问题。一次性根治的办法只有两个:加内存,或减需求。但嵌入式场景下我们往往两者都做不了,所以 swap、调参、清理进程这套组合拳就成了必备技能。
希望这份排查指南能帮你少踩坑。如果你在实战中遇到了本文没覆盖到的 case,欢迎在评论区贴出 free -h、docker info 输出和报错日志,咱一起拆解。
【标签】
NemoClaw, Docker, k3s, Jetson, OOM排查, 嵌入式部署, 边缘AI, cgroup v2, 内存调优, systemd-oomd
【相关阅读】
- k3s v1.30+ 内存调优实战:从 OOM 到稳定运行
- Docker 26+ cgroup v2 配置详解:资源隔离新范式
- NVIDIA Jetson Orin 系列边缘 AI 部署对比(NX / Nano / AGX)
来源华强北商行 · 数码科技资讯