Docker 资源管理终极指南:CPU、内存与 I/O 的精细化控制
在 Docker 容器化部署中,除了镜像的构建与推送,资源管理 和容器的生命周期控制是保障服务稳定运行的关键。如果不对容器进行资源限制,某个"失控"的容器可能会耗尽宿主机的所有硬件资源(如 CPU 100% 或内存溢出),导致其他正常业务的容器无法运行甚至宿主机宕机。
本文将详细介绍 Docker 如何通过 Cgroups 进行资源配额限制,涵盖 CPU、内存、I/O 三大方面,以及如何使用 --restart 策略实现容器的自动恢复。
🛠️ 一、Docker 资源管理介绍
Docker 通过 Linux 内核的 Cgroups (Control Groups) 技术来控制容器使用的资源配额。主要包括以下三大方面:
- CPU:限制容器可以使用的 CPU 时间片或核心数。
- 内存:限制容器可以使用的最大内存空间。
- 磁盘 I/O:限制容器对磁盘的读写速度。
这基本覆盖了常见的资源配额和使用量控制场景,为每个容器划定了"安全边界"。
🔄 二、容器启动管理(重启策略)
为了保证容器运行时的健壮性 和自愈能力 ,Docker 提供了容器重启策略。通过 docker run 时的 --restart 参数,可以让容器在退出时自动尝试重启。
1. 重启策略详解
| 策略值 | 说明 |
|---|---|
| no | 默认策略。守护进程启动时不自动重新启动容器。无论容器是因为错误退出还是正常停止,都不会自动拉起。 |
| on-failure:max-retries | 仅当容器以非零状态 (即异常退出)退出时才重新启动。可选参数 [:max-retries] 用于限制 Docker 守护进程尝试的重启重试次数。 |
| always | 无条件重启。无论容器的当前状态如何,只要 Docker 守护进程启动,容器就会启动;容器退出后也会立即重启。 |
| unless-stopped | 类似于 always,但在容器退出时总是重启容器,除了一种情况:如果 Docker 守护进程启动之前,该容器就已经处于停止状态,那么守护进程启动后不会自动拉起它。 |
2. 策略实战演示
以下实验基于 CentOS 7 环境,镜像为 centos:7.7.1908。
(1) --restart=no (默认策略)
这是最基础的模式,容器挂了就是挂了,Docker 不会管它。
bash
# 启动两个容器,显式指定 no 策略
[root@ ~]# docker run -it --name c1 centos:7.7.1908 /bin/bash
[root@ ~]# docker run -it --name c2 --restart=no centos:7.7.1908 /bin/bash
# 查看容器配置确认策略(通常在输出结果的 42 行左右)
[root@ ~]# docker inspect c2
# 重启 Docker 服务模拟宿主机重启
[root@ ~]# systemctl restart docker
docker container prune -f
# 查看容器状态,发现 c1 和 c2 均处于 Exited 状态,未自动启动
[root@ ~]# docker ps -a
# 清理环境:同时删除所有的容器
[root@ ~]# docker rm $(docker ps -aq)
(2) --restart=on-failure (异常退出才重启)
这个策略常用于后台服务。如果是人为停止(exit 0),它不会重启;如果是程序崩溃(exit 1),它会重启。
bash
# 启动容器
[root@ ~]# docker run -it --name c1 --restart=on-failure centos:7.7.1908 /bin/bash
[root@ ~]# docker run -it --name c2 --restart=on-failure centos:7.7.1908 /bin/bash
# 场景 A:强行关闭或者杀死容器(模拟异常/信号中断)
[root@ ~]# docker stop c1 # 发送 SIGTERM,通常视为正常停止,视版本而定可能不重启
[root@ ~]# docker kill c2 # 发送 SIGKILL,强制杀死,属于异常退出
# 场景 B:正常退出容器
[root@ ~]# docker attach c1
[root@0b09a28ac8 /]# exit # 输入 exit 正常退出 shell
# 重启 Docker 服务
[root@ ~]# systemctl restart docker
[root@ ~]# docker ps -a
# 观察结果:正常退出的容器通常不会自动重启,而被 kill 的容器可能会根据具体信号处理逻辑决定是否重启(通常 on-failure 针对非 0 退出码)。
(3) --restart=always (始终重启)
这是生产环境中最常用的策略之一,确保服务永远在线。
bash
# 启动容器
[root@ ~]# docker run -it --name c1 --restart=always centos:7.7.1908 /bin/bash
# 即使你手动停止了它
[root@ ~]# docker stop c1
# 只要重启 Docker 服务,它就会"复活"
[root@ ~]# systemctl restart docker
[root@ ~]# docker ps -a
# 结果:c1 的状态会变成 Up (Running),因为它被配置为总是启动。
(4) --restart=unless-stopped (除非手动停止)
这个策略比 always 更智能一点。如果你明确想要维护停机,重启服务器后它不会意外启动。
bash
# 启动容器
[root@ ~]# docker run -it --name c1 --restart=unless-stopped centos:7.7.1908 /bin/bash
# 尝试正常退出和停止容器
[root@ ~]# docker stop c1
# 重启 Docker 服务
[root@ ~]# systemctl restart docker
[root@ ~]# docker ps -a
# 结果:因为你在 Docker 重启前手动 stop 了它,所以重启后它依然保持 Exited 状态。
(5) 更新正在运行的容器策略
如果你忘记在启动时设置策略,或者想修改现有容器的行为,不需要删除重建,直接使用 update 命令即可。
bash
# 查看容器的策略
docker inspect c1 | grep "RestartPolicy" -C 3 -n
# -C :查看指定的行数
# -n :显示行号
# 将容器 c1 的策略更新为 always
[root@ ~]# docker update --restart=always c1
💻 三、Docker CPU 控制与压力测试
1. stress 工具介绍
stress 是一个 Linux 系统压力测试工具,可以对系统的 CPU、内存、I/O 等资源进行负载测试。在 Docker 环境中,我们常用它来验证容器的资源限制是否生效。
安装 stress 工具:
bash
# 安装 EPEL 源(stress 工具在 EPEL 仓库中)
[root@ ~]# yum -y install epel-release
# 安装 stress 工具
[root@ ~]# yum -y install stress
stress 常用参数:
| 参数 | 说明 |
|---|---|
-c n |
产生 n 个进程,每个进程反复调用 sqrt() 计算平方根,用于测试 CPU |
-v |
显示程序运行的详细信息 |
--timeout |
设置 stress 程序运行的时长,如 60s |
-m n |
产生 n 个进程,每个进程不断分配内存,用于测试内存 |
2. Docker CPU 权重(cpu-shares)详解
- CPU 权重(
--cpu-shares) 用于设置容器在 CPU 时间片分配时的相对权重。 - 默认值 :每个 Docker 容器的 CPU 权重默认值为 1024。
- 重要特性 :
- 只有当多个容器在同一 CPU 核心上竞争资源时,权重设置才有意义。
- 如果只有一个容器运行,无论权重设为多少,它都会使用全部可用 CPU 资源。
- 权重是相对值,不是绝对百分比。
权重分配示例:
假设有容器 A(权重 1000)和容器 B(权重 500):
- 满负荷竞争:A 和 B 都在高负载运行,CPU 时间片分配比例为 A:B = 2:1。A 约占 66.6%,B 约占 33.3%。
- 非满负荷:如果 A 容器基本不占用 CPU 资源,B 容器可以获得更多 CPU 资源,甚至接近 100%。
3. 实战演示:CPU 权重与核心绑定测试
实验目标 :创建两个容器 docker10 和 docker20,限制它们只能使用 CPU0 和 CPU1,设置不同权重,验证 CPU 资源分配比例。
bash
# 启动 docker10,权重设为 512
[root@ ~]# docker run -itd --name docker10 \
--cpuset-cpus 0,1 \
--cpu-shares 512 \
centos:7.7.1908 /bin/bash
# 启动 docker20,权重设为 1024
[root@ ~]# docker run -itd --name docker20 \
--cpuset-cpus 0,1 \
--cpu-shares 1024 \
centos:7.7.1908 /bin/bash
验证容器 CPU 配置:
bash
# 进入 docker10 容器
[root@ ~]# docker attach docker10
# 查看容器可使用的 CPU 核心
[root@9a6b595ea361 /]# cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-1
# 查看容器 CPU 权重设置
[root@9a6b595ea361 /]# cat /sys/fs/cgroup/cpu/cpu.shares
512
安装 stress 并进行压力测试:
分别在两个容器中安装 stress 并运行压力测试:
bash
# 在 docker10 中执行
[root@9a6b595ea361 /]# yum -y install epel-release stress
[root@9a6b595ea361 /]# stress -c 2 -v --timeout 120s
# 在 docker20 中执行
[root@ ~]# docker attach docker20
[root@7f958975be83 /]# yum -y install epel-release stress
[root@7f958975be83 /]# stress -c 2 -v --timeout 120s
在宿主机观察 CPU 使用率:
bash
top
htop
预期结果:
docker10(权重 512)约占 CPU 总资源的 33.3%docker20(权重 1024)约占 CPU 总资源的 66.6%- 两者比例约为 1:2,与权重比 512:1024 一致。
动态修改容器 CPU 权重:
Docker 支持在容器运行期间动态更新 CPU 权重:
bash
# 将 docker20 的权重从 1024 修改为 2048
docker update docker20 --cpu-shares 2048
修改后权重比例变为:docker10 (512) : docker20 (2048) = 1:4。
💾 四、Docker 内存与 I/O 限制
1. 限制内存使用的极限
通过 -m (或 --memory) 参数,我们可以严格限制容器可以使用的最大物理内存量。如果容器尝试使用超过该限制的内存,内核的 OOM Killer 机制将会介入,终止容器内的进程。
启动一个限制内存为 256MB 的容器:
bash
docker run -it -m 256m --name docker30 centos:7.7.1908 /bin/bash
验证内存限制是否生效:
进入容器内部,查看 Cgroups 配置文件来确认限制是否已应用:
bash
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
预期输出: 268435456 (即 256 MB)。
压力测试:模拟内存溢出
在容器内安装并使用 stress 工具来进行压力测试:
bash
# 安装 stress 工具
yum -y install epel-release stress
# 执行内存压力测试
# -m 2: 产生 2 个进程,每个进程不断调用 malloc() 分配内存
stress -m 2 -v --timeout 60s
当分配的内存总量超过 256MB 时,容器内的进程会被系统强制杀死(OOM Killed),或者容器直接退出。
2. 综合资源限制:CPU 绑定、权重与内存
在实际生产环境中,我们通常需要同时管理 CPU 和内存。Docker 允许在一个命令中组合多种资源限制参数。
bash
docker run -itd \
--name docker40 \
--cpuset-cpus 0,1 \
--cpu-shares 512 \
-m 1024m \
centos:7.7.1908 \
/bin/bash
参数深度解析:
| 参数 | 值 | 含义与作用 |
|---|---|---|
--cpuset-cpus |
0,1 |
CPU 绑定(硬限制) 。强制该容器只能在宿主机的 第 0 号 和 第 1 号 CPU 核心上运行。 |
--cpu-shares |
512 |
CPU 权重(软限制)。默认值为 1024。设置为 512 意味着:当 CPU 资源发生争抢时,该容器获得的 CPU 时间片只有默认容器的一半。 |
-m |
1024m |
内存限制。限制该容器最大只能使用 1GB (1024MB) 的物理内存。 |
3. Docker 资源管理之 I/O 控制
硬盘的读写速度是有限的共享资源。Docker 提供了 --device-read-bps 和 --device-write-bps 参数,允许我们精确限制容器对特定设备的读写速率(Bytes Per Second)。
测试容器默认 I/O 读写速度:
bash
# 启动一个普通容器
docker run -it centos:7.7.1908 /bin/bash
# 在容器内执行 dd 命令测试写入速度
time dd if=/dev/zero of=file1 bs=1M count=10
#查看命令
iotop # 关注的某个进程读写磁盘
iostat -dmx 2 #关注util磁盘负载使用率
预期输出示例: 速度可能达到 1.1 GB/s,因为数据写入了内存缓存。
启动容器并限制 I/O 读写速度:
为了真实地测试磁盘 I/O 限制,我们需要绕过内存缓存,使用 Direct I/O(直接 I/O) 模式。
bash
# 启动容器,限制 /dev/sda 的写入速度为 1MB/s
docker run -it --device-write-bps /dev/sda:1mb centos:7.7.1908 /bin/bash
# 在容器内执行 dd 命令,使用 oflag=direct 跳过缓存
time dd if=/dev/zero of=file1 bs=1M count=10 oflag=direct
预期输出示例: 速度将被限制在 1.1 MB/s 左右,证明 I/O 限制策略已生效。
容器自动释放资源 (--rm):
在开发或测试场景中,可以使用 --rm 参数让容器在退出后自动删除。
bash
docker run -itd --rm --name c1 centos:7.7.1908 sleep 30
💡 五、总结与运维小贴士
- Cgroups 资源限制 是防止单个容器拖垮整个系统的关键。
- Restart Policy 是实现服务高可用(HA)的基础手段。
- stress 工具 是验证容器资源限制的有效手段。
- CPU 权重(cpu-shares) 只在多个容器竞争 CPU 资源时生效,是相对比例而非绝对限制。
- 关于 Swap :默认情况下,Docker 允许容器使用等同于内存限制大小的 Swap 空间。如果你希望完全禁止 容器使用 Swap,可以添加参数
--memory-swap=256m(使其等于内存限制值)。 - 生产环境建议 :对于关键业务容器,建议始终配置
-m限制,并结合使用--restart=unless-stopped或always来部署。