12 KiB
Docker 容器资源限制与 OOM 异常处理
容器资源限制
默认情况下,容器没有资源限制,可以使用主机内核调度程序允许的尽可能多的给定资源。Docker 提供了控制容器可以使用多少内存或者 CPU 的方法,通过设置 docker run 命令的运行时配置标志来实现。
其中一些功能要求宿主机的内核支持 Linux 功能。要检查支持情况,可以使用 docker info 命令。如果内核中禁用了某项功能,可能会在输出结尾处看到警告。
OOM 异常
对于 Linux 主机,如果没有足够的内存来执行其他重要的系统任务,将会抛出 OOM 异常(内存溢出、内存泄漏、内存异常)。随后系统会开始杀死进程以释放内存,凡是运行在宿主机上的进程都有可能被 kill,包括 dockerd 和其他的应用程序。如果重要的系统进程被 kill,会导致和该进程相关的服务全部宕机。
产生 OOM 异常时,dockerd 会尝试通过调整 Docker 守护程序上的 OOM 优先级来减轻这些风险,以便它比系统上的其他进程更不可能被杀死。但是,容器的 OOM 优先级未调整时,单个容器被杀死的可能性更大(不推荐调整容器的优先级这种方式)。
Linux 进程 OOM 评分机制
Linux 会为每个进程计算一个分数,最终它会将分数最高的进程 kill 掉。相关文件说明如下:
/proc/PID/oom_score_adj- 范围为 -1000 到 1000。
- 值越高越容易被宿主机 kill 掉。
- 如果将该值设置为 -1000,则进程永远不会被宿主机 kernel kill。
/proc/PID/oom_adj- 范围为 -17 到 +15。
- 取值越高越容易被干掉。
- 如果是 -17,则表示不能被 kill。
- 该设置参数的存在是为了和旧版本的 Linux 内核兼容。
/proc/PID/oom_score- 这个值是系统综合进程的内存消耗量、CPU 时间(
utime + stime)、存活时间(uptime - start time)和oom_adj计算出的进程得分。 - 消耗内存越多得分越高,越容易被宿主机 kernel 强制杀死。
- 这个值是系统综合进程的内存消耗量、CPU 时间(
容器的内存限制
Docker可以强制执行硬性内存限制,即只允许容器使用给定的内存大小
Docker也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了
内存限制参数
-m 或 --memory
-
功能:限制容器最大可用内存。
-
最小值:4m。
-
示例:
docker run -m 512m my_image
--memory-swap
-
功能:限制容器可用的内存 + swap 总量。
-
前提:需先设置
--memory。 -
特殊值:
-1表示不限制 swap 使用。 -
示例:
docker run --memory 256m --memory-swap 512m my_image
--memory-swappiness
-
功能:控制容器使用 swap 的倾向性。
-
范围:0(尽量不使用 swap)到 100(尽量使用 swap)。
-
示例:
docker run --memory-swappiness 30 my_image
--kernel-memory
-
功能:限制容器使用的内核内存。
-
最小值:4m。
-
注意事项:不推荐设置,可能影响宿主机和其他容器。
-
示例:
docker run --kernel-memory 64m my_image
--memory-reservation
-
功能:设置软内存限制,低于
--memory。 -
激活条件:主机内存争用或不足时生效。
-
注意事项:必须小于
--memory,且不保证容器一定不超过此限制。 -
示例:
docker run --memory 1g --memory-reservation 512m my_image
--oom-kill-disable
-
功能:禁止 OOM 时杀死容器内进程。
-
前提:必须与
--memory一起使用。 -
注意事项:若未设置
--memory,OOM 时仍可能杀死进程。 -
示例:
docker run --memory 512m --oom-kill-disable my_image
内从限制案例
如果一个容器未作内存使用限制,则该容器可以利用到系统内存最大空间,默认创建的容器没有做内存资源限制
一、拉取容器压测工具镜像
[root@localhost ~]# docker pull tylersmith22/docker-stress-ng
[root@localhost ~]# docker run -it --rm tylersmith22/docker-stress-ng -help
二、使用压测工具开启两个工作进程,每个工作进程最大允许使用内存256M,且宿主机不限制当前容器的最大内存
[root@localhost nginx]# docker run -it --rm --name test1 tylersmith22/docker-stress-ng --vm 2 --vm-bytes 256m
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
# 新建窗口查看
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
3ca32774fc20 test1 185.16% 514.3MiB / 1.781GiB 28.21% 648B / 0B 0B / 0B 5
# 可以看到容器的内存占用大概为256的两倍,因为两个进程
三、宿主机限制最大内存使用
[root@localhost nginx]# docker run -it --rm -m 256m --name test2 tylersmith22/docker-stress-ng --vm 2 --vm-bytes 256m
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
# 新建窗口查看
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d612ce9b0776 test2 0.00% 255.9MiB / 256MiB 99.97% 1.02kB / 126B 4.83GB / 36.8GB 5
内存软限制
软限制不会真正限制到内存的使用
[root@localhost ~]# docker run -it --rm -m 256m --memory-reservation 128m --name test1 tylersmith22/docker-stress-ng --vm 2 --vm-bytes 256m
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
0ffb4b8fdbde test1 174.52% 255.9MiB / 256MiB 99.95% 648B / 0B 5.33GB / 18.1GB 5
交换分区限制
[root@localhost ~]# docker run -it --rm -m 256m --memory-swap 512m --name test1 tylersmith22/docker-stress-ng --vm 2 --vm-bytes 256m
容器的CPU限制
一个宿主机,有几十个核心的cpu,但是宿主机上可以同时运行成百上千个不同的进程用以处理不同的任务,多进程共用一个cpu的核心依赖计数就是为可压缩资源,即一个核心cpu可以通过调度而运行多个进程,但是在同一个单位时间内只能由一个进程在cpu上运行,那么这么多的进程怎么在cpu上执行和调度的呢?(进程优先级)
默认情况下,每个容器对主机cpu周期的访问权限是不受限制的,但是我们可以人为干扰
CPU限制参数
--cpus
-
功能:指定容器可以使用的 CPU 资源数量。
-
示例:主机有 2 个 CPU,设置
--cpus=1.5,容器最多可使用 1.5 个 CPU。 -
示例命令:
docker run --cpus 1.5 my_image
--cpu-period 和 --cpu-quota
-
功能:设置 CPU 调度周期和配额。
-
关系:必须一起使用,计算方式为
cpu-quota / cpu-period。 -
示例:
docker run --cpu-period 100000 --cpu-quota 50000 my_image
--cpuset-cpus
-
功能:指定容器运行的 CPU 编号(绑核)。
-
示例:限制容器在 CPU 0 和 CPU 1 上运行。
-
示例命令:
docker run --cpuset-cpus 0,1 my_image
--cpuset-mems
-
功能:设置容器使用的内存节点(仅对 NUMA 架构有效)。
-
示例:限制容器使用内存节点 0。
-
示例命令:
docker run --cpuset-mems 0 my_image
--cpu-shares
-
功能:设置容器的 CPU 时间片权重。
-
默认值:1024。
-
范围:最小 2,最大 262144。
-
示例:容器 A 设置为 1024,容器 B 设置为 2048,容器 B 的 CPU 时间片是容器 A 的两倍。
-
示例命令:
docker run --cpu-shares 2048 my_image
CPU限制案例
未限制容器cpu
- 启动1个进程,占用4核cpu,未限制容器会把cpu全部占完
# 查看我们宿主机的cup数量
[root@localhost ~]# top
top - 21:32:49 up 43 min, 2 users, load average: 3.54, 1.82, 0.80
Tasks: 186 total, 1 running, 185 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni, 99.5 id, 0.0 wa, 0.5 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
[root@localhost ~]# docker run -it --rm --name test1 tylersmith22/docker-stress-ng --vm 1 --cpu 4
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 4 cpu, 1 vm
# 新建窗口查看
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
c6a795e4c09f test1 396.78% 282.6MiB / 1.703GiB 16.21% 876B / 126B 0B / 0B 7
# 可以看出CPU的使用率大概是400% 因为我们是4个核心,单个核心跑满是100%
限制容器cpu
[root@localhost ~]# docker run -it --rm --cpus 2 --name test1 tylersmith22/docker-stress-ng --vm 1 --cpu 4
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 4 cpu, 1 vm
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
5b7dcb36d738 test1 200.65% 279.7MiB / 1.703GiB 16.04% 876B / 126B 0B / 0B 7
[root@localhost ~]# top
top - 21:36:15 up 47 min, 3 users, load average: 1.38, 1.92, 1.05
Tasks: 198 total, 8 running, 190 sleeping, 0 stopped, 0 zombie
%Cpu0 : 50.7 us, 1.4 sy, 0.0 ni, 47.2 id, 0.0 wa, 0.7 hi, 0.0 si, 0.0 st
%Cpu1 : 52.9 us, 0.0 sy, 0.0 ni, 47.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 50.7 us, 0.0 sy, 0.0 ni, 48.6 id, 0.0 wa, 0.7 hi, 0.0 si, 0.0 st
%Cpu3 : 50.4 us, 0.0 sy, 0.0 ni, 48.9 id, 0.0 wa, 0.7 hi, 0.0 si, 0.0 st
# 并且是平均使用所有的cup核心
将容器运行到指定的cpu上
[root@localhost ~]# docker run -it --rm --cpus 2 --cpuset-cpus 0,2 --name test1 tylersmith22/docker-stress-ng --vm 1 --cpu 4
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
ee11d834dde5 test1 186.68% 1.488GiB / 1.781GiB 83.60% 648B / 0B 44.8GB / 95.7MB 25
[root@localhost ~]# top
top - 21:38:25 up 49 min, 3 users, load average: 0.92, 1.40, 0.96
Tasks: 197 total, 6 running, 191 sleeping, 0 stopped, 0 zombie
%Cpu0 : 97.3 us, 2.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.7 hi, 0.0 si, 0.0 st
%Cpu1 : 0.3 us, 0.0 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu2 : 98.3 us, 1.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.7 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.3 hi, 0.0 si, 0.0 st
MiB Mem : 1743.4 total, 457.2 free, 924.2 used, 531.4 buff/cache
MiB Swap: 2048.0 total, 2047.2 free, 0.8 used. 819.2 avail Mem
- 基于cpu-shares对cpu进行切分
[root@localhost ~]# docker run -it --rm -d --cpus 1 --cpuset-cpus 0 --cpu-shares 1024 --name test1 tylersmith22/docker-stress-ng --cpu 1
[root@localhost ~]# docker run -it --rm -d --cpus 1 --cpuset-cpus 0 --cpu-shares 2048 --name test2 tylersmith22/docker-stress-ng --cpu 1
[root@localhost ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d6dd34edb722 test1 543.41% 819.6MiB / 1.781GiB 44.95% 648B / 0B 102MB / 154MB 13
154b07a94e2f test2 241.15% 711.1MiB / 1.781GiB 39.00% 648B / 0B 406MB / 145MB