资源限制

容器资源限制概述

  • 官方文档:https://docs.docker.com/config/containers/resource_constraints/
  • 默认情况下,容器没有对资源的使用限制,即在某些情况下,可以将宿主机的资源耗尽
  • Docker 提供了控制容器使用资源的方法,可以限制容器对内存、cpu等资源的使用量,可以在运行docker run 命令时配合特定的参数加以限制

前期准备

  • 容器中资源限制的很多功能都要求宿主机的内核支持
  • 要检查是否支持这些功能,可以使用docker info命令查看是否有警告信息
  • 官方文档:https://docs.docker.com/engine/install/linux-postinstall/#your-kernel-does-not-support-cgroup-swap-limit-capabilities

范例:

root@Ubuntu2004:~# docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Build with BuildKit (Docker Inc., v0.6.3-docker)
  scan: Docker Scan (Docker Inc., v0.17.0)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 77
 Server Version: 20.10.10
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 2a1d4dbdb2a1030dc5b01e96fb110a9d9f150ecc
 runc version: v1.0.3-0-gf46b6ba
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.18.0-240.el8.x86_64
 Operating System: CentOS Linux 8
 OSType: linux
 Architecture: x86_64
 CPUs: 4
 Total Memory: 3.81GiB
 Name: docker
 ID: RWES:NEXJ:23MD:CYNE:RAHI:45N2:NEJZ:RY42:SHA7:MI4F:XNLL:TUZF
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  10.0.0.100
  10.0.0.101
  127.0.0.0/8
 Registry Mirrors:
  https://jqm0rnhf.mirror.aliyuncs.com/
 Live Restore Enabled: false
 
 WARNING: No swap limit support #无交换限制支持,这样会影响容器运行的性能

解决办法

  • 修改内核参数来消除以上警告
root@Ubuntu2004:~# vim /etc/default/grub
...
#添加或编辑该GRUB_CMDLINE_LINUX行以添加 cgroup_enable=memory swapaccount=1 这两个键值对:
GRUB_CMDLINE_LINUX="net.ifnames=0 cgroup_enable=memory swapaccount=1 biosdevname=0;"
...

#更新GRUB
root@Ubuntu2004:~# update-grub

#禁用swap挂载
root@Ubuntu2004:~# vim /etc/fstab
...
#/swap.img   none    swap    sw  0   0
...

#重启使其生效
root@Ubuntu2004:~# reboot

#再次查看,可以看到无WARNING了

OOM

  • Out Of Memory(内存溢出、内存泄漏、内存异常)
  • 对于Linux主机,如果没有足够的内存,则系统则会抛出OOM,从而kill掉内存量使用最大的进程,而内存使用量较大的进程通常都是生产中重要进程,如Java、MySQL等…
  • 产生 OOM 异常时,dockerd会尝试通过调整docker守护进程上的OOM优先级来减轻这些风险,以便它比系统上的其他进程更不可能被杀死,但是容器的OOM优先级未调整,这使得单个容器被杀死的可能性比docker守护程序或其他系统进程被杀死的可能性更大
  • 不推荐通过在守护进程或容器上手动设置–oom-sorce-adj为极端负数,或通过在容器上设置–oom-kill-disable来绕过这些安全措施

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+存活时间(uptime - start time))和 oom_adj 计算出来的进程得分,消耗内存越多得分越高,则越容易被宿主机内核kill掉

OOM 优先级范例:

# pidof vim
3310
# pidof dockerd 
953

# cat /proc/3310/oom_score_adj 
0
# cat /proc/953/oom_score_adj 
-500

# cat /proc/3310/oom_adj 
0
# cat /proc/953/oom_adj 
-8

# cat /proc/3310/oom_score 
669
# cat /proc/953/oom_score 
366

容器的内存限制

  • docker内存限制分为两种:
    • 硬性内存限制,即只允许容器使用给定的内存大小
    • 非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了

内存限制相关选项

  • 官方文档:https://docs.docker.com/config/containers/resource_constraints/
  • 这些选项中的大多数采用正整数,后跟b, k, m, g, 后缀来表示字节、千字节、兆字节或千兆字节。
  • 常用选项:
选项 说明
-m 或 –memory= 容器可以使用的最大内存量。如果设置此选项,则允许的最小值为6m(6 兆字节)。也就是说,您必须将该值设置为至少 6 兆字节。
–oom-kill-disable 默认情况下,如果发生内存不足 (OOM) 错误,内核会终止容器中的进程。要更改此行为,请使用该--oom-kill-disable选项。仅在您还设置了该-m/--memory选项的容器上禁用 OOM kill。如果-m未设置该标志,主机可能会耗尽内存,内核可能需要终止主机系统的进程以释放内存。

范例:

未做限制前

  • 显示的是宿主机的内存和使用量
[root@docker ~]# docker run --name test -it --rm ubuntu:20.04

root@1162cadbad75:/# free  -h
              total        used        free      shared  buff/cache   available
Mem:          3.8Gi       378Mi       2.9Gi       9.0Mi       512Mi       3.2Gi
Swap:            0B          0B          0B

[root@docker ~]# docker stats --no-stream test 
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
d9463c55ffa2   test      0.00%     2.059MiB / 3.81GiB   0.05%     976B / 0B   0B / 0B     1

做限制后

  • -m 100m,限制使用量为100m
[root@docker ~]# docker run --name test -it --rm -m 100m ubuntu:20.04

[root@docker ~]# docker stats --no-stream test 
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O     BLOCK I/O   PIDS
f81449e7ee62   test      0.00%     2.445MiB / 100MiB   2.45%     766B / 0B   0B / 0B     1

