Docker 容器简介

容器是镜像创建的运行实例,Docker利用容器来运行应用。容器完全使用沙箱机制,相互之间不会有任何接口,每个 Docker 容器都是相互隔离的、保证安全的平台。可以把容器看做是一个轻量级的Linux运行环境。几乎没有性能开销,可以很容易地在机器和数据中心中运行。
Docker容器与其他的容器技术都是大致类似的。但是,Docker在一个单一的容器内捆绑了关键的应用程序组件,这也就让这容器可以在不同平台和云计算之间实现便携性。其结果就是,Docker就成为了需要实现跨多个不同环境运行的应用程序的理想容器技术选择。

Docker是一种轻量级的虚拟化技术,同时是一个开源的应用容器运行环境搭建平台,可以让开发者以便捷方式打包应用到一个可移植的容器中,然后安装至任何运行Linux或Windows等系统的服务器上。相较于传统虚拟机,Docker容器提供了轻量化的虚拟化方式,安装便捷,启停速度快。

资源限制

默认情况下,容器没有资源限制,可以使用主机内核调度程序所允许的、经可能多的给定资源,Docker提供了可以限制容器使用多少内存或CPU的方法。其中,许多功能都要求宿主机的内核支持Linux功能。要检查是否支持Linux,可以使用docker info命令。如果内核中禁用了某项功能,那么可能会在输出结尾处看到警告,例如:

WARNING:No swap limit support

内存资源限制

对于Linux 主机,如果没有足够的内存来执行其他重要的系统任务,将会抛出OOM(Out of Memory Exception,内存溢出、内存泄漏、内存异常),随后系统会开始杀死进程以释放内存,凡是运行在宿主机的进程都有可能被kill,包括dockerd和其他应用程序。如果重要的系统进程被Kill,会导致和该进程相关的服务全部宕机。

产生OOM 异常时,dockerd 尝试通过调整Docker 守护程序上的OOM 优先级来减轻这些风险,以便它比系统上的其他进程更不可能被杀死,但是容器的OOM优先级未调整,这使得单个容器被杀死的可能性比Docker 守护程序或其他系统进程被杀死的可能性更大。不推荐通过在守护程序或容器上手动设置 --oom-score-adj 为极端负数,或通过在容器上设置 --oom-kill-disable 来绕过这些安全措施。

  • –oom-score-adj:宿主机kernel对进程使用的内存进行评分,评分最高的将被宿主机内
    核kill掉,可以指定一个容器的评分制,但是不推荐手动指定。
  • –oom-kill-disable:对某个容器关闭OOM优先级机制。
    OOM优先级机制是指Linux会为每个进程算一个分数,最终它会杀死分数最高的进程。
  • /proc/PID/oom_score_adj:范围为-1000~1000,值越高越容易被宿主机杀死,如果将该
    值设置为-1000,则进程永远不会被宿主机kernel杀死。
  • /proc/PID/oom_adj:范围为-17~+15,取值越高越容易被杀死,如果是-17,则表示不能被杀死,该设置参数的存在是为了和旧版本的Linux内核兼容。
  • /proc/PID/oom score:这个值是系统综合进程的内存消耗量、CPU时间(utime+stime)、存活时间(uptime-start time)和oom_adj计算出的进程得分,消耗内存越多得分越高,越容易被宿主机kernel强制杀死。

容器的内存限制

