八:Docker 资源限制
【参考】 - Runtime options with Memory, CPUs, and GPUs
官方文档:https://docs.docker.com/config/containers/resource_constraints/
By default, a container has no resource constraints and can use as much of a given resource as the host’s kernel scheduler allows. Docker provides ways to control how much memory, or CPU a container can use, setting runtime configuration flags of the docker run command. This section provides details on when you should set such limits and the possible implications of setting them.
Many of these features require your kernel to support Linux capabilities. To check for support, you can use the docker info command. If a capability is disabled in your kernel, you may see a warning at the end of the output like the following:
默认情况下,容器没有资源限制,可以使用系统中的所有资源。
docker 可以通过 docker run 的选项参数来限制容器的内存、cpu、磁盘 io 使用量。
其中许多功能都要求内核支持 Linux capabilities 机制(Kernel 2.2 之后引入的机制)。 要检查是否支持,可以使用 docker info 命令。 如果内核中禁用了某项功能,可能会在输出结尾处看到警告,如下所示:
root@ubuntu:~# docker info
……
WARNING: No swap limit support
8.1:内存限制
8.1.1:OOM 的概念
对于 Linux 主机,如果没有足够的内存来执行重要的系统任务,将会抛出 OOM (Out of Memory Exception,内存溢出、内存泄漏、内存异常), 随后系统会开始 kill 掉某些进程以达到释放内存的目的。
每个进程都有可能被 kill,包括 Dockerd,如果重要的系统进程被 Kill 掉,就会导致整个系统宕机。
OOM 优先级机制:
Linux 会为每个进程计算一个分数(score),最终它会将分数最高的进程 kill 掉。
产生 OOM 异常时,Docker 尝试通过调整 Dockerd 守护进程的 OOM 优先级来减轻这些风险,以便它比系统上的其它进程更可能不被 kill 掉。
如果容器进程的 OOM 优先级未调整,就会使得这个个容器进程被 kill 掉的可能性比 Dockerd 守护进程或其它系统进程更大。
不推荐通过在守护进程(dockerd --oom-score-adjust int)或容器进程(docker run --oom-score-adj int)手动设置 oom_score_adj 为极端负数,或通过在容器进程上设置 --oom-kill-disable 来绕过这些安全措施。
8.1.2:OOM 相关运行参数
收集相关 PID,为接下来的查看验证做准备
root@ubuntu:~# pstree -p 1
containerd 的进程树:
dockerd 的进程树:
/proc/PID/oom_score_adj
范围为 -1000 到 1000,值越高越容易被 kill 掉;
如果将该值设为 -1000,则进程永远不会被内核 kill 掉;
也就意味着,值越小,进程的运行优先权限越高;
dockerd 进程的 oom_score_adj 默认为 -500:
root@ubuntu:~# cat /proc/1086/oom_score_adj -500
containerd 的 oom_score_adj 默认为 -999:
root@ubuntu:~# cat /proc/728/oom_score_adj -999
containerd-shim 容器进程的 oom_score_adj 默认为 -998:
root@ubuntu:~# cat /proc/2131/oom_score_adj -998
Docker 容器内的 1 号进程的 oom_score_adj 默认为 0:
root@ubuntu:~# cat /proc/2156/oom_score_adj 0
/proc/PID/oom_adj
范围为 -17 到 +15,取值越高越容易被 kill 掉;
如果是 -17,则表示不能被 kill,该设置参数的存在是为了和旧版本的 lInux 内核兼容。
dockerd 进程的 oom_adj 默认为 -8:
root@ubuntu:~# cat /proc/1086/oom_adj -8
containerd 的 oom_adj 默认为 -16:
root@ubuntu:~# cat /proc/728/oom_adj -16
containerd-shim 容器进程的 oom_adj 默认为 -16:
root@ubuntu:~# cat /proc/2131/oom_adj -16
Docker 容器内的 1 号进程的 oom_adj 默认为 0:
root@ubuntu:~# cat /proc/2156/oom_adj 0
/proc/PID/oom_score
这个值是系统综合进程的内存消耗量、CPU 时间(uptime+stime)、存活时间(uptime - start time)和 oom_adj 计算出的进程得分;
消耗内存越多得分越高,越容易被 kernel kill 掉。
dockerd、containerd、containerd-shim 进程的 oom_score 值均为 0:
root@ubuntu:~# cat /proc/1086/oom_score 0 root@ubuntu:~# cat /proc/728/oom_score 0 root@ubuntu:~# cat /proc/2131/oom_score 0
Docker 容器内的 1 号进程的 oom_score 为 1:
root@ubuntu:~# cat /proc/2156/oom_score 1
8.1.3:容器内存限制的相关参数
相关参数的值保存在:/sys/fs/cgroup/memory/docker/容器ID/;
memory.limit_in_bytes
容器的物理内存硬限制;
memory.soft_limit_in_bytes
容器的物理内存软限制;
memory.memsw.limit_in_bytes
容器的交换分区限制;
memory.oom_control
OOM 的控制参数;
8.1.4:docker run 内存限制参数
Docker 可以强制执行硬性内存限制,即只允许容器使用给定的内存大小。
Docker 也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了。
Most of these options take a positive integer, followed by a suffix of b, k, m, g, to indicate bytes, kilobytes, megabytes, or gigabytes.
-m or --memory=
容器可以使用的最大物理内存量(最小值为 4MB);
此值为硬限制。
–memory-swap*
设置容器可以使用的交换分区大小(swap 限制参数 --memory-swap 只有在设置了 -m / --memory
后才会有意义);
使用 Swap 可以让容器将超出限制部分的内存交换到磁盘上。
WARNING:经常将内存交换到磁盘的应用程序会降低性能。
不同的 --memory-swap 设置会产生不同的效果:
-
值为正数, 那么 --memory 和 --memory-swap 都必须要设置;
–memory-swap 表示能使用的内存和 swap 分区大小的总和;例如: --memory=300m,–memory-swap=1g,那么该容器能够使用 300m 物理内存和 700m swap;
可用 swap 分区的计算方式为:(–memory-swap) - (–memory) -
值为 0,则忽略该设置,并将该值视为未设置,即未设置交换分区。
-
如果等于 --memory 的值,并且 --memory 的值为正整数,则容器无权访问 swap;
因为:可用 swap = (–memory-swap) - (–memory) ,如果二者相等,相当于可用 swap 为 0;
-
如果设置为 unset,并且宿主机开启了 swap,则容器的可用 swap 值为 2x( --memory),即两倍于物理内存大小。
-
值为 -1,并且宿主机开启了 swap,则容器可以使用主机上 swap 的最大空间。
–memory-swappiness
设置容器使用交换分区的倾向性,值越高表示越倾向于使用 swap 分区;
范围为 0-100,0 表示尽量不用 swap 分区,100 表示尽量使用 swap 分区;
–memory-reservation
用于指定的软限制(该值要小于 --memory 指定的硬限制内存值);
当 Docker 检测到主机上的内存争用或内存不足时会激活该限制;
如果使用–memory-reservation,则必须将其设置为低于–memory 的值,这样才能优先使用该限制;
因为它是软限制,所以不能保证容器不超过限制。
这就是为什么 --memory-reservation 要小于 --memory,虽然无法保证容器的内存使用不超过软限制,但超过后还有一个硬限制;
–kernel-memory
容器可以使用的最大内核内存量(最小值为 4MB);
由于内核空间的内存无法进行 swap 交换,因此内核内存不足的容器可能会阻塞宿主机资源,这会对主机和其它容器产生负面影响。
–oom-kill-disable
默认情况下,发生 OOM 时,kernel 会 kill 掉容器内的进程来释放内存,但可以使用 --oom-kill-disable 参数,来禁止 OOM killer;
仅在已设置 -m / --memory
选项的容器上禁止 OOM killer;
因为如果未设置
-m / --memory
选项,宿主机会产生 OOM(因为容器没有内存限制),为了释放内存,可能会 kill 掉系统进程来释放内存,这就比较危险了;
所以前提是必须设置了-m / --memory
来进行内存硬限制;
8.1.5:内存限制测试
假如一个容器未做内存资源限制,则该容器可以使用系统内存的最大空间,默认创建的容器都没有做内存资源限制。
拉取 lorel/docker-stress-ng 镜像
压测工具为 stress-ng:
stress-ng will stress test a computer system in various selectable ways. It was designed to exercise various physical subsystems of a computer as well as the various operating system kernel interfaces. stress-ng also has a wide range of CPU specific stress tests that exercise floating point, integer, bit manipulation and control flow.
stress-ng was originally intended to make a machine work hard and trip hardware issues such as thermal overruns as well as operating system bugs that only occur when a system is being thrashed hard. Use stress-ng with caution as some of the tests can make a system run hot on poorly designed hardware and also can cause excessive system thrashing which may be difficult to stop.
The tool has a wide range of different stress mechanisms (known as “stressors”) and a full description of these is included in the man page. This document is a quick-start reference guide and covers some of the more typical use cases for stress-ng.
这里使用 stress-ng 的镜像,启动 Docker 容器进行压测;
官方文档:https://kernel.ubuntu.com/~cking/stress-ng/
拉取 stress-ng 的 Docker 镜像:
root@ubuntu:~# docker pull lorel/docker-stress-ng
查看使用帮助:
root@ubuntu:~# docker run -it --rm lorel/docker-stress-ng --help
stress-ng, version 0.03.11
Usage: stress-ng [OPTION [ARG]]
--h, --help show help
--affinity N start N workers that rapidly change CPU affinity
--affinity-ops N stop when N affinity bogo operations completed
--affinity-rand change affinity randomly rather than sequentially
--aio N start N workers that issue async I/O requests
--aio-ops N stop when N bogo async I/O requests completed
--aio-requests N number of async I/O requests per worker
-a N, --all N start N workers of each stress test
-b N, --backoff N wait of N microseconds before work starts
-B N, --bigheap N start N workers that grow the heap using calloc()
--bigheap-ops N stop when N bogo bigheap operations completed
--bigheap-growth N grow heap by N bytes per iteration
--brk N start N workers performing rapid brk calls
--brk-ops N stop when N brk bogo operations completed
--brk-notouch don't touch (page in) new data segment page
--bsearch start N workers that exercise a binary search
--bsearch-ops stop when N binary search bogo operations completed
--bsearch-size number of 32 bit integers to bsearch
-C N, --cache N start N CPU cache thrashing workers
--cache-ops N stop when N cache bogo operations completed (x86 only)
--cache-flush flush cache after every memory write (x86 only)
--cache-fence serialize stores
--class name specify a class of stressors, use with --sequential
--chmod N start N workers thrashing chmod file mode bits
--chmod-ops N stop chmod workers after N bogo operations
-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
-D N, --dentry N start N dentry thrashing processes
--dentry-ops N stop when N dentry bogo operations completed
--dentry-order O specify dentry unlink order (reverse, forward, stride)
--dentries N create N dentries per iteration
--dir N start N directory thrashing processes
--dir-ops N stop when N directory bogo operations completed
-n, --dry-run do not run
--dup N start N workers exercising dup/close
--dup-ops N stop when N dup/close bogo operations completed
--epoll N start N workers doing epoll handled socket activity
--epoll-ops N stop when N epoll bogo operations completed
--epoll-port P use socket ports P upwards
--epoll-domain D specify socket domain, default is unix
--eventfd N start N workers stressing eventfd read/writes
--eventfd-ops N stop eventfd workers after N bogo operations
--fault N start N workers producing page faults
--fault-ops N stop when N page fault bogo operations completed
--fifo N start N workers exercising fifo I/O
--fifo-ops N stop when N fifo bogo operations completed
--fifo-readers N number of fifo reader processes to start
--flock N start N workers locking a single file
--flock-ops N stop when N flock bogo operations completed
-f N, --fork N start N workers spinning on fork() and exit()
--fork-ops N stop when N fork bogo operations completed
--fork-max P create P processes per iteration, default is 1
--fstat N start N workers exercising fstat on files
--fstat-ops N stop when N fstat bogo operations completed
--fstat-dir path fstat files in the specified directory
--futex N start N workers exercising a fast mutex
--futex-ops N stop when N fast mutex bogo operations completed
--get N start N workers exercising the get*() system calls
--get-ops N stop when N get bogo operations completed
-d N, --hdd N start N workers spinning on write()/unlink()
--hdd-ops N stop when N hdd bogo operations completed
--hdd-bytes N write N bytes per hdd worker (default is 1GB)
--hdd-direct minimize cache effects of the I/O
--hdd-dsync equivalent to a write followed by fdatasync
--hdd-noatime do not update the file last access time
--hdd-sync equivalent to a write followed by fsync
--hdd-write-size N set the default write size to N bytes
--hsearch start N workers that exercise a hash table search
--hsearch-ops stop when N hash search bogo operations completed
--hsearch-size number of integers to insert into hash table
--inotify N start N workers exercising inotify events
--inotify-ops N stop inotify workers after N bogo operations
-i N, --io N start N workers spinning on sync()
--io-ops N stop when N io bogo operations completed
--ionice-class C specify ionice class (idle, besteffort, realtime)
--ionice-level L specify ionice level (0 max, 7 min)
-k, --keep-name keep stress process names to be 'stress-ng'
--kill N start N workers killing with SIGUSR1
--kill-ops N stop when N kill bogo operations completed
--lease N start N workers holding and breaking a lease
--lease-ops N stop when N lease bogo operations completed
--lease-breakers N number of lease breaking processes to start
--link N start N workers creating hard links
--link-ops N stop when N link bogo operations completed
--lsearch start N workers that exercise a linear search
--lsearch-ops stop when N linear search bogo operations completed
--lsearch-size number of 32 bit integers to lsearch
-M, --metrics print pseudo metrics of activity
--metrics-brief enable metrics and only show non-zero results
--memcpy N start N workers performing memory copies
--memcpy-ops N stop when N memcpy bogo operations completed
--mmap N start N workers stressing mmap and munmap
--mmap-ops N stop when N mmap bogo operations completed
--mmap-async using asynchronous msyncs for file based mmap
--mmap-bytes N mmap and munmap N bytes for each stress iteration
--mmap-file mmap onto a file using synchronous msyncs
--mmap-mprotect enable mmap mprotect stressing
--msg N start N workers passing messages using System V messages
--msg-ops N stop msg workers after N bogo messages completed
--mq N start N workers passing messages using POSIX messages
--mq-ops N stop mq workers after N bogo messages completed
--mq-size N specify the size of the POSIX message queue
--nice N start N workers that randomly re-adjust nice levels
--nice-ops N stop when N nice bogo operations completed
--no-madvise don't use random madvise options for each mmap
--null N start N workers writing to /dev/null
--null-ops N stop when N /dev/null bogo write operations completed
-o, --open N start N workers exercising open/close
--open-ops N stop when N open/close bogo operations completed
-p N, --pipe N start N workers exercising pipe I/O
--pipe-ops N stop when N pipe I/O bogo operations completed
-P N, --poll N start N workers exercising zero timeout polling
--poll-ops N stop when N poll bogo operations completed
--procfs N start N workers reading portions of /proc
--procfs-ops N stop procfs workers after N bogo read operations
--pthread N start N workers that create multiple threads
--pthread-ops N stop pthread workers after N bogo threads created
--pthread-max P create P threads at a time by each worker
-Q, --qsort N start N workers exercising qsort on 32 bit random integers
--qsort-ops N stop when N qsort bogo operations completed
--qsort-size N number of 32 bit integers to sort
-q, --quiet quiet output
-r, --random N start N random workers
--rdrand N start N workers exercising rdrand instruction (x86 only)
--rdrand-ops N stop when N rdrand bogo operations completed
-R, --rename N start N workers exercising file renames
--rename-ops N stop when N rename bogo operations completed
--sched type set scheduler type
--sched-prio N set scheduler priority level N
--seek N start N workers performing random seek r/w IO
--seek-ops N stop when N seek bogo operations completed
--seek-size N length of file to do random I/O upon
--sem N start N workers doing semaphore operations
--sem-ops N stop when N semaphore bogo operations completed
--sem-procs N number of processes to start per worker
--sendfile N start N workers exercising sendfile
--sendfile-ops N stop after N bogo sendfile operations
--sendfile-size N size of data to be sent with sendfile
--sequential N run all stressors one by one, invoking N of them
--sigfd N start N workers reading signals via signalfd reads
--sigfd-ops N stop when N bogo signalfd reads completed
--sigfpe N start N workers generating floating point math faults
--sigfpe-ops N stop when N bogo floating point math faults completed
--sigsegv N start N workers generating segmentation faults
--sigsegv-ops N stop when N bogo segmentation faults completed
-S N, --sock N start N workers doing socket activity
--sock-ops N stop when N socket bogo operations completed
--sock-port P use socket ports P to P + number of workers - 1
--sock-domain D specify socket domain, default is ipv4
--stack N start N workers generating stack overflows
--stack-ops N stop when N bogo stack overflows completed
-s N, --switch N start N workers doing rapid context switches
--switch-ops N stop when N context switch bogo operations completed
--symlink N start N workers creating symbolic links
--symlink-ops N stop when N symbolic link bogo operations completed
--sysinfo N start N workers reading system information
--sysinfo-ops N stop when sysinfo bogo operations completed
-t N, --timeout N timeout after N seconds
-T N, --timer N start N workers producing timer events
--timer-ops N stop when N timer bogo events completed
--timer-freq F run timer(s) at F Hz, range 1000 to 1000000000
--tsearch start N workers that exercise a tree search
--tsearch-ops stop when N tree search bogo operations completed
--tsearch-size number of 32 bit integers to tsearch
--times show run time summary at end of the run
-u N, --urandom N start N workers reading /dev/urandom
--urandom-ops N stop when N urandom bogo read operations completed
--utime N start N workers updating file timestamps
--utime-ops N stop after N utime bogo operations completed
--utime-fsync force utime meta data sync to the file system
-v, --verbose verbose output
--verify verify results (not available on all tests)
-V, --version show version
-m N, --vm N start N workers spinning on anonymous mmap
--vm-bytes N allocate N bytes per vm worker (default 256MB)
--vm-hang N sleep N seconds before freeing memory
--vm-keep redirty memory instead of reallocating
--vm-ops N stop when N vm bogo operations completed
--vm-locked lock the pages of the mapped region into memory
--vm-method m specify stress vm method m, default is all
--vm-populate populate (prefault) page tables for a mapping
--wait N start N workers waiting on child being stop/resumed
--wait-ops N stop when N bogo wait operations completed
--zero N start N workers reading /dev/zero
--zero-ops N stop when N /dev/zero bogo read operations completed
Example: stress-ng --cpu 8 --io 4 --vm 2 --vm-bytes 128M --fork 4 --timeout 10s
Note: Sizes can be suffixed with B,K,M,G and times with s,m,h,d,y
未进行内存限制的容器
测试:未进行内存限制的容器可以利用到系统内存的最大空间;
查看宿主机内存:
1G 物理内存,2G 交换分区;
root@ubuntu:~# free -m
total used free shared buff/cache available
Mem: 961 249 338 10 374 559
Swap: 2047 0 2047
启动 2 个 vm,每个 vm 占用 256M 物理内存:
这时能用到物理内存的大约 50%;
root@ubuntu:~# docker run -it --rm lorel/docker-stress-ng --vm 2 --vm-bytes 256M
另一个终端验证内存使用情况:
root@ubuntu:~# docker stats
启动 4 个 vm,每个 vm 占用 256M 物理内存:
这时会将物理内存耗尽,因为除了容器进程还有有其它进程,还会占用到一部分的 swap 分区;
root@ubuntu:~# docker run -it --rm lorel/docker-stress-ng --vm 4 --vm-bytes 256M
另一个终端验证内存使用情况:
docker stats 已经卡到无法正常显示;
root@ubuntu:~# free -m
total used free shared buff/cache available
Mem: 961 845 72 2 44 23
Swap: 2047 558 1489
容器的物理内存硬限制
测试:在进行内存硬限制的情况下,即使容器要使用比限制更多的内存,也会被限制在指定值以下;
限制容器使用内存为 256M,同时启动 4 个 vm,每个 vm 占用 256M 物理内存(共需要 1024M 内存):
root@ubuntu:~# docker run -it --rm -m 256m lorel/docker-stress-ng --vm 4 --vm-bytes 256M
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
提示内核没有开启 swap 限制机制,所以本次内存限制不包括 swap 交换分区;这个从后面的 free -m 的结果中可以看出;
这个提示可以通过更改 /etc/default/grub 的启动参数、update-grub,并重启系统来解决,上边有详细说明;
另一终端验证内存限制:
物理内存被限制在了 256M;
root@ubuntu:~# docker stats
由于没有 swap 限制,所以 swap 分区会被使用一部分:
root@ubuntu:~# free -m
total used free shared buff/cache available
Mem: 961 468 247 2 245 352
Swap: 2047 968 1079
动态修改容器的物理内存限制
查看物理内存限制参数的值:
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/a1575adf8ccd2ee61d2972e66f9b052c6c52a22d159118d285dd1f7e9da7616a/memory.limit_in_bytes
268435456
root@ubuntu:~# bc
268435456/1024/1024
256
可以直接修改 memory.limit_in_bytes
的值来更改容器的物理内存限制:
因为 stress-ng 容器当前将 256M 物理内存全部占满,内存正在使用,所以这时动态减小物理内存限制会报
write error: Device or resource busy
的错误;加大内存限制是没有问题的;
如果容器空闲就可以减小物理内存限制;
root@ubuntu:~# bc
128*1024*1024
134217728
root@ubuntu:~# echo 134217728 > /sys/fs/cgroup/memory/docker/a1575adf8ccd2ee61d2972e66f9b052c6c52a22d159118d285dd1f7e9da7616a/memory.limit_in_bytes
-bash: echo: write error: Device or resource busy
root@ubuntu:~# bc
512*1024*1024
536870912
root@ubuntu:~# echo 536870912 > /sys/fs/cgroup/memory/docker/a1575adf8ccd2ee61d2972e66f9b052c6c52a22d159118d285dd1f7e9da7616a/memory.limit_in_bytes
验证动态修改的物理内存限制:
root@ubuntu:~# docker stats
容器内存的物理内存软限制
测试:即使容器指定了软限制,但当容器内存需求量大于此值时,仍然会占用更多的内存,只是不会超过硬限制的数值;
启动 2 个 vm,每个 vm 占用 256M 内存,容器内存软限制为 128M,硬限制为 256M:
root@ubuntu:~# docker run -it --rm -m 256m --memory-reservation 128m lorel/docker-stress-ng --vm 2 --vm-bytes 256m
另一终端验证内存限制:
容器实际使用内存达到 256M,超过软限制限定的 128M;
root@ubuntu:~# docker stats
查看 memory.soft_limit_in_bytes 的值:
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/3bc8defefc336b8c42aea0de68e03b130f57c4bb7b4fbaf8d0e152614930ec92/memory.soft_limit_in_bytes
134217728
root@ubuntu:~# bc
134217728/1024/1024
128
容器的交换分区限制
测试:限制容器的物理内存为 256M,swap 交换分区为 128M(-m 256M --memory-swap 384M),容器中启动超过总限制(384M)的 vm,查看 swap 交换分区的限制是否生效;
启动 3 个 vm,每个 vm 占用 256M 内存,限制物理内存 256M,swap 128M:
root@ubuntu:~# docker run -it --rm -m 256m --memory-swap 384m lorel/docker-stress-ng --vm 3 --vm-bytes 256M
另一终端验证 swap 限制:
docker stats 显示容器物理内存使用量已达到限制的最大值 256M:
root@ubuntu:~# docker stats
free -m 查看 swap 分区的使用情况:
swap 交换分区的使用量被限制在 169M,之所以不是设置的 128M 是因为还有其它进程可能会用到 swap;
root@ubuntu:~# free -m
total used free shared buff/cache available
Mem: 961 514 96 11 350 294
Swap: 2047 169 1878
查看 memory.memsw.limit_in_bytes 的值:
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/1023b6b6725e571078eedb297c6da38e3a18342d7785fd2f295530ce90d72914/memory.memsw.limit_in_bytes
402653184
root@ubuntu:~# bc
402653184/1024/1024
384
K8s 1.8.3更新日志:
https://github.com/kubernetes/kubernetes/blob/release-1.8/CHANGELOG-1.8.md
关闭容器的 oom-kill 机制
使用 --oom-kill-disable 启动容器:
使用 --oom-kill-disable 时,必须限制容器的内存(-m/–memory);
root@ubuntu:~# docker run -it --rm -m 256m --oom-kill-disable lorel/docker-stress-ng --vm 3 --vm-bytes 256M
查看 memory.oom_control :
root@ubuntu:~# cat /sys/fs/cgroup/memory/docker/31a1c65604ff23b59f08006a5fcf980140dca4478b748c2ba4d980dc14741d28/memory.oom_control
oom_kill_disable 1
under_oom 1
oom_kill 0
8.2:CPU 限制
进程的优先级关系到进程使用 CPU 资源的优先级。
进程优先级的调整策略:
- CPU 密集型的场景:优先级越低越好,计算密集型任务的特点是要进行大量的计算,消耗 CPU 资源,比如计算圆周率、对视频进行高清解码等等,全靠 CPU 的运算能力;
- IO密集型的场景:优先级值高点,涉及到网络、磁盘 IO 的任务都是 IO 密集型任务,这类任务的特点是 CPU 消耗很少,任务的大部分时间都在等待 IO 操作完成(因为 IO 的速度远远低于 CPU 和内存的速度),比如 Web 应用,高并发,数据量大的动态网站来说,数据库应该为 IO 密集型。
磁盘调度算法:
root@ubuntu:~# cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
磁盘算法会严重影响磁盘 IO,CentOS7 有 3 个,CentOS6 有 4 个;
默认情况下,每个容器对宿主机 CPU 周期的访问权限是不受限制的,但是我们可以设置各种约束来限制给定容器访问主机的 CPU 周期,大多数用户使用的是默认的 CFS 调度方式,在Docker 1.13及更高版本中,还可以配置实时优先级。
CFS(Completely Fair Scheduler),完全公平调度。
8.2.1:Linux 进程优先级
参考:https://www.cnblogs.com/lcword/p/8267342.html
在同一个调度周期中,优先级高的进程占用 CPU 的时间长些,而优先级低的进程占用的短些。
进程优先级由 nice(NI) 和 priority(PR) 控制:
ps -l:
root@ubuntu:~# ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S 0 1569 1276 0 80 0 - 5722 wait pts/0 00:00:00 bash 0 R 0 2971 1569 0 80 0 - 7229 - pts/0 00:00:00 ps
top:
top - 14:53:13 up 29 min, 3 users, load average: 3.91, 5.92, 4.29 Tasks: 168 total, 1 running, 93 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.3 us, 0.7 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 985064 total, 557852 free, 228176 used, 199036 buff/cache KiB Swap: 2097148 total, 2071036 free, 26112 used. 607380 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2972 root 20 0 44088 3972 3328 R 0.7 0.4 0:00.05 top 1276 root 20 0 105692 3304 2328 S 0.3 0.3 0:00.36 sshd 2106 root 20 0 0 0 0 I 0.3 0.0 0:02.99 kworker/0:0 1 root 20 0 77668 5184 3068 S 0.0 0.5 0:01.99 systemd
nice
nice 是反应一个进程“优先级”状态的值,其取值范围是-20至19,一共40个级别。
默认情况下,子进程的 nice 值是从父进程继承来的,这个值一般是 0。
nice 值可以影响进程的优先级 PRI;
在英语中,如果我们形容一个人nice,那一般说明这个人的人缘比较好。什么样的人人缘好?往往是谦让、有礼貌的人。
比如,你跟一个nice的人一起去吃午饭,点了两个一样的饭,先上了一份后,nice的那位一般都会说:“你先吃你先吃!”,这就是人缘好,这人nice!但是如果另一份上的很晚,那么这位nice的人就要饿着了。
这说明什么?
越nice的人抢占资源的能力就越差,而越不nice的人抢占能力就越强。这就是nice值大小的含义,nice值越低,说明进程越不nice,抢占cpu的能力就越强,优先级就越高
查看当前 bash 的 nice 值:
root@ubuntu:~# nice
0
另外打开一个 nice 值为 10 的 bash:
root@ubuntu:~# nice -n 10 bash
root@ubuntu:~# nice
10
#退出
root@ubuntu:~# exit
exit
root@ubuntu:~#
使用 renice 可以调整进程的 nice 值;
进程优先级的取值
在内核中,进程优先级的取值范围是通过一个宏定义的,这个宏的名称是 MAX_PRIO,它的值为 140。
而这个值又是由另外两个值相加组成的:
- 一个是代表 nice 值取值范围的 NICE_WIDTH 宏;
- 另一个是代表实时进程 (realtime) 优先级范围的 MAX_RT_PRIO 宏;
Linux 实际上实现了140 个优先级范围,取值范围是从 0-139,这个值越小,优先级越高。
nice 值的 -20 到 19,映射到实际的优先级范围是 100-139。
新产生进程的默认优先级被定义为:
- #define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
实际上对应的就是nice值的0。
正常情况下,任何一个进程的优先级都是这个值,即使我们通过 nice 和 renice 命令调整了进程的优先级,它的取值范围也不会超出 100-139 的范围,除非这个进程是一个实时进程,那么它的优先级取值才会变成 0-99 这个范围中的一个。
实时进程/非实时进程
实时操作系统需要保证相关的实时进程在较短的时间内响应,不会有较长的延时,并且要求最小的中断延时和进程切换延时。
对于这样的需求,一般的进程调度算法,无论是 O1 还是 CFS 都是无法满足的,所以内核在设计的时候,将实时进程单独映射了 100 个优先级,这些优先级都要高于正常进程的优先级(nice值);
总的来说,Linux系统中运行的进程可以分成两类:
- 实时进程
- 非实时进程
它们的主要区别就是通过优先级来区分的。
所有优先级值在0-99范围内的,都是实时进程,所以这个优先级范围也可以叫做实时进程优先级,而100-139范围内的是非实时进程。
8.2.2:容器 CPU 限制的相关参数
cpu.cfs_quota_us
/sys/fs/cgroup/cpu/docker/容器ID/cpu.cfs_quota_us
当前容器的 CPU 配额(μs);
cpuset.cpus
/sys/fs/cgroup/cpuset/docker/容器ID/cpuset.cpus
从哪些核心上分配容器的 CPU 资源。
cpu.shares
/sys/fs/cgroup/cpu/docker/容器ID/cpu.shares
8.2.3:docker run CPU 限制参数:
–cpus=
指定容器可以使用多少可用 CPU 资源(最大不能超过宿主机的 CPU 核心数)。
例如,如果主机有两个 CPU,并且设置了–cpus =“1.5”,那么该容器将保证最多可以访问一个半的 CPU。
这相当于设置 --cpu-period =“100000” 和 --cpu-quota =“150000”。
–cpus 在Docker 1.13和更高版本中可用,用于替代这两个参数。
–cpu-period
设置CPU CFS 调度程序周期,它与 --cpu-quota 一起使用,默认为 100000 μs(100 ms),范围从 100000 - 1000000 μs(即 0.1s~1s);
–cpu-quota
在容器上添加 CPU CFS 配额;
–cpus 的值就相当于 cpu-quota / cpu-period 的值;
Docker 1.13 之后通常使用 --cpus 取代这两个参数来设定 CPU 的限制;
–cpuset-cpus
主要用于指定容器运行在哪个 CPU 核心上(从哪个核分配 CPU 资源)。
–cpuset-mem
设置使用哪个 CPU 的内存,仅对非统一内存访问(NUMA)架构有效。
这个选项一般不用。
–cpu-shares
设置 CFS 调度中的相对权重;
cpu-shares 值越高,将会分得更多的时间片;
默认的时间片 1024,最大 262144;
一般不设置;
如果容器 A 为 1024,容器 B 为 2048,则 容器 B 会获得容器 A 两倍的 CPU 资源;
8.2.4:CPU 限制测试
未进行 CPU 限制的容器
查看宿主机的 CPU 配置:
共 4 个 CPU 核心;
root@ubuntu:~# top
top - 15:43:46 up 5 min, 3 users, load average: 6.22, 3.63, 1.50
Tasks: 192 total, 1 running, 106 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.3 us, 1.0 sy, 0.0 ni, 97.3 id, 1.3 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 0.0 us, 1.0 sy, 0.0 ni, 92.4 id, 6.6 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.0 us, 0.7 sy, 0.0 ni, 75.8 id, 23.6 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 0.0 us, 2.3 sy, 0.0 ni, 60.0 id, 36.4 wa, 0.0 hi, 1.3 si, 0.0 st
启动 4 个 vm,从 4 个 CPU 分配资源:
root@ubuntu:~# docker run -it --rm lorel/docker-stress-ng --cpu 4 --vm 4
查看宿主机的 CPU 利用率:
4 个 CPU 已跑满;
top - 15:44:51 up 6 min, 3 users, load average: 6.20, 3.83, 1.69
Tasks: 210 total, 6 running, 116 sleeping, 0 stopped, 0 zombie
%Cpu0 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 97.4 us, 2.6 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 96.2 us, 1.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 2.8 si, 0.0 st
限制容器的 CPU
限制容器的 CPU 为 2:
root@ubuntu:~# docker run -it --rm --cpus 2 lorel/docker-stress-ng --cpu 4 --vm 4
查看容器的资源使用情况:
CPU 利用率为 200%;
将分配给容器的 2 核心平均分配到了每一个宿主机 CPU 核心上,每个核心的利用率为 50% 左右;
root@ubuntu:~# docker stats
root@ubuntu:~# top
top - 16:00:54 up 10 min, 2 users, load average: 2.78, 1.31, 0.57
Tasks: 204 total, 9 running, 111 sleeping, 0 stopped, 0 zombie
%Cpu0 : 49.7 us, 0.3 sy, 0.0 ni, 50.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 44.5 us, 0.0 sy, 0.0 ni, 55.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 53.0 us, 0.0 sy, 0.0 ni, 47.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 45.9 us, 0.3 sy, 0.0 ni, 53.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
查看 cpu.cfs_quota_us 的值:
CPU 的 quata 默认为 100000 μs,200% 的利用率就是 200000/100000 这样得来的。
root@ubuntu:~# cat /sys/fs/cgroup/cpu/docker/62c76fc9ac7480cffef3b50b3515026971ab45a119d022f7fca70628ee63f798/cpu.cfs_quota_us
200000
指定容器运行的 CPU 核心
将容器运行到指定的 CPU 上(1和3):
root@ubuntu:~# docker run -it --rm --cpus 2 --cpuset-cpus 1,3 lorel/docker-stress-ng --cpu 4 --vm 4
查看 cpuset.cpus:
root@ubuntu:~# cat /sys/fs/cgroup/cpuset/docker/a675ac9ee12bff9ff33988f2c9697f28a5cf58144659fa73b975105b5abe83a3/cpuset.cpus
1,3
查看宿主机的 CPU 利用率:
仅在1,3 CPU 上为容器分配 CPU 资源;
root@ubuntu:~# top
top - 16:13:37 up 22 min, 2 users, load average: 7.37, 4.80, 2.85
Tasks: 206 total, 9 running, 111 sleeping, 0 stopped, 0 zombie
%Cpu0 : 0.3 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 st
%Cpu1 : 99.7 us, 0.0 sy, 0.0 ni, 0.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 93.3 us, 0.0 sy, 0.0 ni, 6.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
基于 cpu.shares 对 CPU 进行切分
启动两个容器,一个 cpu-shares 设为 1000,一个为 500,shares 值为 500 的容器对 CPU 的利用率是 shares 值为 1000 的容器的一半:
root@ubuntu:~# docker run -it --rm --cpu-shares 1000 --name shares1000 lorel/docker-stress-ng --cpu 4 --vm 4
root@ubuntu:~# docker run -it --rm --cpu-shares 500 --name shares500 lorel/docker-stress-ng --cpu 4 --vm 4
查看两个容器的 CPU 利用率:
root@ubuntu:~# docker stats
root@docker-node2:~# docker run -it --rm --cpu-shares 1000 lorel/docker-stress-ng --cpu 4 --vm 4
root@docker-node2:~# docker run -it --rm --cpu-shares 500 lorel/docker-stress-ng --cpu 4 --vm 4
动态修改 cpu.shares
将 shares 值为 500 的容器的 shares 值动态修改为 1000:
root@ubuntu:~# echo 1000 > /sys/fs/cgroup/cpu/docker/c253e9912e64d6481fb3dd33147856fe0a05a645decd0dd67af362f92878c0b2/cpu.shares
查看两个容器的 CPU 利用率:
利用率一致了;
root@ubuntu:~# docker stats