5 Docker-资源限制
问题:
VM中,在创建虚拟机时,就限制了资源的使用
容器中,本质上为物理机中的进程,由于Linux系统中的进程可以消耗当前系统的所有资源
所以:默认情况下,容器没有资源限制
一、Cgroup:
Linux Cgroup:Linux Control group
(process container ----》 cgroup)
核心:
限制资源使用
优先级控制
一些审计或一些统计
挂起进程,恢复执行进程
1)Cgroup子系统:
cpu 子系统,主要限制进程的 cpu 使用率
cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告
cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点
memory 子系统,可以限制进程的 memory 使用量
blkio 子系统,可以限制进程的块设备 io
devices 子系统,可以控制进程能够访问某些设备
net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制
net_prio — 这个子系统用来设计网络流量的优先级
freezer 子系统,可以挂起或者恢复 cgroups 中的进程
ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace
hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统
2)实验:Cgroup实现资源限制
1、挂载
将当前的cgroup系统挂载为文件的视图,修改文件即可实现对应的功能
$ mount -t cgroup
拓展:
cpu.cfs_period_us #周期:调度的频率
cpu.cfs_quota_us #配额:默认为10万
tasks #其中包含了进程ip号
Cgroup cpu 子系统
跟系统 / 当前系统所有进程默认都在CPU子系统的跟系统中,且跟系统中没有任何资源限制
2、创建隔离组
$ cd /sys/fs/cgroup/cpu #进入cgroup子系统中的,CPU子系统的,根系统目录
$ mkdir test #在CPU子系统的根系统下,创建一个test子系统
#示例代码
int main(void) #定义一个main函数
{
int i = 0; #定义一个变量为int类型(整型),初始值为0
for(;;) i++; #for死循环,每个循环i+1
return 0; #如果退出的返回码为0
}
3、写一个c代码,并执行
$ vim main.c
$ cat main.c
int main(void)
{
int i = 0;
for(;;) i++;
return 0;
}
$ yum -y install gcc gcc-c++
$ gcc main.c
$ ls
a.out Dockerfile main.c overlay
$ ./a.out
另开一个终端,通过top命令得到进程id为:2079

4、对进程加以限制
$ cd /sys/fs/cgroup/cpu/test
$ echo "20000" > cpu.cfs_quota_us
限制成功