容器的cpu限制

  • 一个核心的CPU可以通过调度(上下文切换)而运行多个进程,但是同一个单位时间内只能有一个进程在CPU上运行。

  • Linux kernel 进程的调度基于CFS(Completely Fair Scheduler)完全公平调度

  • 官方文档:https://docs.docker.com/config/containers/resource_constraints/

cpu限制相关选项

  • 配置默认的CFS调度程序

  • 常用选项:

选项 描述
–cpus= 指定容器可以使用多少可用 CPU 资源。例如,如果主机有两个 CPU,并且您设置--cpus="1.5"了 ,则容器最多可以保证一个半的 CPU。这相当于设置--cpu-period="100000"--cpu-quota="150000"。(假设有4个cpu,并且设置了–cpus=“2”,则表示将利用每个cpu的百分之五十(雨露均沾,而不是盯住两个cpu来进行限制))
–cpuset-cpus 限制容器可以使用的特定 CPU 或内核。如果您有多个 CPU,则容器可以使用的逗号分隔列表或连字符分隔的 CPU 范围。第一个 CPU 编号为 0。有效值可能是0-3(使用第一个、第二个、第三个和第四个 CPU)或1,3(使用第二个和第四个 CPU)。

压力测试工具 stress-ng

官方镜像

docker pull lorel/docker-stress-ng

测试cpu限制

相关选项

[root@docker ~]# docker run -it --rm --name test-cpu lorel/docker-stress-ng|grep cpu
 -c N, --cpu N            start N workers spinning on sqrt(rand())
       --cpu-ops N        stop when N cpu bogo operations completed
 -l P, --cpu-load P       load CPU by P %%, 0=sleep, 100=full load (see -c)
       --cpu-method m     specify stress cpu method m, default is all
Example: stress-ng --cpu 8 --io 4 --vm 2 --vm-bytes 128M --fork 4 --timeout 10s

宿主机CPU概况

[root@docker ~]# lscpu |grep CPU
CPU op-mode(s):      32-bit, 64-bit
CPU(s):              4
On-line CPU(s) list: 0-3
CPU family:          23
CPU MHz:             2095.977
NUMA node0 CPU(s):   0-3

未做限制前测试

#使用三颗cpu
[root@docker ~]# docker run -it --rm --name test-cpu lorel/docker-stress-ng --cpu 3
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 3 cpu

#可以看到使用了284.61%的CPU,也就相当于3颗cpu(每颗cpu100%)
[root@docker ~]# docker stats --no-stream test-cpu 
CONTAINER ID   NAME       CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
672fd8f10064   test-cpu   284.61%   14.65MiB / 3.81GiB   0.38%     906B / 0B   0B / 0B     4

#查看容器进程的cpu使用相关文件
[root@docker ~]# cat /sys/fs/cgroup/cpuset/docker/c5888748a888d58834e7d4249c1cf1017b0fad690de256f915baa036e07ffd12/cpuset.cpus 
0-3 #可以使用0-3号cpu,也就是4颗cpu

[root@docker ~]# top
top - 00:36:38 up  4:11,  2 users,  load average: 2.01, 1.70, 0.81
Tasks: 189 total,   5 running, 184 sleeping,   0 stopped,   0 zombie
%Cpu0  : 93.9 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  6.1 hi,  0.0 si,  0.0 st
%Cpu1  : 93.9 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  6.1 hi,  0.0 si,  0.0 st
%Cpu2  : 95.8 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  4.2 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

做限制后测试

#使用三颗cpu,但限制使用一颗(也支持浮点数,如:--cpus 1.5)
[root@docker ~]# docker run -it --rm --name test-cpu --cpus 1 lorel/docker-stress-ng --cpu 3
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 3 cpu


[root@docker ~]# docker stats --no-stream test-cpu 
CONTAINER ID   NAME       CPU %     MEM USAGE / LIMIT    MEM %     NET I/O       BLOCK I/O   PIDS
7e62901a2ad1   test-cpu   115.60%   12.75MiB / 3.81GiB   0.33%     1.12kB / 0B   0B / 0B     4


[root@docker ~]# top
top - 00:42:48 up  4:18,  2 users,  load average: 0.06, 1.01, 0.87
Tasks: 186 total,   4 running, 182 sleeping,   0 stopped,   0 zombie
%Cpu0  : 29.8 us,  0.0 sy,  0.0 ni, 67.7 id,  0.0 wa,  2.5 hi,  0.0 si,  0.0 st
%Cpu1  : 29.7 us,  0.3 sy,  0.0 ni, 68.2 id,  0.0 wa,  1.7 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.0 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
%Cpu3  : 30.0 us,  0.0 sy,  0.0 ni, 68.6 id,  0.0 wa,  1.4 hi,  0.0 si,  0.0 st

测试cpu绑定

  • 一般不建议将进程绑定在0号cpu上,因为0号cpu通常比较忙
#限制使用1颗cpu,并只绑定在1和3号cpu上
[root@docker ~]# docker run -it --rm --name test-cpu --cpus 1 --cpuset-cpus 1,3 lorel/docker-stress-ng --cpu 3
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 3 cpu


#查看绑定结果
[root@docker ~]# top
top - 00:52:01 up  4:27,  2 users,  load average: 0.92, 0.80, 0.92
Tasks: 182 total,   5 running, 177 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.7 hi,  0.0 si,  0.0 st
%Cpu1  : 50.0 us,  0.0 sy,  0.0 ni, 47.0 id,  0.0 wa,  3.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.3 us,  0.0 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
%Cpu3  : 49.8 us,  0.0 sy,  0.0 ni, 46.8 id,  0.0 wa,  3.0 hi,  0.3 si,  0.0 st