Docker可以强制执行硬性内存限制,即只允许容器使用给定的内存大小。Docker也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了。
内存限制的设置选项中的大多数采用正整数,后跟b、k、m、g后缀,以表示字节、千字节、兆字节或千兆字节。例如:

  • -m or --memory:容器可以使用的最大内存量,如果设置此选项,则允许的最小存值为4m(4兆字节)。
  • –memory-swap:容器可以使用的交换分区大小,必须在设置了物理内存限制的前提才能设置交换分区的限制
  • –memory-swappiness:设置容器使用交换分区的倾向性,值越高表示越倾向于使用swap分区,范围为0~100,0为能不用就不用,100为能用就用。
  • -kernel-memory:容器可以使用的最大内核内存量,最小为4m,由于内核内存与用白空间内存隔离,无法与用户空间内存直接交换,因此内核内存不足的容器可能会阻宿主机资源,这会对主机和其他容器或者其他服务进程产生影响,因此不要设置内核内存大小。
  • -memory-reservation:允许指定小于-memory的软限制,当Docker检测到主机上的多用或内存不足时便会激活该限制。如果使用-memory-reservation,则必须将它设置为低于-memory才能使其优先。因为它是软限制,所以不能保证容器不超过限制。
  • -oom-kill-disable:默认情况下,发生OOM时,kernel会杀死容器内进程,但是可以使用-oom-kill-disable参数禁止OOM发生在指定的容器上,即仅在已设置-m/-memory选项的容器上禁用OOM;如果-m参数未配置,那么产生OOM时,主机为了释放内存还是会杀死系统进程。
    其中,swap限制的是–memory-swap,只有在设置了–memory后才会有意义。使用swap可以让容器将超出限制部分的内存置换到磁盘上,但经常将内存交换到磁盘上会降低性能。不同的–memory-swap设置会产生不同的效果;
  • –memory-swap值为正数,那么–memory和-memory-swap都必须设置,–memory-swap表示能使用的内存和swap分区大小的总和。例如:-memory=300m,–memory-swap=1g.那么该容器能够使用300m内存和700mswap分区,即-memory是实际物理内存大小值不变,而swap的实际大小计算方式为:(-memory-swap)-(-memory)=容器可用swap.-memory-swap设置为0,则忽略该设置,并将该值视为未设置,即未设置交换分区.–memory-swap等于–memory的值,并且–memory设置为正整数,则容器无权访问swap,即也没有设置交换分区。
  • –memory-swap设置为unset,如果宿主机开启了swap,则实际容器的swap值为2*(–memory),即两倍于物理内存大小,但是并不准确(在容器中使用free命令所看到的swap空间并不精确,毕竟每个容器都可以看到具体大小,但是宿主机的swap是有上限的)。
    -memory-swap设置为-1,如果宿主机开启了swap,则容器可以使用主机上swap的最[大空间。

对内存限制之后,可以进行有效性验证。假设一个容器未做内存限制,则该容器可以利用系统内存的最大空间,默认创建的容器没有做内存资源限制。

1.内存大小硬限制

下载测试容器

[root@docker ~]#docker pull lorel/docker-stress-ng
Using default tag: latest
latest: Pulling from lorel/docker-stress-ng
[DEPRECATION NOTICE] Docker Image Format v1, and Docker Image manifest version 2, schema 1 support will be removed in an upcoming release. Suggest the author of docker.io/lorel/docker-stress-ng:latest to upgrade the image to the OCI Format, or Docker Image manifest v2, schema 2. More information at https://docs.docker.com/go/deprecated-image-specs/
c52e3ed763ff: Pull complete 
a3ed95caeb02: Pull complete 
7f831269c70e: Pull complete 
Digest: sha256:c8776b750869e274b340f8e8eb9a7d8fb2472edd5b25ff5b7d55728bca681322
Status: Downloaded newer image for lorel/docker-stress-ng:latest
docker.io/lorel/docker-stress-ng:latest
[root@docker mynginx]#

启动两个工作进程,每个工作进程最大允许使用256M内存,且书主机不限制当前容器的最大内存:

[root@docker ~]# docker run -idt --name streel --rm lorel/docker-stress-ng -vm 2 --vm-bytes 256M
8a0555bed98a3366125a49c3174c6fce9f9c763401386f5e84f4aece1ea1cf96
[root@docker ~]# docker ps
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
8a0555bed98a   streel    199.62%   514.2MiB / 7.62GiB   6.59%     656B / 0B   0B / 0B     5

接着宿主机限制容器的最大内存使用:

启动过程中会出现以下报错信息,由于两个工作进程启动所需的内存超过宿主机限制,所以一直会出现00M,容器进程会一直被kill

[root@docker ~]# docker run -it --name streel --memory 256m --rm lorel/docker-stress-ng -vm 2 --vm-bytes 256M
stress-ng: debug: [1] 4 processors online
stress-ng: debug: [1] main: can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
stress-ng: debug: [1] starting processes
stress-ng: debug: [1] 2 processes running
stress-ng: debug: [7] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [7] stress-ng-vm: started [7] (instance 0)
stress-ng: debug: [8] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [8] stress-ng-vm: started [8] (instance 1)
stress-ng: debug: [8] stress-ng-vm: child died: 9 (instance 1)
stress-ng: debug: [8] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 1)
stress-ng: debug: [7] stress-ng-vm: child died: 9 (instance 0)
stress-ng: debug: [7] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 0)
stress-ng: debug: [8] stress-ng-vm: child died: 9 (instance 1)
stress-ng: debug: [8] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 1)
stress-ng: debug: [7] stress-ng-vm: child died: 9 (instance 0)
stress-ng: debug: [7] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 0)
stress-ng: debug: [1] process [7] terminated
stress-ng: debug: [1] process [8] terminated
stress-ng: info: [1] successful run completed in 7.16s
[root@docker ~]# docker stats
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O     BLOCK I/O         PIDS
5b0e0cbcfc13   streel    245.08%   227.9MiB / 256MiB   89.03%    656B / 0B   3.22GB / 11.5GB   5

