Docker容器怎么限制CPU和内存?防止一个容器吃光VPS所有资源

Docker容器默认可以用VPS全部CPU和内存,一个内存泄漏就能拖死整台机器。本文给出内存硬限制软限制、CPU权重配额绑定、OOM排查和Docker Compose配置方法。雨云优惠码admin01五折。
发布于
11

在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.resourcesdocker-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。这样内存偶尔短暂超限不会触发重启。

如何确定合理的资源限制值

不是拍脑袋设一个值就完事。需要基于实际观测数据:

  1. 先不设限制跑一段时间:docker stats 持续观察容器的CPU和内存使用模式
  2. 记录正常负载下的内存峰值:比如MySQL在业务高峰期最高用到380MB
  3. 留20-30%的余量设置硬限制:380MB × 1.3 ≈ 500MB,设置 --memory="512m"
  4. 软限制设为正常值:--memory-reservation="384m"
  5. 持续观察,按需调整:如果频繁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资源限制配置属于常规的服务器运维操作。服务器的使用应遵守《网络安全法》《数据安全法》等法律法规。

常见问题(FAQ)

Docker容器内存限制和JVM堆内存限制有什么区别?
Docker的--memory限制是容器整体物理内存上限(含JVM堆、堆外内存、Metaspace和OS开销)。JVM的-Xmx只限制堆内存。两者需要配合使用:容器限制通常比-Xmx大30-50%,留足非堆内存空间。Java 10+可以用-XX:+UseContainerSupport自动感知容器限制。
设置内存限制后容器被OOM杀了怎么办?
用docker inspect查看OOMKilled状态确认确实是被OOM杀的。然后根据docker stats观察内存使用峰值,提高--memory限制值。如果内存泄漏是代码问题,需要修复代码而不是一味提高限制。配合--restart=on-failure:N防止无限重启。
CPU限制用--cpus还是--cpu-shares?
--cpus是硬限制(容器最多用几个核),--cpu-shares是相对权重(只在CPU争抢时生效)。计算密集型容器用--cpus防止吃光CPU,Web服务通常用--cpu-shares让多个服务合理分享CPU。
雨云VPS的KVM架构支持cgroup资源限制吗?
支持。雨云KVM云服务器完整支持cgroup v1和v2,Docker的--memory、--cpus等限制参数可以精确生效。轻量虚拟化方案可能有cgroup限制,购买时选择KVM架构即可。优惠码admin01新用户五折。

本文由作者原创/授权发布于极跃圈(jiyueip.com)未经许可,禁止转载。题图来自Unsplash,基于CC0协议。

声明:极跃圈(JIYUEIP.com)内网友所发表的所有内容及言论仅代表其本人,并不反映任何极跃圈(JIYUEIP.com)之意见及观点。

0 / 600
0 讨论
热门最新
总结
暂无总结

暂无数据