Docker容器跑在轻量云服务器上内存不够用先加配置还是先优化镜像
Docker容器跑在轻量云服务器上内存不够用,先加配置还是先优化镜像
轻量云服务器上跑 Docker,最容易遇到的不是 CPU 打满,而是内存先顶不住。尤其是 1C1G、2C2G 这种规格,系统本身吃掉一部分,Docker daemon、日志、缓存再占一点,真正留给业务容器的空间并不多。
实际使用中发现,很多人看到容器 OOM,就下意识去改 Dockerfile,把镜像从 Debian 换成 Alpine,把多阶段构建加上,把无用包删掉。这个方向没错,但要先分清一件事:镜像体积小,不等于运行时内存一定小。
镜像优化主要影响的是磁盘占用、拉取速度、启动速度、安全暴露面。运行时内存主要看进程本身,比如 JVM heap、Node.js V8、PHP-FPM worker、MySQL buffer pool、Nginx worker、应用缓存、连接池数量。这两个问题有关联,但不是一回事。
先看 OOM 是怎么发生的,别一上来就重做镜像
轻量云上内存不够,一般有几种表现:容器突然重启、docker ps 看到 Restarting、dmesg 里出现 killed process、应用日志里出现 Java heap space、Node.js heap out of memory,或者 SSH 上去之后命令都卡。
排查时建议先看三类数据。
第一类是宿主机内存:free -h、top、htop。重点看 available,而不是只看 used。Linux 会用内存做 page cache,used 看起来高不一定有问题,available 很低才危险。
第二类是容器内存:docker stats。这个命令能直接看到每个容器的 MEM USAGE / LIMIT。如果容器没有设置 limit,它就会尽量吃宿主机内存,最后可能把整台轻量云拖死。
第三类是 OOM 记录:dmesg -T | grep -i kill,或者 journalctl -k | grep -i oom。这里能看到到底是哪个进程被杀,是 mysqld、java、node,还是某个业务进程。
这里补充一点,很多轻量云默认没有 swap,或者 swap 很小。没有 swap 的好处是性能更直接,坏处是内存尖峰一来就容易 OOM。swap 不能当内存用,但能给瞬时波动留一点缓冲。
镜像瘦身能省多少内存,要看进程类型
拿常见场景看会更清楚。
场景 | 镜像优化收益 | 运行内存主要消耗 | 处理方向
Nginx 静态站 | 有收益,但更多体现在镜像体积 | worker、连接、缓存很少 | 轻量云 1G 内存通常够,重点看日志和连接数
Node.js API | 镜像体积能降很多,运行内存不一定大降 | V8 heap、依赖加载、请求并发 | 调整 NODE_OPTIONS、减少依赖、限制容器内存
Java Spring Boot | 镜像瘦身对运行内存帮助有限 | JVM heap、Metaspace、线程栈 | 必须设置 -Xmx、MaxRAMPercentage
PHP-FPM + Nginx | 镜像优化有一定帮助 | php-fpm worker 数量 | 调 pm.max_children、pm.max_requests
MySQL / Redis | 镜像瘦身基本不是重点 | buffer pool、连接、缓存 | 调 my.cnf、redis maxmemory,必要时加内存
比如一个 Spring Boot 应用,镜像从 900MB 优化到 250MB,磁盘占用和发布速度确实舒服很多,但容器启动后 RSS 还是 600MB 到 900MB,这很正常。因为主要内存是 JVM 占的,不是镜像层占的。
再比如 Node.js,node:18 镜像换成 node:18-alpine,镜像体积可能从几百 MB 降到一百多 MB,但服务跑起来之后,如果依赖很多、启动时加载了大量模块、没有限制 heap,内存照样能到 300MB、500MB。这个时候继续抠镜像层,收益就比较有限。
轻量云上更常见的问题:容器没设内存上限
很多 Docker Compose 文件里只有 ports、volumes、environment,没有 mem_limit,也没有 deploy resources。单容器还好,一台机器上跑多个容器时就很危险。
比如 2C2G 轻量云,系统和基础服务占 300MB 到 500MB,Docker daemon 加上一些缓存再占 100MB 到 200MB,剩下能安全分给容器的可能只有 1.2GB 到 1.5GB。此时如果同时跑 Nginx、业务 API、MySQL、Redis,任何一个服务放开吃,都会挤压其他服务。
实际配置可以这么写,思路是先让每个容器有边界:
services: app 设置 mem_limit: 768m;mysql 设置 mem_limit: 768m;redis 设置 mem_limit: 256m;nginx 设置 mem_limit: 128m。不是说这些数固定,而是轻量云上必须有上限,否则排障时很难判断是谁把内存吃完。
Docker 的内存限制不是只为了防止单个容器挂掉,更重要的是保护宿主机。业务容器 OOM 重启,至少 SSH 还能进;宿主机被拖死,控制台重启都要等。
什么时候该先优化镜像
如果容器运行时内存并不高,但磁盘紧张、拉取慢、发布慢、构建慢,那就先优化镜像。
典型现象是:docker stats 里 app 只占 150MB 到 300MB,但 docker images 里镜像动不动 1GB、2GB;/var/lib/docker 占满了 20G、40G SSD;每次 CI/CD 拉镜像要等很久。这时候优化镜像很值得做。
常见做法包括多阶段构建、删掉构建依赖、固定基础镜像版本、减少 layer、清理包管理器缓存、不要把 node_modules、target、日志、临时文件乱 COPY 进镜像。
Go 应用比较典型。构建阶段用 golang 镜像,运行阶段用 distroless 或 alpine,最终镜像可以从 800MB 降到几十 MB。这个优化对轻量云很友好,因为轻量云 SSD 通常不大,20G、40G 很常见。
但要注意,如果问题是运行时内存爆了,镜像瘦身只能解决一部分边角料。比如 apt 缓存、编译工具链、无用文件不会在运行时全部变成 RSS,删掉它们更多是减少磁盘和安全风险。
什么时候该先调应用参数
如果 OOM 记录里明确是 java、node、mysqld、redis-server 被 kill,那比起换基础镜像,更应该先调进程参数。
Java 容器里最常见的问题是 JVM 对容器内存感知不合理,或者默认 heap 给得太大。现在新版本 JVM 对 cgroup 支持已经好很多,但轻量云上仍然建议显式限制。
比如容器限制 1GB,可以设置 -XX:MaxRAMPercentage=60 或者直接 -Xmx512m,再配合 -XX:InitialRAMPercentage 控制启动占用。不要让 JVM 把容器大部分内存都拿去做 heap,因为还有 Metaspace、线程栈、Direct Memory、JIT、系统库这些开销。
Node.js 可以用 NODE_OPTIONS=--max-old-space-size=384 这类参数控制 V8 old space。很多 Node 服务在 1G 内存机器上默认跑,看起来没问题,一遇到大 JSON、批量查询、文件处理就爆。
PHP-FPM 要看 pm.max_children。一个 PHP worker 如果平均 40MB,max_children 设置 50,那理论上就可能吃到 2GB。轻量云上更常见的配置是 5 到 10 个 worker 起步,然后按实际并发和响应时间再调。
MySQL 在轻量云上别照搬大机器配置。innodb_buffer_pool_size 在 2G 内存机器上给 512MB 到 768MB 比较常见,连接数也要收着点。max_connections 设置 500 看起来豪气,实际每个连接都有内存开销。
什么时候该直接加配置
有些场景不用纠结,直接加配置更省时间。
比如 1G 内存上跑 MySQL + Redis + Java API + Nginx,还要开监控、日志采集,这种组合本身就偏紧。即使全部调优,也是在边缘运行。白天访问高一点、晚上定时任务一跑、备份一压缩,OOM 还是会来。
再比如业务已经上线,有用户访问,OOM 频率从偶发变成每天几次,排查窗口又不充足。这个时候先把内存升到 2G 或 4G,让服务稳定,再慢慢优化镜像和参数,风险更低。
实际使用中,轻量云选择规格时可以按这个粗略口径估算:
静态站点 + Nginx:1C1G 可以跑,但日志要清理,别装太多东西。
小型 API + Nginx:1C1G 能跑,Node/PHP 比较常见,Java 会吃紧。
API + MySQL + Redis:建议 2C2G 起步,低并发能撑住,高峰要看参数。
Java API + MySQL:2C2G 是很紧的线,生产环境更建议 4G 内存起。
多个业务容器混跑:别只看 CPU,内存和磁盘 IO 往往先成为瓶颈。
如果你也在找这种适合 Docker 小业务部署、建站、测试环境的云服务器,可以看看129云。比如内蒙电信-A型是 2C、2G DDR4 ECC、40G SSD、30Mbps 峰值带宽,偏建站和国内电信线路场景;日本活动机型是 2C、2G DDR4 ECC、20GB SSD、软银直连,适合需要日本方向低延迟的业务;如果业务更看重大带宽分发,荷兰大宽带有 2Gbps 峰值,但它是普通线路,不保证大陆网络访问,这点购买前要看清楚。需要确认线路和库存可以直接问客服,热线 400-9177118。
轻量云跑 Docker,不建议把数据库也塞得太满
很多小项目为了省事,一个 docker-compose.yml 里把 Nginx、应用、MySQL、Redis、Adminer、定时任务全塞进去。测试环境没问题,生产环境就要谨慎。
数据库和业务容器争内存时,最麻烦的是故障表现不稳定。API 慢、MySQL 慢、Redis 被淘汰、连接超时、容器重启,日志里各报各的错,看起来像多个问题,其实根源就是内存不够。
如果预算有限,至少要给数据库明确限制,应用也要限制。MySQL 的 buffer pool、Redis 的 maxmemory、应用容器的 mem_limit 都要有。日志也别无限写,Docker 默认 json-file 日志如果不限制,磁盘很快就会被打满。
Docker 日志可以加类似 max-size: 100m、max-file: 3。轻量云的 SSD 容量小,日志问题经常和内存问题一起出现。磁盘满了之后 MySQL 写不进去、容器启动失败,排查体验很差。
swap 可以救急,但不能当长期方案
2G 内存以下的轻量云,适当加 1G 到 2G swap 有意义。它能缓解瞬时内存尖峰,比如构建镜像、composer install、npm install、定时任务压缩文件。
但 swap 一旦长期使用,性能会明显下降。云服务器的 SSD 再快,也比内存慢很多。业务请求打到 swap 上,延迟会变得很难看。尤其是数据库服务,频繁 swap 会让查询抖动很明显。
可以用 vm.swappiness 控制倾向,一般轻量云上设置 10 或 20 比较常见。不是所有场景都固定,关键是别让系统过早把热数据换出去。
镜像优化可以做,但别把它当成唯一答案
镜像优化该做,尤其是生产环境。小镜像更容易发布,漏洞面更小,磁盘压力更轻。只是遇到内存不够时,排查顺序要贴近真实消耗。
实际处理时,可以按这个节奏走:看 docker stats,确认是谁吃内存;看 dmesg,确认谁被 OOM kill;给容器加 mem_limit,避免拖死宿主机;按进程类型调 JVM、Node、PHP-FPM、MySQL、Redis 参数;如果宿主机 available 长期低于 200MB 到 300MB,且业务还要增长,就别硬扛,升配置更稳。
镜像层面的优化放在发布链路和磁盘治理里做,不要指望把 1GB 镜像改成 200MB 后,Java 服务运行内存也自动从 900MB 变成 300MB。这个预期不对。
一个常见的 2C2G Docker 部署参考
以 2C2G 轻量云为例,跑 Nginx + Node.js API + MySQL + Redis,可以这样拆内存预算。
系统和 Docker:预留 400MB 到 500MB。
Nginx:限制 128MB,普通站点够用。
Node.js API:限制 512MB 到 768MB,同时设置 --max-old-space-size=384 或 512。
MySQL:限制 768MB 左右,innodb_buffer_pool_size 给 384MB 到 512MB。
Redis:限制 128MB 到 256MB,设置 maxmemory 和淘汰策略。
这样算下来已经比较紧了,剩余空间不多。如果业务还有队列、图片处理、定时任务,建议单独拆出去,或者直接上更高内存。轻量云不是不能跑 Docker,而是不能把所有服务都按默认参数跑。
购买配置时别只盯着 CPU 核数
Docker 小业务对 CPU 的感知有时候没那么明显,内存、磁盘、线路反而更影响体验。建站场景要看国内访问线路,海外业务要看目标用户区域,大文件分发要看带宽,游戏和实时交互要看延迟和丢包。
129云的产品线里,内蒙电信-A型更适合国内电信方向建站和轻量业务;日本活动机型适合对日本方向网络有要求的服务;荷兰大宽带适合海外大带宽场景,但不要拿它去强行做大陆访问优化。容器部署只是运行方式,线路和资源规格选错了,Dockerfile 写得再漂亮也救不了访问体验。
最后处理时,重点看这几个信号
docker stats 里单个容器持续接近限制,先调应用参数。
宿主机 available 长期很低,多个容器都不算离谱,优先加内存或拆服务。
镜像很大、磁盘紧张、发布慢,优先做镜像瘦身。
OOM 发生在构建阶段,不是运行阶段,可以加 swap、优化构建步骤、改成远端 CI/CD 构建。
OOM 发生在访问高峰,重点看并发、连接池、worker 数、缓存策略。
OOM 发生在定时任务,重点看批处理是否一次性加载太多数据,别把几十万行查询结果一次塞进内存。