Docker容器默认没有资源上限,一个失控的容器可以吃光宿主机的CPU和内存,进而影响同一台机器上的其他服务。CentOS作为国内使用量很大的服务器系统,其内核默认开启的cgroups功能正好是Docker实现资源限制的底层基础。本文将从参数含义、配置命令、动态调整和常见问题四个方面,详细介绍如何在CentOS上对容器做CPU、内存等资源限制。

一、资源限制的底层机制:cgroups
Docker本身并不实现资源隔离,真正干活的是Linux内核的控制组(Control Groups,简称cgroups)。cgroups可以对一组进程的CPU时间、内存、块设备IO、网络等资源进行限额和统计。Docker在启动容器时,会在/sys/fs/cgroup目录下为容器创建对应的cgroup节点,把容器内所有进程都挂到这个节点上,资源限制参数最终会写入该节点的配置文件。
CentOS 7默认使用cgroups v1,各个资源控制器(cpu、memory、blkio等)各自独立成树;CentOS 8及之后的版本内核支持cgroups v2,Docker较新版本也可以切换使用。两者在目录结构上有差异,但Docker命令层面是屏蔽掉的,用户只需要关注docker run的参数即可。可以通过下面的命令确认宿主机的cgroups版本:
# 查看cgroups版本,输出cgroup2fs表示v2,tmpfs表示v1 stat -fc %T /sys/fs/cgroup/ # 查看当前系统支持的资源控制器 cat /proc/cgroups
理解这一点很重要,因为当资源限制行为和预期不符时,往往需要到cgroups文件系统里核对实际写入的值。比如容器ID前12位对应的目录下,cpu.cfs_quota_us和cpu.cfs_period_us的比值就决定了容器能使用的CPU核数上限。
二、CPU限制:shares、quota和cpuset三种方式
Docker提供三种角度的CPU控制手段,含义各不相同,容易混淆。--cpu-shares设置的是相对权重,默认值1024。它只在CPU竞争激烈时才起作用:假如两个容器的shares分别是1024和512,那么争抢时前者能拿到大约三分之二的CPU时间。宿主机空闲时,shares为512的容器同样可以跑满CPU,这一点很多人理解有误。
--cpus(对应--cpu-period和--cpu-quota)是硬性上限,比如--cpus=1.5表示容器最多使用1.5个核的计算能力,无论宿主机是否空闲都不能突破。--cpuset-cpus则是把容器绑定到指定的CPU核心上运行,适合需要避免进程在核心间迁移、减少缓存失效的场景。三种方式可以组合使用,示例命令如下:
# 相对权重限制,竞争时按2:1分配 docker run -d --name web1 --cpu-shares=1024 nginx:alpine # 硬限制最多使用1.5核 docker run -d --name web2 --cpus=1.5 nginx:alpine # 绑定到第0和第2个核心 docker run -d --name web3 --cpuset-cpus=0,2 nginx:alpine # 组合使用:绑定核心且最多用1核 docker run -d --name web4 --cpuset-cpus=0 --cpus=1 nginx:alpine
验证CPU限制是否生效,可以在容器内跑一个死循环压测,同时在宿主机用top命令观察。如果容器配了--cpus=0.5,那么无论压测多猛,容器进程的CPU占用率都不会超过50%。需要注意,宿主机本身要留出足够的CPU给系统进程和其他容器,把所有核心都绑给容器是常见的错误做法。
三、内存限制与OOM行为处理
内存限制的核心参数是-m或--memory,例如-m 512m表示容器最多使用512MB物理内存。与之配套的--memory-swap含义稍微绕一点:它限制的是内存加交换分区的总量。如果只设置-m 512m而不设置swap,Docker默认--memory-swap等于内存的两倍,即容器还能额外使用512MB的swap。若想彻底禁用swap,需要显式指定--memory-swap=512m,让swap可用量等于0。
# 限制内存512MB,且不允许使用swap docker run -d --name app1 \ -m 512m \ --memory-swap 512m \ --memory-reservation 256m \ myapp:latest
其中--memory-reservation是一个软限制,当宿主机内存紧张时,内核会尽量把容器压到这个水位以下,它不保证强制执行,但适合作为保护性配置。当容器实际用内存超过硬限制时,内核的OOM Killer会杀掉容器内占用内存最多的进程,容器随之退出,docker inspect可以看到OOMKilled字段为true。排查这类问题不能一味调大限制,应先用docker stats观察容器的真实内存曲线,判断是应用泄漏还是限制值设置过低。
四、运行中容器的动态调整与监控
线上容器往往不能随意重启,Docker提供了docker update命令来动态修改运行中容器的资源限制,支持内存、CPU、blkio权重等参数。这在扩容场景非常实用,比如发现某个容器内存吃紧,可以直接把上限从512MB提到1GB:
# 动态调整运行中容器的限制
docker update --memory 1g --memory-swap 1g app1
docker update --cpus 2 app1
# 查看当前所有容器的资源占用
docker stats --no-stream
# 查看某个容器的详细限制值
docker inspect app1 --format '{{.HostConfig.Memory}} {{.HostConfig.NanoCpus}}'
需要注意docker update修改内存时只能调大不能调小到低于当前实际占用,否则命令会报错。日常监控方面,docker stats输出的CPU百分比是相对宿主机总核数的,一个8核机器上限制为2核的容器,压满时显示约250%而不是100%,读数据时要换算清楚。对于块设备IO,还可以用--device-read-bps、--blkio-weight等参数限制读写速率和权重,避免某个日志类容器把磁盘带宽占满。综合运用这些参数,再配合容器编排工具统一管理,就能在CentOS上构建出资源分配清晰、互不干扰的容器运行环境。