Docker 资源管理终极指南:CPU、内存与 IO 的精细化控制

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 权重与核心绑定测试

实验目标 :创建两个容器 docker10docker20,限制它们只能使用 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

💡 五、总结与运维小贴士

  1. Cgroups 资源限制 是防止单个容器拖垮整个系统的关键。
  2. Restart Policy 是实现服务高可用(HA)的基础手段。
  3. stress 工具 是验证容器资源限制的有效手段。
  4. CPU 权重(cpu-shares) 只在多个容器竞争 CPU 资源时生效,是相对比例而非绝对限制。
  5. 关于 Swap :默认情况下,Docker 允许容器使用等同于内存限制大小的 Swap 空间。如果你希望完全禁止 容器使用 Swap,可以添加参数 --memory-swap=256m(使其等于内存限制值)。
  6. 生产环境建议 :对于关键业务容器,建议始终配置 -m 限制,并结合使用 --restart=unless-stoppedalways 来部署。
相关推荐
xiaoye-duck1 小时前
《Linux系统编程》Linux 系统多线程(五):<线程同步与互斥>线程互斥(上):从条件变量到生产者消费者模型详解
linux·线程
小柯南敲键盘2 小时前
图片翻译API接入与自动化实现指南
运维·python·自动化
云计算磊哥@3 小时前
运维开发宝典058-大型网站nginx服务器管理全集4
服务器·nginx·运维开发
952363 小时前
Docker - 基础
运维·后端·docker·容器
Promise微笑3 小时前
智能激光清障仪选型指南:高效运维与安全保障的关键考量
运维·安全
Code_Ignis3 小时前
docker国内可用源(持续维护)
运维·docker·容器
一叶龙洲3 小时前
向日葵远程Ubuntu,支持隐私屏
linux·运维·ubuntu
酷可达拉斯4 小时前
Linux操作系统-shell编程之Grep与正则表达式
linux·运维·服务器·正则表达式·centos
寒水馨4 小时前
Linux下载、安装llama.cpp-b10068(附安装包llama-b10068-bin-ubuntu-vulkan-x64.tar.gz)
linux·ubuntu·llm·llama·本地部署·llama.cpp·推理引擎