3)验证docker使用了cgroup
验证docker创建容器时,使用了cgroup对资源进行限制
[root@localhost ~]# cd /sys/fs/cgroup/cpu/docker/ #进入cgroup子系统中的,cpu根系统中的,docker子系统
[root@localhost docker]# ls #没有创建容器时,没有对应的容器,资源限制的子系统目录
cgroup.clone_children cpuacct.usage_percpu cpu.shares
cgroup.event_control cpu.cfs_period_us cpu.stat
cgroup.procs cpu.cfs_quota_us notify_on_release
cpuacct.stat cpu.rt_period_us tasks
cpuacct.usage cpu.rt_runtime_us
[root@localhost docker]# docker run --name wordpress -d wordpress #创建容器,给出容器id号
15f62c10aebcd46f86fdbcd7cb368964cbd47c100e93705548292d213b29633f
[root@localhost docker]# ls #在docker子系统中出现,以容器id号命名的,限制资源的子系统目录
15f62c10aebcd46f86fdbcd7cb368964cbd47c100e93705548292d213b29633f
cgroup.clone_children
cgroup.event_control
cgroup.procs
cpuacct.stat
cpuacct.usage
cpuacct.usage_percpu
cpu.cfs_period_us
cpu.cfs_quota_us
cpu.rt_period_us
cpu.rt_runtime_us
cpu.shares
cpu.stat
notify_on_release
tasks
#其中也存在对应的限制文件
[root@localhost docker]# ls 15f62c10aebcd46f86fdbcd7cb368964cbd47c100e93705548292d213b29633f/
cgroup.clone_children cpuacct.usage_percpu cpu.shares
cgroup.event_control cpu.cfs_period_us cpu.stat
cgroup.procs cpu.cfs_quota_us notify_on_release
cpuacct.stat cpu.rt_period_us tasks
cpuacct.usage cpu.rt_runtime_us
#对应的,task文件中记录了:容器中运行的进程id
[root@localhost docker]# cat 15f62c10aebcd46f86fdbcd7cb368964cbd47c100e93705548292d213b29633f/tasks
2200
2232
2233
2234
2235
2236
[root@localhost docker]# ps aux|grep 2200 |grep -v grep
root 2200 0.0 1.1 226812 22284 ? Ss 20:00 0:00 apache2 -DFOREGROUND
总结:
虽然此处,可以在对应子系统,向cpu.cfg_quota_us中写入资源限制,以实现资源限制
但是,docker有对应的命令,直接实现,所以一般不会直接在此处进行修改,从而实现资源限制
综上:docker使用 cgroup 技术,从而可以实现对资源使用的限制
二、内存资源限制
1)相关概念:
容器默认使用MEM无限制
OOME
资源分类
> 默认情况下,如果不对容器做任何限制,容器能够占用当前系统能给容器提供的所有资源
> Docker 限制可以从 Memory、CPU、Block I/O 三个方面 (Block I/O 很少用)
#若仅有一个进程对磁盘I/O进行使用,而进行了限制,会导致资源浪费
> OOME:Out Of Memory Exception
>> 当用户使用内存过高时,则会随机杀死进程,知道内存够用位置,包括 docker daemon。
>> 为此,docker 调整了docker daemon 的 OOM 的优先级,在其他进程被杀死完之前,不会杀死docker daemon
资源分类:
可压缩型资源:最低所需达不到时,仍然能使用(CPU、等)
不可压缩型资源:最低所需达不到时,不能使用(仅MEM)
2)容器内存资源限制参数
-m,--memory #内存限制,最小为4M(官方认为:一个容器能正常运行,他的内核最小使用空间为4M)
#格式:数字加单位,单位可以是b,k,m,g
#设置的是物理内存
--memory-swap #内存+swap总大小
拓展:--memory-swap 与 --memory 组合的所有情况
| --memoey-swap | --memory | MEM+swap | MEM | swap | 其他 |
|---|---|---|---|---|---|
| 正数S | 正数M | 容器可用总空间S | M | S-M | 若S=M,则无可用swap资源 |
| 0 | 正数M | M | M | 0 | |
| unset | 正数M | (前提:主机启用了swap) 3M | M | 2M | |
| -1(表示最大) | 正数M | (前提:主机启用了swap) all swap+M | M | 主机中所有swap |
**注意:**在容器中的free命令,看到的空间并不可靠
--memory-reservation #内存软限制,设置以后(进程使用内存会按照软限制为最大使用,必要情况下,会超出软限制的使用)
--oom-kill-disable #是否阻止oom killer杀死容器,默认没设置,使用此选项,必须与--memory组合
--oom-score-adj #容器被oom killer杀死的优先级,范围:[-1000,1000],默认为0
#数字越小,优先级越低,越不容易被杀死
#数字越大,优先级越高,越容易被杀死
--memory-swappiness #设置容器的swap控制行为,[0,100),100不能用
使用实例:需要运行的进程需要1G内存
若设置为0,此时:内存消耗完全由物理内存提供
若设置为10,此时:物理内存提供900M,虚拟内存提供100M
拓:swap分区设置(历史遗留问题)
一般为内存的2倍,但不超过4G,在有PHP或特殊情况下,必须有swap,也有不能使用swap的情况(k8s)
若物理内存够用下,建议关闭swap
--kernel-memory #容器中,核心内存使用限制,最小为4M
拓展:
容器内部中,内核可以设置OOME
在容器中,设置--kelnel-memory参数后,保障了内核的使用,若内存不够用,内核会随即杀死进程,即容器中的OOME
注意:
加了--kelnel-memory参数后,并不表示万事大吉,还需要对容器整体内存进行设置
容器中进程无法正常运行的可能:
由于容器中内核的OOME,杀死进程
由于容器本身整体内存受限制,而启动进程所需内存大于整体内存,导致无法启动而自己死亡
**注意:**物理机不会对内核设置资源限制,避免资源浪费

