在VPS上跑Docker,最怕的局面不是服务挂了,是一个容器的内存泄漏把整个机器拖死——MySQL因为一个慢查询撑到2GB、Node.js的内存泄漏慢慢涨到1.5GB、Java应用的堆内存不受控制。默认情况下Docker容器可以使用宿主机的全部CPU和内存,一个出问题的容器可以吃掉所有资源,导致SSH都连不上。
本文给出Docker容器资源限制的完整配置方法:内存硬限制和软限制、CPU权重和绑定、重启策略和健康检查的配合使用。雨云优惠码admin01新用户五折,KVM云服务器完整支持cgroup,资源限制配置可以精确生效。
默认情况下Docker容器没有资源限制
用 docker stats 可以实时看到所有容器的资源占用:
docker stats --no-stream
输出会显示每个容器的CPU百分比、内存使用量和内存上限。如果MEM USAGE / LIMIT那一列显示类似 1.2GiB / 3.8GiB,那个3.8GiB的LIMIT是宿主机的总内存,不是容器的限制。说明这个容器没有被限制,可以一直占用到宿主机内存耗尽。
Docker的资源限制依赖Linux cgroup(控制组),在启动容器时通过参数指定。雨云的KVM云服务器完整支持cgroup v1和v2,轻量虚拟化(如OpenVZ)可能有限制。购买前确认是KVM架构即可。
内存限制:硬限制和软限制
Docker的内存限制有两个参数,作用不同:
--memory(硬限制):容器能使用的最大物理内存。超过后容器会被OOM Killer杀掉。这是最常用的限制方式--memory-reservation(软限制):一个建议值,当宿主机内存紧张时,Docker会尝试把容器的内存使用压到这个值以下。正常情况下容器可以超过这个值
一块配的例子:
# MySQL容器:硬限制512MB,软限制384MB
docker run -d
--name mysql
--memory="512m"
--memory-reservation="384m"
-e MYSQL_ROOT_PASSWORD=yourpassword
mysql:8.0
这个配置下:MySQL正常运行时可以用到512MB。如果VPS内存紧张,Docker会尝试把MySQL压到384MB。如果MySQL超过512MB,直接被OOM Killer杀掉。
内存单位:m表示MB,g表示GB,k表示KB。也可以写字节数如 --memory="536870912"(512MB)。
Docker Compose中的内存限制
在 docker-compose.yml 中配置:
services:
mysql:
image: mysql:8.0
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 384M
注意 deploy.resources 在 docker-compose up 中会被忽略,只在 docker stack deploy(Swarm模式)中生效。如果只用 docker-compose up,用旧版语法:
services:
mysql:
image: mysql:8.0
mem_limit: 512m
mem_reservation: 384m
CPU限制:权重、绑定和配额
CPU限制比内存复杂,有三种方式:
方式一:CPU权重(相对限制)
docker run -d --name app1 --cpu-shares=512 nginx
docker run -d --name app2 --cpu-shares=1024 nginx
--cpu-shares 是一个相对权重,默认值是1024。上面两个容器:当CPU争抢时,app2分到的CPU时间是app1的两倍。但如果只有app1在跑,它可以用全部CPU——权重只在竞争时才生效。
方式二:CPU配额(绝对限制)
# 限制容器最多使用1.5个CPU核心
docker run -d --name app --cpus="1.5" nginx
--cpus 是硬限制,容器在任何情况下都不能超过指定的CPU核心数。1.5表示最多使用一个半核心的计算能力。适合限制计算密集型容器。
方式三:绑定CPU核心
# 绑定到第0和第1号CPU核心
docker run -d --name app --cpuset-cpus="0,1" nginx
--cpuset-cpus 指定容器只能在哪些CPU核心上运行。适用于NUMA架构的服务器(把容器绑定到同一个NUMA节点的核心上以获得更好的内存访问性能),或者隔离噪音邻居(把批处理任务绑定到单独的核心,不干扰在线服务)。
Docker Compose中的CPU限制
services:
app:
image: nginx
cpus: "1.5"
cpu_shares: 512
cpuset: "0,1"
重启策略:配合资源限制使用
设置了内存限制后,容器可能因为超限被OOM杀掉。这时候需要合理的重启策略:
docker run -d
--name app
--memory="256m"
--restart=on-failure:5
nginx
--restart=on-failure:5 表示容器异常退出时最多自动重启5次。如果容器因为内存超限反复被杀和重启,5次后Docker放弃重启——避免进入无限重启循环。
更安全的做法是配合健康检查:
docker run -d
--name app
--memory="256m"
--restart=on-failure:3
--health-cmd="curl -f http://localhost:8080/health || exit 1"
--health-interval=30s
--health-retries=3
--health-timeout=5s
your-app
健康检查每30秒执行一次,连续3次失败才标记为unhealthy。这样内存偶尔短暂超限不会触发重启。
如何确定合理的资源限制值
不是拍脑袋设一个值就完事。需要基于实际观测数据:
- 先不设限制跑一段时间:用
docker stats持续观察容器的CPU和内存使用模式 - 记录正常负载下的内存峰值:比如MySQL在业务高峰期最高用到380MB
- 留20-30%的余量设置硬限制:380MB × 1.3 ≈ 500MB,设置
--memory="512m" - 软限制设为正常值:
--memory-reservation="384m" - 持续观察,按需调整:如果频繁OOM,提高限制;如果长期只用一半,可以降低
对于Java应用(Spring Boot、Minecraft服务端等),注意JVM的堆内存是独立于容器内存限制的。JVM默认看到的是宿主机的总内存,需要用 -Xmx 参数手动限制堆大小:
# 容器限制512MB,JVM堆限制384MB(留128MB给堆外内存和Metaspace)
docker run -d
--memory="512m"
-e JAVA_OPTS="-Xmx384m -Xms256m"
your-java-app
Java 10+可以使用 -XX:+UseContainerSupport 让JVM自动感知容器的内存限制。
监控容器资源使用
配置好限制后,持续监控确认值设得合理:
# 实时监控所有容器
docker stats
# 查看容器的详细资源使用(包括限制值和实际使用)
docker stats --no-stream --format "table {{.Name}}t{{.CPUPerc}}t{{.MemUsage}}t{{.MemPerc}}"
更完整的方式是用Prometheus + Grafana或Uptime Kuma监控资源趋势。如果一台VPS上跑5个以上的容器,不看趋势很难发现哪个容器在慢慢泄漏内存。
OOM发生后怎么排查
如果容器被OOM Killer杀了,检查内核日志:
dmesg | grep -i "out of memory"
dmesg | grep -i oom
或者用 docker inspect 查看容器的退出状态:
docker inspect 容器名 --format='{{.State.OOMKilled}}'
返回 true 表示这个容器是被OOM Killer杀掉的。查看容器的内存限制:
docker inspect 容器名 --format='{{.HostConfig.Memory}}'
返回的数值单位是字节。除以1048576就是MB。
一个VPS上跑多个容器的总内存规划
假设你的雨云VPS有2GB内存,跑以下服务:
- 系统开销(SSH、systemd等):约200MB
- MySQL:512MB限制
- Node.js应用:256MB限制
- Nginx:128MB限制
- Redis:256MB限制
- 剩余可用:约650MB
总计限制值 = 512 + 256 + 128 + 256 = 1152MB,加上系统200MB = 1352MB,在2GB的范围内有约650MB的缓冲。这个缓冲很重要——Docker本身、日志、文件缓存都需要内存。
一个常见错误:把所有容器的限制值加起来刚好等于总内存。这会导致系统缓冲空间被压缩,IO性能大幅下降。建议所有容器的限制值总和不超过总内存的70-80%。
合规声明
本文所述的Docker资源限制配置属于常规的服务器运维操作。服务器的使用应遵守《网络安全法》《数据安全法》等法律法规。






