日本VPS上跑多个Docker容器时出现内存错误,最反直觉的一点是:容器本身没有内存上限。Docker默认不限制每个容器能用多少内存。这意味着只要有一个容器出现内存泄漏或突发占用,它就能吃掉宿主机所有可用内存,直到Linux内核触发OOM Killer,随机杀掉进程来救急。被杀的可能是你的数据库容器,也可能是SSH会话,甚至Docker守护进程本身。
日本VPS的常见配置在1GB到2GB内存区间,这个量级下跑多个容器,如果没有人为设限,OOM几乎是必然结局。排查和修复的顺序应该是:先确认是不是OOM、再给每个容器设硬上限、最后处理那些“设了限还是被杀”的应用层问题。
第一步:确认是不是OOM,别靠猜
容器“挂了”有很多种原因,端口冲突、配置错误、应用崩溃都会让容器退出。先看退出码和OOM标记:
docker inspect <容器名> --format='{{.State.ExitCode}} {{.State.OOMKilled}}'
如果输出是 137 true,基本可以确定是内存超限被内核杀掉。137 是 128+9,代表进程收到了 SIGKILL,而 OOMKilled: true 直接说明这次终止由OOM触发。
再补一刀,看内核日志里有没有对应的记录:
dmesg -T | grep -i "out of memory\|oom-killer"
如果看到类似 Memory cgroup out of memory: Killed process 的行,并且进程名对应你的容器,证据链就完整了。注意,docker ps -a 里显示 Exited (137) 不一定是OOM,也可能是手动 docker kill,所以 OOMKilled 字段比退出码更可靠
第二步:给每个容器设硬上限,这是唯一有效的止损手段
确认是OOM之后,最直接的修复不是“加内存”,而是让每个容器在触碰到自己的天花板时被单独杀掉,而不是拖垮整台机器。Docker的 --memory 参数就是干这个的。
以2GB内存的日本VPS为例,假设跑三个容器:一个Nginx、一个应用服务、一个Redis。分配逻辑应该是:
- Nginx:
--memory 256m,静态代理不需要大内存 - 应用服务:
--memory 1g,留出主要计算空间 - Redis:
--memory 512m,配合maxmemory配置限制实际数据占用 - 剩余约200MB留给系统进程和Docker守护进程本身
运行时的写法:
docker run -d --name app --memory 1g --memory-swap 1g myapp:latest
--memory-swap 设为和 --memory 相同的值,意味着这个容器不使用swap。对于日本VPS这种磁盘I/O可能受限的环境,强制容器在物理内存内运行比让它把压力转移到swap上更可预测。swap的读写速度远低于内存,频繁swap的容器响应时间会急剧恶化。
如果容器已经在运行,可以用 docker update 在线调整(部分场景需要重启容器才能让应用适配新上限):
docker update --memory 1g --memory-swap 1g <容器名>
对于使用 Docker Compose 的项目,在 docker-compose.yml 里写清楚:
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 256M
deploy.resources.limits 在单机 docker compose up 下同样生效,不需要Swarm集群。
第三步:设了限还是被杀,问题在应用层
有一种情况会让设限失效:容器的内存限制设了,但应用自己“不知道”这个限制的存在,按宿主机总内存来规划自己的内存使用。
Java应用是重灾区。 老版本JDK不感知cgroup限制,JVM默认按宿主机物理内存的1/4来设定堆上限。在一个2GB的日本VPS上,JVM可能把 -Xmx 设到512MB甚至更高,加上元空间、线程栈、直接内存,容器总内存轻松突破1GB限制,然后被OOM杀掉。修复方式是显式指定堆大小,并且留出余量:
java -Xmx700m -Xms256m -XX:MaxRAMPercentage=70 -jar app.jar
在容器内存限制为1GB时,堆设在700MB左右比较安全,剩下的300MB留给堆外内存和系统开销。
Node.js应用同理,老生代内存默认不感知容器限制。可以在环境变量里设置:
NODE_OPTIONS="--max-old-space-size=512"
这个值应该低于容器内存限制,给Buffer、Socket和其他原生内存留出空间。
Redis容器即使设了Docker内存限制,Redis自身也可能因为不限制数据集大小而持续增长直到撞上容器天花板。在 redis.conf 里设置 maxmemory 400mb 和 maxmemory-policy allkeys-lru,让Redis在容器限制之前就主动淘汰旧数据,比等着被OOM杀掉更可控。
第四步:日本VPS的swap,开还是不开
日本VPS的存储性能参差不齐,有些低价方案用的是共享型SSD,I/O波动大。在这种环境下,swap能起的作用有限,但不能说完全没用。它的价值在于给OOM Killer争取时间:当内存即将耗尽时,内核可以先换出一部分不活跃的内存页,而不是立刻开始杀进程。
查看当前swap状态:
free -h
如果 Swap 一行全是 0,可以考虑创建一个小的swap文件。2GB内存的机器,1GB到2GB的swap文件是合理的起点。配置之后,Docker容器的 --memory-swap 设置需要重新审视:如果保持 --memory-swap 等于 --memory,容器本身不会用到swap;宿主机层面的swap只在系统整体内存紧张时作为缓冲。这种组合下,容器行为仍然可预测,同时系统多了一层缓冲,不会在内存临界点直接触发OOM。
常见问答
Q:日本VPS只有1GB内存,最多能跑几个Docker容器?
没有固定答案,取决于每个容器的实际内存占用。一个粗略的参考:1GB内存的机器,跑2到3个轻量容器(如单个Nginx、一个小型API服务、一个Redis实例)是可行的,前提是每个容器都设了合理的 --memory 上限,并且宿主机本身没有跑其他吃内存的服务。如果有Java或Node.js应用,1GB通常不够,因为运行时本身就有基础开销。
Q:OOM Killer杀掉的是容器里的进程,还是整个容器?
内核杀掉的是容器内的一个或多个进程(通常是内存占用最高的那个)。如果被杀的是容器的PID 1(主进程),容器会随之退出。如果容器内跑了多个进程,杀掉其中一个非主进程,容器可能仍然存活但服务已经异常。这就是为什么在容器内跑多进程(如supervisord)时,OOM的后果更难预测,也更难排查。
总结:多个容器内存错误其核心矛盾在于宿主机总内存与容器无限制占用之间的冲突。华纳云日本VPS 的内存选项覆盖了从1GB 到 96GB 的区间,选择时需要考虑的不只是“跑几个容器”,而是容器内存上限之和 + 系统预留是否在你的控制范围内。如果目前用的是 1GB 或 2GB 配置,跑多个容器时即使设了 --memory 限制,宿主机本身留给系统、Docker 守护进程、日志缓冲的空间也很有限。4GB 或 8GB 档位能让你的容器内存分配策略有实际的执行空间,而不是在紧巴巴的总量里做零和博弈。日本云服务器支持自定义升级 CPU 和内存,如果你的容器负载会增长,可以把它作为一个可扩展的选项。
推荐文章