Docker容器跑在云服务器上内存该怎么分配不会互相抢资源
Docker容器跑在云服务器上,内存不能靠“谁用谁拿”
云服务器上跑 Docker,最容易踩的坑不是容器起不来,而是刚开始都很正常,访问量一上来,某个容器把内存吃满,宿主机开始 swap,所有服务一起变慢。更严重一点,Linux OOM Killer 介入,随机挑一个进程杀掉,表面看像是某个业务容器自己崩了,实际是整台机器内存分配没管住。
实际使用中发现,很多人部署 Docker 时只写了镜像、端口、挂载目录,没写内存限制。比如一台 2C2G 云服务器上同时跑 Nginx、MySQL、Redis、业务 API、定时任务,容器之间默认没有硬边界。谁申请得快,谁就先拿。等 MySQL buffer、Java heap、Redis dataset、日志缓冲一起涨起来,宿主机剩余内存很快被挤干。
Docker 的内存分配不是看容器数量平均分,而是要按服务类型、峰值行为、宿主机保留量来拆。尤其是云服务器,CPU、内存、磁盘 IO、网络带宽都是固定套餐资源,不能按本地开发机那种“先跑起来再说”的方式处理。
先给宿主机留内存,不要把云服务器榨干
很多内存抢占问题,根源是把整台机器内存都分给容器了。比如 2G 内存机器,看到可用内存是 2G,就给容器总和配到 2G,甚至超过 2G,认为 Linux 会自动调度。这个思路在生产环境不稳。
宿主机自己也要吃内存:systemd、journald、sshd、Docker daemon、containerd、内核 page cache、iptables、监控 Agent、日志采集组件都会占用。磁盘读写多的时候,page cache 还会明显上涨。不给宿主机留余量,容器看似没超限,整机也会抖。
比较稳的做法是按机器规格先扣掉宿主机保留内存:
2G 内存云服务器:宿主机建议保留 400MB 到 600MB,容器总硬限制控制在 1.3G 到 1.5G。
4G 内存云服务器:宿主机建议保留 700MB 到 1G,容器总硬限制控制在 3G 左右。
8G 内存云服务器:宿主机建议保留 1G 到 1.5G,容器总硬限制控制在 6G 到 6.5G。
16G 内存云服务器:宿主机建议保留 2G 左右,容器总硬限制控制在 13G 到 14G。
这里补充一点,保留内存不是浪费。云服务器上很多“偶发卡顿”,看监控都是内存接近打满后触发的连锁反应。尤其是跑数据库、日志量大、镜像频繁更新的机器,宿主机余量太小会让问题很难定位。
Docker内存参数要分清,别只会写 --memory
--memory 是硬限制
--memory 是容器能使用的最大内存。容器超过这个限制后,内核会尝试回收,如果回收不了,就会触发容器内进程 OOM。这个限制是防止一个容器拖垮整台云服务器的关键。
例如:
docker run -d --name api --memory=512m nginx
这个容器最多用 512MB 内存。超过后不是去抢别的容器内存,而是在自己的 cgroup 限制内处理,处理不了就自己被杀。
--memory-reservation 是软限制
--memory-reservation 可以理解为内存紧张时的参考线。它不是硬性上限,机器内存充足时容器可以超过这个值;当宿主机内存紧张时,内核会倾向把容器压回这个值附近。
例如:
docker run -d --name api --memory=768m --memory-reservation=512m my-api:latest
这个配置的意思是,API 容器平时可以最高用到 768MB,但内存紧张时希望它回落到 512MB 附近。对于业务 API、Node.js 服务、PHP-FPM 这类有波动的服务,这个参数比只写硬限制更柔和。
--memory-swap 不建议随手放开
--memory-swap 很容易被误解。Docker 里如果设置 --memory=512m --memory-swap=1g,表示内存加 swap 总共最多 1G,也就是最多 512MB 内存加 512MB swap。
云服务器上是否允许容器用 swap,要看业务类型。数据库、Redis、低延迟 API 不建议依赖 swap。swap 一旦大量使用,服务可能不死,但延迟会变得很难看。游戏服、实时接口、支付链路这类场景,宁愿让容器 OOM 重启,也不要长时间 swap 卡住。
如果希望容器不要使用 swap,可以这样写:
docker run -d --name redis --memory=512m --memory-swap=512m redis:7
这里 --memory 和 --memory-swap 相等,表示不额外使用 swap。
按服务类型分内存,比按容器数量平均分靠谱
同样是一个容器,Nginx 和 MySQL 对内存的行为完全不同。平均分配看起来公平,实际容易把关键服务限制死,也容易让不重要的任务占走资源。
Nginx、网关、静态服务
Nginx 本身内存占用通常不高。普通反向代理、静态站点、证书终止,128MB 到 256MB 很多场景够用。连接数特别高、开启大 buffer、上传下载多的场景要单独看。
常见配置:
--memory=256m --memory-reservation=128m
如果是香港大宽带这种高并发访问、文件分发、小下载业务,不能只看 Nginx 主进程占用,还要看 worker connections、proxy buffer、文件缓存策略。带宽大了以后,内存和连接状态也会跟着涨。
MySQL、PostgreSQL 这类数据库
数据库不建议和一堆业务容器混在小内存机器上硬跑。实在要跑,必须限制容器内存,同时把数据库自己的内存参数调小。只限制 Docker,不调 MySQL 的 innodb_buffer_pool_size,容器到上限后照样 OOM。
2G 机器上跑 MySQL,通常给 MySQL 容器 768MB 到 1G 已经比较紧张,innodb_buffer_pool_size 可以放在 256MB 到 512MB。再加业务容器,就不要指望还能抗很高并发。
4G 机器上 MySQL 容器可以给 1.5G 到 2G,业务容器另算。8G 机器上,如果数据库是主要服务,给 3G 到 4G 更合理,但仍然要保留宿主机和其他容器空间。
Redis 不要只看当前 used_memory
Redis 容器很容易因为数据增长被撑爆。Docker 层限制 512MB,但 Redis 自己没设 maxmemory,到了上限可能直接 OOM。正确做法是 Docker 限制和 Redis maxmemory 配合。
例如 Redis 容器限制 512MB,Redis 的 maxmemory 不要也写 512MB,可以写 384MB 或 400MB,给连接、复制缓冲、AOF rewrite、运行开销留空间。
参考配置:
docker run -d --name redis --memory=512m --memory-swap=512m redis:7 redis-server --maxmemory 400mb --maxmemory-policy allkeys-lru
实际使用中发现,很多 Redis OOM 不是业务瞬间暴涨,而是 key 没过期、缓存当数据库用、AOF rewrite 时内存峰值上来。Docker 限制只能兜底,Redis 自己的策略也要跟上。
Java、Node.js、Go 服务的差异
Java 容器要特别注意 JVM heap。比如容器限制 1G,不能把 -Xmx 也写 1G。JVM 除了 heap,还有 metaspace、线程栈、direct memory、JIT、native memory。一般容器 1G,-Xmx 放 512MB 到 700MB 更稳。
Node.js 默认内存也不是完全按容器限制来自动规划,老版本尤其要注意。可以通过 --max-old-space-size 控制 V8 heap。比如容器 768MB,Node old space 可以设置 512MB 左右。
Go 服务看起来内存管理省心,但在高并发、对象分配多、GC 压力大时,也会出现 RSS 高于预期的情况。Go 服务建议配 GOMEMLIMIT,让运行时知道内存边界。例如容器 512MB,可以设 GOMEMLIMIT=400MiB。
2C2G云服务器怎么分,别塞太多角色
2C2G 是很常见的入门规格,适合轻量站点、小后台、测试环境、低访问量 API。它的问题也明显:内存余量很薄,Docker 容器一多就互相挤。
比较稳的拆法:
宿主机保留:500MB。
Nginx:128MB 到 256MB。
业务 API:512MB 到 768MB。
Redis:256MB 到 512MB,Redis maxmemory 要低于 Docker 限制。
MySQL:如果必须放同机,至少 768MB,但这时业务 API 就要压缩,整机压力会很明显。
2C2G 上比较推荐的组合是 Nginx + 一个业务服务 + Redis 小缓存。MySQL 最好外置,或者换更大内存的云服务器。如果强行 Nginx + API + MySQL + Redis + 定时任务,全都容器化,后面排查 OOM 会很烦。
如果是建站类小业务,内蒙电信-A型这种 2C 2G、40GB SSD、30Mbps 峰值带宽的机器,跑轻量 Docker 服务是够用的,关键是别把数据库、搜索、消息队列全塞进去。选购时可以看看129云这类云服务器产品,建站、企业站、小型 API 场景匹配度比较高,客服热线 400-9177118 也能直接问线路和配置。
4C8G云服务器的容器内存会宽松很多,但也要定边界
4C8G 是更舒服的 Docker 单机场景。可以放 Nginx、业务服务、数据库、Redis、监控 Agent,甚至再加一个后台任务容器。但宽松不代表不限制。8G 内存如果放开跑,Java 服务、MySQL、Redis 一起涨,照样能打满。
一个常见分配方式:
宿主机保留:1G 到 1.5G。
Nginx 或网关:256MB。
业务 API 1:1G 到 1.5G。
业务 API 2:1G 到 1.5G。
MySQL:2G 到 3G,根据数据量和查询压力调整。
Redis:512MB 到 1G,内部 maxmemory 留 20% 左右余量。
监控、日志、定时任务:每个 128MB 到 512MB,看实际行为。
香港大宽带-C型是 4C 8G、300Mbps 峰值带宽、1TB 流量,适合对带宽和访问质量更敏感的站点、跨境业务、下载分发、企业应用入口。跑 Docker 时可以把网关、业务服务、缓存拆开限制,避免大流量下某个服务把内存和连接资源拖满。如果你也在找这种高带宽云服务器,可以看看129云的香港大宽带产品。
docker compose 里要把限制写进去,不要只写在命令行
生产环境大多不会一直手敲 docker run,而是用 docker compose 管理。内存限制最好也写进 compose 文件,不然迁移、重启、交接时很容易丢。
compose 里可以这样写:
services:
api:
image: my-api:latest
mem_limit: 768m
mem_reservation: 512m
redis:
image: redis:7
mem_limit: 512m
command: redis-server --maxmemory 400mb --maxmemory-policy allkeys-lru
这里多说一句,不同 compose 版本、不同 Docker 运行模式,对 deploy.resources 的支持不一样。很多人把 Swarm 模式的写法复制到普通 docker compose 里,结果限制没生效。单机 Docker 场景,确认 docker stats 和 docker inspect 看到的限制才算数。
判断容器是不是在抢内存,看这几个现象就够直接
容器互相抢资源时,表现不一定是“内存满了”这么直观。常见现象包括:接口延迟突然变大、数据库连接超时、Redis 偶发断连、容器自动重启、SSH 登录变慢、执行 docker ps 都卡。
排查时先看宿主机:
free -h 看 available,不要只看 free。Linux 会把空闲内存用于 cache,available 更接近还能给进程用多少。
top 或 htop 看 RES 和进程排序,确认是不是某类服务持续增长。
vmstat 1 看 si、so,如果 swap in、swap out 持续有值,说明已经在靠 swap 硬撑。
dmesg -T | grep -i oom 看是否发生过 OOM。很多容器重启,业务日志里不一定能看到原因,但内核日志里有。
再看 Docker:
docker stats 看每个容器的 MEM USAGE / LIMIT,注意是不是某个容器长期贴近上限。
docker inspect 容器名 | grep -i memory 看限制是否真的生效。
docker events 可以观察容器是否发生 oom、die、restart。
如果某个容器内存一直涨,不要马上加机器。先确认是业务正常缓存、连接未释放、内存泄漏、日志堆积,还是程序本身没有按容器限制设置运行参数。加内存能延后爆炸,但不能替代定位。
容器重启策略要配,但不能拿它当内存治理
--restart=always 或 compose 里的 restart: always 很常见。它能让 OOM 后的容器自动拉起来,减少人工处理。但这只是恢复手段,不是内存分配策略。
如果容器因为内存超限每隔几分钟重启一次,业务看起来还能访问,实际状态已经不健康。尤其是有队列消费、定时任务、支付回调、文件处理的服务,频繁重启会带来重复执行、中间状态丢失、任务积压。
更稳的做法是把内存限制、应用内部限制、健康检查一起配。比如 API 容器 768MB,JVM heap 512MB,健康检查 30 秒一次,连续失败再重启。Redis 容器 512MB,Redis maxmemory 400MB,淘汰策略明确。MySQL 容器 2G,buffer pool 不超过 1.2G 到 1.5G,并观察连接数和临时表。
不要忽略日志,日志也会间接吃内存和磁盘
Docker 默认 json-file 日志如果不限制,容器输出多了会把磁盘打满。磁盘满以后,数据库写入失败、容器异常、系统服务报错都会出现。虽然这不是内存抢占,但现场排障时经常和内存问题混在一起。
建议给 Docker 日志加限制:
docker run --log-driver=json-file --log-opt max-size=100m --log-opt max-file=3 ...
compose 里写:
logging:
driver: json-file
options:
max-size: "100m"
max-file: "3"
日志量大的服务还会影响 page cache,间接影响 available memory。特别是访问日志、debug 日志、爬虫流量明显的站点,日志策略和内存稳定性是有关联的。
小内存机器上不要同时跑太多基础组件
2G 或 4G 云服务器上,最容易被低估的是“基础组件的固定开销”。MySQL、Redis、RabbitMQ、Elasticsearch、Prometheus、GitLab、Jenkins 这些东西单看都能容器化,但放在一台小机器上就是另一回事。
Elasticsearch 这类服务不适合塞进 2G 机器。Jenkins 也容易吃内存,构建时还会拉镜像、解压依赖、启动子进程。Prometheus 如果抓取目标多、保留时间长,也会涨。不要因为它们都是容器,就以为隔离后能安全共存。
韩国活动机型 2C 2G、20GB SSD、3Mbps 带宽,更适合轻量服务、测试环境、低流量业务入口。用 Docker 跑一两个核心容器没问题,别把它当成全家桶宿主机。内存小、硬盘小、带宽也不高,部署策略要收敛。
内存分配可以按“硬限制总和”算,不要按平均使用量算
很多人看监控时会说:这些容器平时总共才用 1.2G,为什么 2G 机器不能跑?问题在于平时平均值没法代表峰值。容器抢资源通常发生在峰值叠加:访问量上来、定时任务启动、数据库备份、日志切割、缓存重建、镜像更新,几个动作撞在一起。
更接近生产的算法是看硬限制总和。比如 2G 机器,宿主机保留 500MB,容器硬限制总和控制在 1.5G 以内。Nginx 256MB,API 768MB,Redis 512MB,加起来 1.5G。这个组合能解释清楚,出了问题也知道谁到边界。
如果写成 Nginx 不限、API 不限、Redis 不限,平时看着都只用一点,峰值来了就变成互相抢。Docker 的价值之一就是把边界划出来,不划边界,只是换了一种方式裸跑进程。
CPU和内存要一起看,内存限制太小也会拖慢CPU
内存限制不是越小越安全。容器内存给得过低,应用频繁 GC、缓存命中下降、数据库反复读盘,CPU 反而上升。表面上是 CPU 忙,根因可能是内存太紧。
Java 服务最典型。heap 太小,Full GC 频繁,接口延迟变大,CPU 被 GC 吃掉。MySQL buffer pool 太小,热数据放不下,磁盘 IO 上来,查询变慢。Redis maxmemory 太低,频繁淘汰 key,业务缓存命中率下降,后端数据库压力又上来。
所以分配内存时,不是把每个容器压到刚好能启动,而是要给核心服务留运行空间。非核心任务可以压紧一点,比如定时清理、低频后台、临时脚本容器;核心链路别太抠。
云服务器选型时,先按内存角色估算,再看带宽和线路
买云服务器时经常先看 CPU 和带宽,但 Docker 多容器部署里,内存通常更早成为瓶颈。带宽够不够影响访问速度,内存不够会影响服务稳定性。
轻量建站、小后台、低频 API:2C2G 可以用,但容器数量要少,数据库尽量轻量或外置。
企业站、跨境业务入口、多个业务容器:4C8G 更合适,能给数据库、缓存、API 都划出边界。
游戏、DDoS 风险高、连接数多、对线路质量敏感的业务:除了内存,还要看高防、BGP、CN2、GIA、海外节点质量。内存分配解决的是机器内部资源争抢,线路和防护解决的是外部访问质量和攻击压力。
129云的产品线里,香港大宽带-C型适合带宽敏感业务,内蒙电信-A型适合建站和国内电信线路场景,韩国活动机型适合轻量海外部署。选机器时可以直接按容器内存预算反推规格,不要只按“当前访问量不大”来买。
线上调整内存限制要注意容器重建
Docker 修改内存限制,有些参数可以用 docker update 调整,有些场景还是重建容器更清晰。比如:
docker update --memory=1g --memory-swap=1g api
调整前要确认应用内部参数是否也要跟着改。Java 的 -Xmx、Redis 的 maxmemory、MySQL 的 buffer 配置,不会因为 Docker memory 变大就自动变合理。
线上改配置时,建议先看 24 小时和 7 天监控,确认峰值时段。不要在流量高峰直接压小内存限制。需要压缩内存时,先从非核心容器开始,核心服务留到低峰窗口处理。
容器内看到的内存信息可能会误导应用
早期一些运行时或应用会读取宿主机总内存,而不是 cgroup 限制。结果就是容器限制 1G,应用以为机器有 8G,然后按 8G 规划缓存或 heap,最后被 cgroup OOM。现在 Java、Go、Node.js 对容器环境支持比以前好很多,但仍然建议显式设置关键内存参数。
Java 用 -XX:MaxRAMPercentage 或直接设 -Xmx。Go 用 GOMEMLIMIT。Node.js 用 --max-old-space-size。Redis、MySQL 用自己的配置文件控制内存。不要完全依赖自动识别。
一台机器上跑多套环境,要把环境也隔开
有些云服务器上会同时跑 prod、test、dev,甚至还跑临时压测容器。这样很容易出现测试任务把生产内存吃掉。Docker 网络隔离不等于资源隔离,资源边界还是要靠 cgroups。
如果必须混跑,prod 容器要有明确硬限制,test/dev 容器限制更严格。临时任务容器一定要带 --rm、内存限制、CPU 限制,避免脚本异常后一直占资源。
例如临时数据处理容器:
docker run --rm --memory=512m --memory-swap=512m --cpus=0.5 batch-job:latest
这种写法至少能保证它不会把整台机器拖死。临时任务最怕“跑一下就删”,结果没删、没限制、日志还一直打。
内存分配示例:2C2G轻量站点
场景:一个企业官网,一个后台 API,一个 Redis 缓存,不在本机跑 MySQL。
宿主机:保留 500MB。
Nginx:--memory=256m --memory-reservation=128m。
API:--memory=768m --memory-reservation=512m,如果是 Java,-Xmx512m;如果是 Go,GOMEMLIMIT=600MiB。
Redis:--memory=512m --memory-swap=512m,Redis maxmemory 400mb。
剩余一点空间留给监控、日志波动和系统 cache。这个配置不适合高并发,也不适合本机再加 MySQL。访问量上来后,优先升级到 4G 或 8G,而不是继续往 2G 机器里挤。
内存分配示例:4C8G业务服务器
场景:Nginx 网关、两个 API 容器、MySQL、Redis、一个定时任务容器。
宿主机:保留 1G 到 1.5G。
Nginx:256MB。
API-A:1.5G,Java heap 控制在 1G 左右。
API-B:1G,Node.js old space 控制在 768MB 左右。
MySQL:2.5G,innodb_buffer_pool_size 设 1.5G 到 2G。
Redis:1G,maxmemory 设 800MB。
定时任务:512MB,跑批量任务时观察峰值。
监控和日志 Agent:各 128MB 到 256MB。
这类机器的重点是核心服务之间不要互相越界。MySQL 和 API 都是大户,Redis 要给淘汰策略,定时任务不要无限吃内存。带宽较高的香港节点上,Nginx 和 API 的连接压力也要一起看。
发生 OOM 后要看谁被杀,不要只重启服务
容器 OOM 后,业务侧常见处理是重启一下。但如果不看 OOM 记录,很容易下次继续发生。
可以执行:
dmesg -T | grep -i "killed process"
docker inspect 容器名 | grep -i OOMKilled
如果 OOMKilled 是 true,说明容器确实因为超过内存限制被杀。接下来要看容器限制是否太小,还是应用内部没有控制内存。Redis 看 maxmemory 和 key 增长,Java 看 heap 和 GC,MySQL 看 buffer、连接数、慢查询和临时表,Node.js 看 heap snapshot 或进程 RSS。
如果被杀的是宿主机上的其他进程,比如 Docker daemon、监控 Agent、sshd,那说明整机内存规划已经有问题,容器限制总和可能过高,或者某些容器根本没限制。
别把所有余量都交给 page cache,也别完全压掉 cache
Linux page cache 对性能有帮助,数据库文件、静态文件、镜像层读取都能受益。云服务器上如果完全没有 cache 空间,磁盘 IO 会更频繁,SSD 也会有压力。
但 page cache 也会让新手误判内存。free -h 里 used 很高,不一定代表进程吃满;available 很低,才更危险。Docker 容器限制的是进程可用内存,不是把整机 cache 行为完全隔离开。跑文件服务、下载站、图片站时,这一点很明显。
香港大宽带、G口大带宽服务器这类场景,文件传输和连接多,page cache、socket buffer、Nginx buffer 都会参与进来。只看容器 RSS 不够,还要看整机 available、磁盘 IO、网络连接数。
生产环境里,内存限制要和监控阈值放在一起看
给容器设了限制,还要给监控设阈值。容器内存长期超过限制的 80%,就要关注。超过 90% 并持续几分钟,基本要处理。不是等 OOM 后再看。
可以按这种方式设告警:
容器内存使用率超过 80%,持续 5 分钟,告警级别 warning。
容器内存使用率超过 90%,持续 3 分钟,告警级别 critical。
宿主机 available memory 低于总内存 10%,持续 5 分钟,告警。
swap 使用持续增长,告警。
容器 OOMKilled,立即告警。
监控数据保留一段时间后,内存分配就不是拍脑袋了。能看到哪个容器在什么时间涨,涨到哪里,是否和定时任务、发布、爬虫、活动流量有关。
真正稳定的分配方式,是让每个容器都有边界
Docker 跑在云服务器上,内存分配的关键不是把数字写得多漂亮,而是让每个服务知道自己能用多少,宿主机也有自己的空间。Nginx 这类轻服务给小一点,数据库和业务核心服务给足一点,Redis、JVM、Node.js、Go 都要配应用内部内存参数。
小机器少放角色,大机器也要限制边界。2C2G 适合轻量容器组合,4C8G 可以跑更完整的业务栈。选云服务器时,把容器内存预算算清楚,再结合带宽、线路、防护去选,香港大宽带、内蒙电信、韩国节点这类产品适合的业务位置不一样。需要按业务场景选配置,可以直接看129云的云服务器、高防服务器和海外云计算产品。
docker stats、free -h、vmstat 1、dmesg -T 这几条命令平时就要会看。等服务已经被 OOM Killer 杀掉,再去猜哪个容器抢了资源,现场会很被动。