宿主机cgroup验证:

[root@docker fs]# docker ps -a
CONTAINER ID   IMAGE                    COMMAND                   CREATED          STATUS          PORTS     
1802e17591c0   lorel/docker-stress-ng   "/usr/bin/stress-ng …"   48 seconds ago   Up 47 seconds             s
[root@docker fs]# cd /sys/fs/cgroup/memory/docker/1802e17591c0daa6de4026d38ea9a66c67e8c196cdd37478c762a8ddb01
[root@docker 1802e17591c0daa6de4026d38ea9a66c67e8c196cdd37478c762a8ddb01c0c02]# cat memory.limit_imen_bytes
cat: memory.limit_imen_bytes: 没有那个文件或目录
[root@docker 1802e17591c0daa6de4026d38ea9a66c67e8c196cdd37478c762a8ddb01c0c02]# cat memory.limit_in_bytes 
268435456 ##宿主机基于cqroup对容器进行内存资源的大小限制
[root@docker 1802e17591c0daa6de4026d38ea9a66c67e8c196cdd37478c762a8ddb01c0c02]# 

注意:通过echo命令可以修改内存限制的值,但是只可以在原基础之上增大内存限制,小内存限制会报错(write error:Device or resource busy)。

2. 内存大小软限制

使用–memory-reservation,命令如下:

[root@docker ~]# docker run -it --memory 256m --name streel --memory-reservation 127m --rm lorel/docker-stress-ng -vm 2 --vm-bytes 256M
stress-ng: debug: [1] 4 processors online
stress-ng: debug: [1] main: can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
stress-ng: debug: [1] starting processes
stress-ng: debug: [1] 2 processes running
stress-ng: debug: [7] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [8] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [7] stress-ng-vm: started [7] (instance 0)
stress-ng: debug: [8] stress-ng-vm: started [8] (instance 1)
stress-ng: debug: [7] stress-ng-vm: child died: 9 (instance 0)
stress-ng: debug: [7] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 0)
stress-ng: debug: [8] stress-ng-vm: child died: 9 (instance 1)
stress-ng: debug: [8] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 1)
stress-ng: debug: [7] stress-ng-vm: child died: 9 (instance 0)
stress-ng: debug: [7] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 0)
stress-ng: debug: [8] stress-ng-vm: child died: 9 (instance 1)
stress-ng: debug: [8] stress-ng-vm: assuming killed by OOM killer, restarting again (instance 1)
stress-ng: debug: [7] stress-ng-vm: child died: 9 (instance 0)

宿主机cgroup验证:

[root@docker ~]# cat /sys/fs/cgroup/memory/docker/3170e5dc054013f193f9ecddf872597d50303fdcfe59181ba85a11b036bf35ef/memory.soft_limit_in_bytes 
133169152 #返回的软限制结果
[root@docker ~]# 

3.关闭OOM限制