三、CPU资源限制
1)相关概念
> 默认情况下,如果不对容器做任何限制,容器能够占用当前系统中的所有 CPU 资源
> 大多数进程是采用 CFS 调度算法 (公平调度算法,平均分配算法)
> 1.13 Docker 版本后支持实时调度算法 (动态调度算法)
2)容器CPU限制参数
1、--cpuset-cpus=""
--cpuset-cpus="" #允许使用的cpu集,值可以为 0-3,0,1(能实现cpu亲和绑定)
详解:

比如CPU有4个计算单元:
0-3 #4个计算单元都允许使用,能使用400%的资源
0 #仅允许使用其中的1个计算单元,能使用100%的资源
0,1 #允许使用其中的2个计算单元,能使用200%的资源
2、--c,--cpu-shares=0
--c,--cpu-shares=0 #cpu共享权值(相对权值),默认为4096

3、--cpu-period=0
--cpu-period=0 #限制cpu CFS的周期
4、--cpu-quota=0
--cpu-quota=0 #限制cpu CFS的配额
注意:周期/配额,为资源利用率
5、--cpu-mems=""
--cpu-mems="" #允许在上执行的内存节点(MEMS),只对NUMA系统有效
拓展:服务器性能升级方式:
NUMA:cpu之间独立处理
非NUMA:cpu之间整合处理
实例:
--cpu-mems="0" #将cpu产生的数据,仅记录到当前CPU所管理的0号内存条上
--cpu-mems="0,1" #将cpu产生的数据,仅记录到当前CPU所管理的0号、1号内存条上
#根据cpu产生的量级数据确定:
若量级少,优先一个内存条存储
若量级大,有限多个内存条存储
6、--cpus
--cpus #设置能够使用的cpu核心数目
#可以使用小数
2 #cpu200%的调用
0.1 #cpu10%的调用
0.01 #cpu1%的调用
四、实验演示
1)核心命令
$ docker run --name stress -it --rm -m 256m lorel/docker-stress-ng:latest stress -vm 2
$ docker run --name stress -it --rm --cpus 2 lorel/docker-stress-ng:latest stress --cpu 8
$ docker run --name stress -it --rm --cpuset-cpus 0 lorel/docker-stress-ng:latest stress --cpu 8

2)详细实验
建议:将内存调为1G,核心跳到最大
1、不加参数压测-mem
不加参数,对内存进行压测
[root@localhost ~]# docker run --name stress -it --rm lorel/docker-stress-ng:latest stress -vm 8
系统出现OOME杀死进程:

2、加参数压测-mem
加参数限制内存使用,进行压测
#限制容器内存为256m
[root@localhost ~]# docker run --name stress -it --rm -m 256m lorel/docker-stress-ng:latest stress -vm 8
容器中出现内核OOME:


3、加参数压测-cpu
加参数限制cpu使用,进行压测
#限制cpu使用量200%,所有cpu核心拼凑达到
[root@localhost ~]# docker run --name stress -it --rm --cpus 2 lorel/docker-stress-ng:latest stress --cpu 8

#限制cpu使用量200%,完整使用第0个cpu核心和第1和cpu核心达到
[root@localhost ~]# docker run --name stress -it --rm --cpuset-cpus 0-1 lorel/docker-stress-ng:latest stress --cpu 8

注意:--cpus 与 --cpuset-cpus参数区别
--cpuset-cpus="0-2" 表示使用第0个cpu核心到第2个cpu核心完全调用,最后达到300%使用量
--cpus 3 表示所有cpu核心都能使用,最后拼凑为300%使用量
两者都表示:能使用cpu300%使用量
--cpuset-cpus 0-2 --cpus 1 #表示使用第0个cpu核心到第2个cpu核心,最后拼凑到一起,使用100%的使用量
五:docker是什么?
Docker 是一个使用LXC(linux container)技术
基于golang语言开发的高级容器引擎
代码基于h2协议托管在github上
当前容器技术,也需要依托于内核的各种机制
比如:
CGROUP 进行资源限制
namespace进行隔离,NETWORK、MOUNT、PID等
veth、bridge、netfilter进行网络联通
chroot实现内部伪根
UFS的overlay实现容器可写层、镜像的分层
(限制 隔离 联通 伪根 存储)
(cgroup namespace veth chroot overlay)