Docker容器跑在轻量云上内存不够用怎么办
Docker容器跑在轻量云上内存不够用,先别急着扩容
轻量云上跑 Docker,最常见的尴尬场景是:机器看着还有点内存,容器却时不时被杀;或者应用没报明显错误,访问突然变慢,过一会儿又恢复。实际使用中发现,这类问题不一定都是配置太低,也可能是容器内存限制、Swap、日志、缓存、进程模型没处理好。
轻量云通常是 1C1G、2C2G、2C4G 这类规格,适合跑小站、API、管理后台、爬虫、监控节点、轻量数据库、边缘代理。但 Docker 一上来就把应用、数据库、缓存、反向代理全塞进去,内存压力会来得很快。
这里不要只盯着 free -h 看到的 available。Linux 会把空闲内存拿去做 page cache,表面看 used 很高不一定有问题。真正要看的是容器有没有 OOM、宿主机有没有频繁 Swap、应用的 RSS 是否持续上涨。
先确认是不是 OOM,不要凭感觉调配置
排查时先看 Docker 容器状态。执行 docker ps -a,如果容器 Exit Code 是 137,基本就要怀疑被 OOM Killer 干掉了。再看 docker inspect 容器名,里面有 OOMKilled 字段,true 就很明确。
宿主机上可以看内核日志:dmesg -T | grep -i 'killed process'。如果看到 java、node、mysqld、php-fpm 之类的进程被 killed,说明内存已经顶到系统保护线了。
再看实时资源:docker stats。这个命令能看到每个容器的 MEM USAGE / LIMIT。实际排查里经常遇到一种情况:宿主机 2G 内存,某个容器被限制成 512M,应用一高峰就炸;还有一种是完全没限制,结果某个容器把整台机器吃满,连 SSH 都卡。
几个常见现象对应的问题
容器隔一段时间自动重启,日志里没明显应用异常,多半是 OOM 或健康检查失败。
宿主机 SSH 卡顿、top 里 wa 不高但 load 飙升,可能是内存不足后进入 Swap 抖动。
docker logs 巨大,磁盘快满,应用写日志变慢,也会造成看起来像内存问题的卡顿。
Java、Elasticsearch、Redis 这类服务跑在小内存机器上,如果不显式限制内存,很容易默认吃得过猛。
给容器设内存限制,不是为了省,是为了不拖垮整台机器
很多人跑 Docker 时直接 docker run -d,不加 --memory。轻量云上这样做风险比较大。因为某个容器内存泄漏,影响的不是它自己,而是整台宿主机。
建议给每个容器设置明确上限,例如:
docker run -d --name app --memory=512m --memory-swap=768m nginx
这里 --memory=512m 表示容器最多使用 512M 物理内存,--memory-swap=768m 表示物理内存加 Swap 总计最多 768M。注意 Docker 里 memory-swap 的语义容易误解,它不是单独的 Swap 大小,而是总量。
如果是 docker compose,可以这样写:
services: app: mem_limit: 512m memswap_limit: 768m
实际生产里,不建议把所有容器的上限加起来刚好等于宿主机内存。宿主机系统、Docker daemon、日志、文件缓存、监控 agent 都要吃内存。2G 机器上,容器总内存限制控制在 1.4G 到 1.6G 更稳一点;4G 机器上,留 600M 到 1G 给系统会舒服很多。
Swap 可以救急,但不能当内存用
轻量云内存不够时,开 Swap 是常用操作。尤其是 1G、2G 机器,完全没有 Swap,稍微波动就可能触发 OOM。加 1G 或 2G Swap,可以把一些瞬时峰值扛过去。
常见做法是创建 swapfile:
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
再调一下 swappiness:
sysctl vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.conf
这里补充一点,Swap 只是防止服务直接死,不是性能方案。SSD 上 Swap 比内存慢很多,如果应用长期在 Swap 里来回换页,接口延迟会明显升高。比如 API 平时 30ms,进入 Swap 抖动后可能到 300ms 甚至几秒。数据库更明显,MySQL 一旦内存不足频繁换页,慢查询会突然变多。
轻量云上最容易吃内存的不是 Nginx,而是这些服务
Nginx 本身很省,正常配置下几十 MB。真正占内存的通常是应用运行时、数据库、缓存、中间件。
Node.js 默认 V8 old space 在 64 位环境下可能给得比较大,小内存机器上建议设置 NODE_OPTIONS=--max-old-space-size=256 或 512,看业务情况调整。
Java 服务更要注意。不要让 JVM 默认按宿主机资源猜内存。可以显式设置 -Xms256m -Xmx512m。Spring Boot 小服务如果只跑几个接口,512M 堆通常能跑,但依赖太重、开启大量连接池就另说。
MySQL 在 1G 或 2G 机器上不要用默认大配置。innodb_buffer_pool_size 可以先从 256M、512M 起步。连接数 max_connections 也别开太高,很多轻量应用根本不需要 500 个连接。
Redis 如果作为缓存使用,一定设置 maxmemory 和淘汰策略。比如 maxmemory 256mb,maxmemory-policy allkeys-lru。不设上限的 Redis 在小机器上很危险,数据一多直接顶爆内存。
PHP-FPM 要看 pm.max_children。一个 PHP 进程如果占 40M,开 50 个就是 2G。轻量云上常见的配置是 pm.max_children=5 到 10,再结合请求量慢慢调。
日志也是内存和磁盘问题的放大器
Docker 默认 json-file 日志如果不限制,时间一长会非常夸张。遇到过轻量云磁盘被容器日志打满,应用写文件失败,数据库也异常,最后表现出来像服务“内存不够、运行不稳”。
建议在 /etc/docker/daemon.json 里加日志轮转:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }
改完重启 Docker:systemctl restart docker。注意重启会影响容器,线上机器要安排窗口。
应用日志也不要全打 DEBUG。实际使用中发现,很多小服务压测时内存和 CPU 都还行,磁盘 IO 被日志拖住了,接口照样变慢。
镜像和进程模型也会影响内存占用
同一个应用,用不同基础镜像,内存占用差异不一定特别大,但镜像体积、启动速度、依赖复杂度会影响运维体验。Alpine 镜像小,不过有些依赖 musl libc,排查问题时不如 Debian slim 顺手。生产环境不要只追求镜像越小越好,关键是稳定、可维护。
容器里不要跑一堆无关进程。一个容器一个主进程更容易控制资源,也更容易看 docker stats。把 Nginx、PHP、MySQL、Redis 全塞一个容器里,出问题时很难判断谁吃了内存。
多说一句,轻量云上 docker compose 很方便,但不要把它当 Kubernetes 用。机器只有 2G 内存,却放十几个服务,监控、数据库、消息队列、管理后台全上,后面排查起来基本就是拆服务。
轻量云规格怎么选,别只看 CPU 核数
Docker 场景里,内存优先级经常高于 CPU。很多 Web 应用 CPU 平时只跑 5% 到 20%,但内存长期占着释放不掉。尤其是 Java、Node.js、Python worker、数据库混跑时,2C2G 比 4C1G 更实用。
可以按下面这种方式粗略估算:
纯静态站点或反向代理:1G 内存能跑,配 Nginx、Caddy、简单监控问题不大。
小型 API 加 Redis:建议 2G 起步,Redis 设置 maxmemory。
小型 API 加 MySQL:建议 2G 到 4G,MySQL 参数要收紧。
Java 服务加 MySQL:4G 起步更稳,2G 能跑但容错空间很小。
多个 Docker 服务混跑:能上 4G 就不要卡 2G,尤其是还要留日志、监控、备份任务。
如果业务还涉及海外访问、回国链路、游戏服、跨境管理后台,除了内存还要看线路质量。比如美国到国内访问,普通线路晚高峰抖动会比较明显,CMIN2、CN2、GIA 这类精品线路体验差异很大。购买时如果想要云服务器配置和线路一起考虑,可以看看129云,它有美国精品网、香港大宽带、高防服务器等产品,适合游戏、企业、高防、海外云计算这类场景,客服热线 400-9177118 可以直接确认具体线路和防护配置。
几个配置场景可以这样对照
美国精品网-C型:4C、8G DDR4 ECC、100G SSD、100Mbps 峰值、1 个 IPv4,三网精品线路,霄龙 CPU,带基础防御。这个规格比较适合 Docker 跑中小型业务,比如 API、后台、Redis、MySQL 拆分不细但又不想频繁 OOM 的场景。
美国精品网-D型:8C、8G DDR4 ECC、1000G SSD、100Mbps 峰值、1 个 IPv4,CMIN2 高速回国线路。适合磁盘占用比较大的服务,比如日志留存、文件服务、资源站、跨境业务节点。8G 内存对 Docker 来说空间明显宽松,1000G SSD 也能减少日志和数据增长带来的焦虑。
香港大宽带-B型:4C、4G DDR4 ECC、70G SSD、500Mbps 峰值、1 个 IPv4、1TB 单向流量。适合更看重带宽和低延迟访问的业务,比如下载分发、企业入口、小型业务网关。4G 内存跑 Docker 要控制服务数量,别把数据库和一堆 worker 全压上去。
容器内存参数要和应用参数一起调
只给 Docker 设置 --memory,不调应用本身,有时会出现应用不知道自己被限制了,还按宿主机内存规划资源。比较典型的是 JVM、Node.js、数据库连接池。
比如容器限制 1G,Java 堆设置 -Xmx900m,看着没问题,但还要考虑 Metaspace、线程栈、Direct Memory、JIT、native library。实际运行可能超过 1G 后被杀。更稳的方式是容器 1G,JVM 堆先给 512M 到 700M,再看 GC 和接口延迟。
Node.js 也是类似。容器 512M,old space 直接开到 512M,进程整体内存会超过限制。可以先设 --max-old-space-size=384,再根据 heap snapshot 看是否有泄漏。
MySQL 更不能只看 innodb_buffer_pool_size。sort_buffer_size、join_buffer_size、tmp_table_size、max_connections 都可能叠加。轻量云上把 max_connections 从默认或较大值压到 50、100,经常比盲目扩内存更有效。
别忽略 cgroup 版本差异
现在很多新系统默认是 cgroup v2。Docker 在 cgroup v1 和 v2 下内存统计表现有差别,一些老脚本读 /sys/fs/cgroup/memory 可能会失效。排查时如果发现监控数据和 docker stats 不一致,要看一下系统 cgroup 版本。
可以用 stat -fc %T /sys/fs/cgroup 判断。输出 cgroup2fs 就是 cgroup v2。线上不建议为了一个监控脚本随便切 cgroup 版本,优先升级监控或改采集方式。
内存泄漏要用趋势看,不能只看某一秒
很多服务刚启动内存 200M,跑一天变 800M,跑两天 OOM。docker stats 看一眼不一定抓得到问题。建议接入 Prometheus、Grafana,或者轻量一点,用 Netdata、Glances、node_exporter 也行。
判断内存泄漏时看 RSS 趋势。如果请求量下降后内存完全不回落,或者每次定时任务后都抬高一截,就要查代码、连接池、缓存、文件句柄。
Java 看 GC 日志和 heap dump,Node.js 看 heap snapshot,Python 看 tracemalloc 或 objgraph。Go 服务则要看 pprof,尤其是 goroutine 泄漏、map 缓存无限增长这类问题。
轻量云跑数据库,能拆就拆
很多小项目一开始为了方便,把 MySQL、Redis、应用都放一台轻量云上,这没问题。但业务稍微起来后,数据库会成为内存大户,也会让整台机器稳定性变差。
如果预算允许,数据库单独放一台,或者用云数据库。应用容器挂了可以快速重启,数据库因为内存不足被杀,恢复成本就高很多。尤其是 MySQL 正在写入时被 OOM,虽然 InnoDB 有恢复机制,但恢复期间服务不可用,慢日志和错误日志也会增加。
实在必须混跑,就给数据库更明确的资源边界。MySQL 不要无上限,Redis 必须 maxmemory,应用 worker 数量要控制。备份任务放到低峰期,不要凌晨同时跑 mysqldump、日志压缩、镜像构建。
镜像构建不要放在小内存生产机上硬跑
不少人直接在轻量云生产机上 docker build。Node 前端构建、Java Maven 构建、Python 编译依赖都可能吃大量内存。2G 机器上 npm build 被杀很常见。
更好的方式是本地或 CI 构建镜像,推到镜像仓库,生产机只 docker pull 和 docker compose up -d。这样生产机内存压力小很多,也减少构建残留层占磁盘的问题。
如果必须在机器上构建,至少避开业务高峰,并且清理无用镜像:docker system df 看占用,docker image prune 清理悬空镜像。不要随手 docker system prune -a,可能把暂时没运行但还要用的镜像删掉。
容器重启策略要设置,但别用它掩盖问题
restart: always 或 unless-stopped 能提高服务可用性。容器被 OOM 杀掉后自动拉起,短时间内用户可能感知不明显。
但如果容器一直 OOM、一直重启,重启策略只是把问题延后。docker ps 里看到 STATUS 一直是 Restarting,或者 uptime 很短,就要看 docker inspect、dmesg、应用日志。
线上建议给关键容器加健康检查,比如 HTTP /health。健康检查失败时让编排工具重启容器,比进程假死在那里更好。不过健康检查间隔别太密,小机器上每秒探测一堆服务也会增加负担。
轻量云内存紧张时的处理顺序
如果服务已经卡住,先别急着重启所有容器。先保留现场:free -h、top、docker stats、docker ps -a、dmesg -T、df -h、du -sh /var/lib/docker/containers/*。这些信息能判断是内存、磁盘、日志还是单个进程异常。
确认是某个容器吃满内存,可以先重启单个容器,不要直接 reboot。重启后马上补限制参数,否则过一阵还会复现。
如果是宿主机完全无响应,只能通过云厂商控制台重启。恢复后第一件事看 dmesg 和 Docker 日志,不要只看到服务起来了就结束处理。
短期止血可以开 Swap、降低 worker 数、关 DEBUG 日志、限制 Redis、调小 JVM/Node 内存。中期要拆数据库、拆构建流程、做监控。业务持续增长时,该换 4G、8G 就换,不要把时间都花在 1G 机器上抠参数。
一个实际配置参考
2C2G 轻量云,跑 Nginx、一个 Node.js API、Redis、小型 MySQL,可以这样压一下资源:
Nginx 不单独限制太死,通常 128M 到 256M 足够。
Node.js 容器限制 512M,NODE_OPTIONS=--max-old-space-size=384。
Redis 设置 maxmemory 256mb,maxmemory-policy allkeys-lru。
MySQL innodb_buffer_pool_size 设置 512M,max_connections 控制在 50 到 100。
宿主机开 1G 到 2G Swap,vm.swappiness=10。
Docker 日志限制 max-size=100m,max-file=3。
这种配置能跑不少小业务,但余量不大。访问量上来、定时任务变多、日志增长后,很容易逼近边界。要是换成 4C8G,容器限制可以更从容,数据库 buffer pool、应用 worker、缓存空间都有调整余地,稳定性差距会很明显。
什么时候该直接升级机器
如果 OOM 是偶发,优化参数和加 Swap 通常能处理。如果内存长期超过 80%,Swap 持续有读写,应用延迟已经受影响,就不要再硬扛。
还有一种情况是业务本身已经不适合轻量配置:Java 多服务、MySQL 数据量增长、Redis 缓存变大、还有定时任务和文件处理。继续压在 2G 机器上,维护成本会比机器差价高。
选择新机器时,把内存、磁盘、带宽、线路一起看。海外业务尤其要注意回国链路,普通国际线路和精品线路在晚高峰差别很明显。像129云的美国精品网-C型有 4C8G、三网精品和基础防御,适合从轻量云升级到更稳的 Docker 主机;如果需要更大磁盘和 CMIN2 高速回国,美国精品网-D型会更适合。
最后处理一个容易忽略的细节:监控告警阈值
内存告警不要只设 used 大于 90%。Linux page cache 会让 used 看起来偏高。更合理的是看 available、Swap 使用量、容器内存占比、OOM 次数、重启次数。
可以把告警设成:available 低于 15% 持续 5 分钟;Swap 使用超过 30% 持续 10 分钟;容器内存超过限制的 85% 持续 5 分钟;容器 10 分钟内重启超过 2 次。
这些阈值不需要一开始就很复杂,先能发现问题,再慢慢减少误报。轻量云资源本来就紧,监控比事后翻日志更省时间。