[root@docker ~]# docker run -it --memory 257m --name streel --oom-kill-disable --rm lorel/docker-stress-ng -vm 2 --vm-bytes 256M
stress-ng: debug: [1] 4 processors online
stress-ng: debug: [1] main: can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 vm
stress-ng: debug: [1] starting processes
stress-ng: debug: [1] 2 processes running
stress-ng: debug: [7] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [7] stress-ng-vm: started [7] (instance 0)
stress-ng: debug: [8] : can't set oom_score_adj failed, errno=13 (Permission denied)
stress-ng: debug: [8] stress-ng-vm: started [8] (instance 1)
[root@docker ~]# docker stats ##此时可以看到容器进程不会因为OOM而被ki11
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O     BLOCK I/O      PIDS
420200db00fd   streel    0.01%     256.8MiB / 257MiB   99.93%    586B / 0B   51MB / 640MB   5
[root@docker ~]# cat /sys/fs/cgroup/memory/docker/420200db00fd39eebfc77fbf3c386288ec8646ad61bbd790523f535b385d81eb/memory.soft_limit_in_bytes 
9223372036854771712
[root@docker ~]# cat /sys/fs/cgroup/memory/docker/420200db00fd39eebfc77fbf3c386288ec8646ad61bbd790523f535b385d81eb/memory.oom_control 
oom_kill_disable 1
under_oom 1
[root@docker ~]# 

4.交换分区限制

[root@docker ~]# docker run -idt --rm -m 256m --memory-swap 512m centos:7 /bin/bash
59f539558d8db2b73356be7799abf45555ec2dd72249096d66de84d89ac5c89d
[root@docker ~]# cat /sys/fs/cgroup/memory/docker/59f539558d8db2b73356be7799abf45555ec2dd72249096d66de84d89ac5c89d/memory.memsw.limit_in_bytes
536870912
[root@docker ~]# 

容器的CPU限制

一个宿主机有几十个核的CPU,但是宿主机上可以同时运行成百上千个不同的进程用以处理不同的任务,多进程共用一个 CPU 的核心依赖计数就是为可压缩资源,即一个核心的 CPU 可以通过调度而运行多个进程,但是同一个单位时间内只能有一个进程在CPU 上运行,那么这么多的进程怎么在 CPU 上执行和调度的呢?

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

  • CPU 密集型的场景:优先级越低越好,计算密集型任务的特点是要进行大量的计算,消耗CPU 资源,例如计算圆周率、数据处理、对视频进行高清解码等等,全靠CPU 的运算能力。
  • IO 密集型的场景:优先级越低越好,涉及到网络、磁盘IO 的任务都是IO 密集型任务,这类任务的特点是CPU 消耗很少,任务的大部分时间都在等待IO 操作完成(因为IO 的速度远远低于CPU 和内存的速度),例如Web 应用,对于高并发、数据量大的动态网站来说,数据库应该为IO 密集型。

交互式任务通常会重度依赖VO操作(如GUI应用),并且通常用不完分配给它的时间片。而非交互式任务(如数学运算)则需要使用更多的CPU资源,它们通常会在用完自己的时间片之后被抢占,并不会被1O请求频繁阻擦,词度策略的T资源,它们通的会在务,并且保证每个任务都能停到足够的执行资源,而不会对其他任务产生明显的性能影响。为了保证CPU利用率最大化,同们:而针对10张集动出想,倾向于为非交互式任务分配更大的时间片,但是以较低的频率运行它们;而针对I/O密集型任务,则会在较短周期内频繁地执行。

Linux kernel进程的调度基于完全公平调度(Completely Fair Scheduler,CFS)。实现了一个基千权重的公平队列算法,从而将CPU时间分配给多个任务,每个任务都有一个关联的虚拟运行时间vruntime,表示一个任务所使用的CPU时间除以其优先级得到的值。相同优先级和相同vruntime的两个任务实际运行的时间也是相同的,这就意味着CPU资源是由它们均分了。为了保证所有任务能够公平推进,每当需要抢占当前任务时,CFS总会挑选出vruntime较小的那个任务来运行。

[root@docker ~]# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq 
[root@docker ~]# 

默认情况下,每个容器对主机CPU周期的访问权限是不受限制的,但是我们可以设置各种约束来限制给定容器访问主机的CPU周期,大多数用户使用的是默认的CFS调度方式。在Docker 1.13及更高版本中,还可以配置实时优先级。

  • –cpus:指定容器可以使用多少可用的CPU资源,例如,如果主机有两个CPU,并且设置了–cpus=“1.5”,那么该容器将保证最多可以访问1.5个的CPU(如果是4核CPU,那么还可以是4个核心上每核用一点,但是总计是1.5核心的CPU),这相当于设置–cpu-period ="100000"和–cpu-quota=“150000”。–cpus主要在Docker 1.13和更高版本中使用,目的是替代–cpu-period和–cpu-quota两个参数,从而使配置更简单,但是最大不能超出宿主机CPU的总核心数。
[root@docker ~]# docker run -idt --rm --cpus 10  centos:7 /bin/bash
docker: Error response from daemon: Range of CPUs is from 0.01 to 4.00, as there are only 4 CPUs available.
See 'docker run --help'. .#分配给容器的CPU超出了宿主机CPU总数
[root@docker ~]# 

1.未限制容器CPU

对于一台2核的服务器,如果不做限制,容器会把宿主机的CPU全部占完:

#分配4核CPU并启动2个工作线程

[root@docker ~]# docker run -dit --rm --name magedu-cl lorel/docker-stress-ng --cpu 4 --vm 2
b96fa003832cf387f0447a4c21119efdc1e8ef4e9b9beba10556a60fbd93058c
[root@docker ~]# docker stats
CONTAINER ID   NAME                CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
b96fa003832c   magedu-cl           398.04%   272.2MiB / 7.62GiB   3.49%     586B / 0B   0B / 0B     9
59f539558d8d   stupefied_wozniak   0.00%     404KiB / 256MiB      0.15%     656B / 0B   0B / 0B     1
[root@docker ~]# 

在宿主机查看CPU限制参数:

[root@docker ~]# cat /sys/fs/cgroup/cpuset/docker/59f539558d8db2b73356be7799abf45555ec2dd72249096d66de84d89ac5c89d/cpuset.cpus
0-3
[root@docker ~]# 

宿主机CPU利用率:

[root@docker ~]# top
top - 20:04:14 up  9:52,  2 users,  load average: 5.47, 2.35, 1.32
Tasks: 134 total,   7 running, 127 sleeping,   0 stopped,   0 zombie
%Cpu(s):100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7990060 total,  5765328 free,  1138884 used,  1085848 buff/cache
KiB Swap:  4194300 total,  4192260 free,     2040 used.  6564776 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                               
  8146 root      20   0    6896   2136    252 R  78.7  0.0   1:40.18 stress-ng-cpu                         
  8150 root      20   0    6896   2136    252 R  66.4  0.0   1:34.68 stress-ng-cpu                         
  8152 root      20   0  268400 262152    132 R  66.4  3.3   1:38.56 stress-ng-vm                          
  8151 root      20   0    6896   2136    252 R  65.4  0.0   1:35.01 stress-ng-cpu                         
  8153 root      20   0  268400 262152    132 R  61.8  3.3   1:39.49 stress-ng-vm                          
  8148 root      20   0    6896   2136    252 R  60.8  0.0   1:39.15 stress-ng-cpu  

2.限制容器CPU

只给容器分配最多2核宿主机CPU利用率:

[root@docker ~]# docker run -it --rm --name stressl --cpus 1 lorel/docker-stress-ng --cpu 2 --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 cpu, 2 vm
[root@docker ~]# docker stats ## 当前容器状态

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
576e9ae814e3   stressl   0.04%     518.1MiB / 7.62GiB   6.64%     656B / 0B   0B / 0B     7
[root@docker ~]# 

宿主机cgroup验证:

[root@docker ~]# cat /sys/fs/cgroup/cpu,cpuacct/docker/576e9ae814e3b9b19e19ecc60c4a7667bd1a9bf64bb33ba0d9999f4358ed77e8/cpu.cfs_quota_us
100000
[root@docker ~]# 

每核心CPU会按照1000为单位转换成百分比进行资源划分,2个核心的CPU就是2000/1000=200%,4个核心4000/1000=400%,以此类推。

宿主机CPU利用率:

Tasks: 132 total,   5 running, 127 sleeping,   0 stopped,   0 zombie
%Cpu(s): 16.4 us,  1.5 sy,  0.0 ni, 82.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7990060 total,  5786472 free,  1117664 used,  1085924 buff/cache
KiB Swap:  4194300 total,  4192260 free,     2040 used.  6585976 avail Mem 
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                                                                                                
  8362 root      20   0    6892   2136    252 R  11.8  0.0   0:14.67 stress-ng-cpu                                                                                                                                                                                                                                                          
  8364 root      20   0    6892   2140    252 R  11.8  0.0   0:14.58 stress-ng-cpu                                                                                                                                                                                                                                                          
  8366 root      20   0  268392 262156    132 R  11.8  3.3   0:14.50 stress-ng-vm                                                                                                                                                                                                                                                           
  8367 root      20   0  268392 262156    132 R  11.8  3.3   0:14.54 stress-ng-vm 

注意:CPU资源限制是将分配给容器的1核分配到了宿主机的每一核CPU上,也就是容器的总CPU值是在宿主机的每一核CPU上都分配了部分比例。

3.将容器运行到指定的CPU上

[root@docker ~]# docker run -it --rm --name stressl --cpus 1 --cpuset-cpus 0 lorel/docker-stress-ng --cpu 2 --vm 2
stress-ng: info: [1] defaulting to a 86400 second run per stressor
stress-ng: info: [1] dispatching hogs: 2 cpu, 2 vm
[root@docker ~]# cat /sys/fs/cgroup/cpuset/docker/3ea1748f9294851dcb8702cf2b15a810fcf7d3740b0158e1d6eb8a63cf4d498a/cpuset.cpus
0
[root@docker ~]# 

容器运行状态:

[root@docker ~]# docker stats
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
3ea1748f9294   stressl   0.22%     519.7MiB / 7.62GiB   6.66%     656B / 0B   0B / 0B     7
[root@docker ~]#

宿主机CPU利用率:

Tasks: 131 total,   5 running, 126 sleeping,   0 stopped,   0 zombie
%Cpu(s): 25.8 us,  1.5 sy,  0.0 ni, 72.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  7990060 total,  5782148 free,  1122168 used,  1085744 buff/cache
KiB Swap:  4194300 total,  4192260 free,     2040 used.  6581568 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                               
  8501 root      20   0  268392 262152    132 R  31.2  3.3   0:28.57 stress-ng-vm                          
  8502 root      20   0  268392 262152    132 R  25.0  3.3   0:28.57 stress-ng-vm                          
  8497 root      20   0    6888   2864    244 R  18.8  0.0   0:28.57 stress-ng-cpu                         
  8499 root      20   0    6888   2864    244 R  18.8  0.0   0:28.57 stress-ng-cpu           

4.基于cpu-shares对CPU进行切分

启动两个容器stressl和stress2,stress1的–cpu-shares值为1000,stress2的–cpu-shares值为500.观察最终效果,–cpu-shares值为1000的stress1的CPU利用率基本是–cpu-shares值为500的stress2的2倍:

[root@docker ~]# docker run -dit --rm --name stress1 --cpu-shares 1000 lorel/docker-stress-ng --cpu 2 --vm 2adabc4039e5d2a2ccda31b08f0ef8b105e20ac158b776c94a303b94ecf1de4c0
[root@docker ~]# docker run -dit --rm --name stress2 --cpu-shares 500 lorel/docker-stress-ng --cpu 2 --vm 2
02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187
[root@docker ~]# 

验证容器运行状态:

[root@docker ~]# docker stats

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
02fe7ff9b27c   stress2   133.77%   519.9MiB / 7.62GiB   6.66%     656B / 0B   0B / 0B     7
adabc4039e5d   stress1   0.27%     518.1MiB / 7.62GiB   6.64%     656B / 0B   0B / 0B     7

宿主机cgroup验证:

[root@docker ~]# cat /sys/fs/cgroup/cpu,cpuacct/docker/02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187/cpu.shares
500
[root@docker ~]# cat /sys/fs/cgroup/cpu,cpuacct/docker/adabc4039e5d2a2ccda31b08f0ef8b105e20ac158b776c94a303b94ecf1de4c0/cpu.shares
1000
[root@docker ~]# 

–cpu-shares的值可以在宿主机cgroup中动态修改,修改完成后立即生效,其值可以调大也可以
#将stress2的CPU shares设置为2000

[root@docker ~]# echo 2000 > /sys/fs/cgroup/cpu,cpuacct/docker/02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187/cpu.shares
[root@docker ~]# 

验证修改后的容器运行状态:

[root@docker ~]# docker stats
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O     BLOCK I/O   PIDS
02fe7ff9b27c   stress2   273.88%   519.9MiB / 7.62GiB   6.66%     656B / 0B   0B / 0B     7
adabc4039e5d   stress1   124.82%   518.1MiB / 7.62GiB   6.64%     656B / 0B   0B / 0B     7

容器的底层技术

所有的可运行的容器运行在宿主机系统的内核之上,但是锁定在自己的运行环境中,与主机以及其他容器的运行环境是隔离的。Docker底层的两个核心技术分别是Cgroup和Namespace,并且使用了一系列Linux内核提供的特性来实现容器的基本功能,包含命名空间、群组控制、联合文件系统以及LXC等特性。

Cgroup

Cgroup是Controlgroups的缩写,即群组控制技术。Docker使用到了群组控制技术来管理可利用的资源,其主要具有对共享资源的分配、限制、审计及管理等功能,例如可以为每个容器分配固定的CPU、内存以及I/O等资源。群组控制特性使得容器能在物理机上互不干扰地运行,并且平等使用物理资源。

Cgroup是由Linux内核提供的限制、记录和隔离进程组所使用物理资源的机制。Cgroup中的重要概念是“子系统”,也就是资源控制器,每种子系统就是一个资源的分配器,例如CPU子系统是控制CPU时间分配的。是首先挂载子系统,然后才有control group,例如先挂载memory子系统,然后在memory限制的子系统中创建一个Cgroup节点,在这个节点中,将需要控制的进程ID写入,并且将控制的属性写入,这就完成了内存的资源限制。在很多领域可以取代虚拟化技术分割资源。Cgroup默认有诸多资源组,可以限制几乎所有服务器上的资源:cpu、mem、iops等。例如:

  • CPU:/sys/fs/cgroup/cpu/docker。
  • 内存:/sys/fs/cgroup/memory/docker。
  • 磁盘IO:/sys/fs/cgroup/blkio/docker。

当运行一个容器时,Linux系统(宿主机)就会为这个容器创建一个cgroup目录,并以容器长ID命名

[root@docker ~]# ll /sys/fs/cgroup/cpu/docker/
总用量 0
drwxr-xr-x. 2 root root 0 129 20:19 02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187
drwxr-xr-x. 2 root root 0 129 20:19 adabc4039e5d2a2ccda31b08f0ef8b105e20ac158b776c94a303b94ecf1de4c0
drwxr-xr-x. 2 root root 0 129 16:14 buildkit
-rw-r--r--. 1 root root 0 129 10:31 cgroup.clone_children
--w--w--w-. 1 root root 0 129 10:31 cgroup.event_control
-rw-r--r--. 1 root root 0 129 10:31 cgroup.procs
-r--r--r--. 1 root root 0 129 10:31 cpuacct.stat
-rw-r--r--. 1 root root 0 129 10:31 cpuacct.usage
-r--r--r--. 1 root root 0 129 10:31 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 129 10:31 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 129 10:31 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 129 10:31 cpu.rt_period_us
-rw-r--r--. 1 root root 0 129 10:31 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 129 10:31 cpu.shares
-r--r--r--. 1 root root 0 129 10:31 cpu.stat
-rw-r--r--. 1 root root 0 129 10:31 notify_on_release
-rw-r--r--. 1 root root 0 129 10:31 tasks
[root@docker ~]# 

该长ID日录下有一个cpushares文件,其值为默认值,或者是在运行容器时指定的值.

[root@docker ~]# ll /sys/fs/cgroup/cpu/docker/02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187/
总用量 0
-rw-r--r--. 1 root root 0 129 20:19 cgroup.clone_children
--w--w--w-. 1 root root 0 129 20:19 cgroup.event_control
-rw-r--r--. 1 root root 0 129 20:19 cgroup.procs
-r--r--r--. 1 root root 0 129 20:19 cpuacct.stat
-rw-r--r--. 1 root root 0 129 20:19 cpuacct.usage
-r--r--r--. 1 root root 0 129 20:19 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 129 20:19 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 129 20:19 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 129 20:19 cpu.rt_period_us
-rw-r--r--. 1 root root 0 129 20:19 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 129 20:22 cpu.shares
-r--r--r--. 1 root root 0 129 20:19 cpu.stat
-rw-r--r--. 1 root root 0 129 20:19 notify_on_release
-rw-r--r--. 1 root root 0 129 20:19 tasks
[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/02fe7ff9b27ca2fcabf6f701afc822aa88615462a1c223eb069a20bf968a7187/cpu.shares 
2000
[root@docker ~]# 

Namespace

在Docker中,每个容器都有自己独立的一套完整资源,包括网卡设备、文件系统等。而实现这一技术的正是 Namespace,Namespace管理 Host 中全局唯一资源,并可以让每个容器都觉得只有自己在使用它。其实也就是 Namespace实现了容器间资源的隔离。也可以类比 K8s 的 Namespace,在 K8s 中,Namespace可实现权限划分、资源访问等。
Namespace的作用是为容器提供进程间隔离的技术,每个容器都有独立的运行空间,比如pid、net、mnt、uts、ipc等Namespace,以及为每个容器提供不同的主机名。Namespace可以保证不同的容器之间不会相互干扰,每个容器都像是一个独立空间且有可使用的系统。利用Namespace提供了一个隔离层,每一个应用服务都是在它们自己的命名空间中运行,而且不会访问到命名空间之外的资源。

  1. Mount Namespace
    Mount Namespace简称mnt Namespace。该Namespace可让容器拥有自己独立的文件系统,比如容器有自己的/目录,可实现相关的挂载操作,当然这些操作并不会影响宿主机及其他容器通过mnt Namespace可以将一个进程放到一个特定的目录执行。mnt Namespace允连不Namespace的进程看到的文件结构不同,这样每个Namespace中的进程所看到的文件且录就被隔开了,与chroot不同的是,每个Namespace中的container在/proc/mounts的信息只包含所在Namenag
    的mount point上。

  2. UTS Namespace
    简单地说,UTS Namespace让容器有自己的主机名。
    UTS Namesace可让容器拥有自己独立的主机名和域名,例如在运行容器时通过-h指定主机使它在网络上可以被视作一个独立的节点,而不是主机上的一个进程。默认情况下,容器的主机名是它的短ID,可以通过-h或-hostname参数设置,例如:

docker run -it -h rab

其中rab就是该容器的主机名

  1. IPC Namespace
    IPC Namespace让容器拥有自己的共享内存和信号量,来实现进程间的通信,而不会与宿主机及其他容器混在一起。
    容器中进程交互还是采用Linux常见的进程间交互方法(Inter Process Communication,IPC),包括常见的信号量、消息队列和共享内存。然而与VM不同的是,容器的进程间交互实际上还是Host上具有相同PID Namespace中的进程间交互,因此需要在IPC资源申请时加入Namespace信息,每个IPC资源有一个唯一的32位ID。

  2. PID Namespace
    PID Namespace让容器拥有自己独立的一套PID,每个容器都是以进程的形式在宿主机中运行。不同的用户进程通过PID Namespace隔离开,且不同Namespace可以有相同的PID。PIDNamespace具有以下特点:

  • 每个Namespace中的PID是自己PID=1的进程。
  • 每个Namespace中的进程只能影响同一个 Namespace中的进程,或者子Namespace中的进程。

注意:每个容器拥有自己独立的一套PID,但容器中为1的PID并不是宿主机的init进程,因为容器与宿主机Host的PID是完全隔离且完整独立的。

  1. Network Namespace
    Network Namespace简称net Namespace。net Namespace让容器拥有自己独立的网卡设备、路由等资源,同样与宿主机的Network是完全隔离且完整独立的。
    网络隔离是通过net Namespace实现的,每个net Namespace有独立的network devices、IPaddresses、IP routing tables、/proc/net目录。这样每个容器的网络就能隔离开来。Docker默认采用veth的方式将容器中的虚拟网卡与宿主机上的一个Docker Bridge连接在一起。

  2. User Namespace
    User NameSpace主要隔离了安全相关的标识符和树形,比如用户ID、用户组ID、root目录、秘钥等。一个进程的用户ID和组ID在User NameSpace内外可以不同,在该User NameSpace外,它是一个非特权的用户ID,而在此命名空间内,进程可以使用0作为用户ID,且具有完全的特殊权限。
    User Namespace让容器拥有自己独立的用户空间,同样与宿主机的User是完全隔离且完整独立的。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