一、概述
Docker容器的本质是宿主机上的一个进程。Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过*写时复制机制(copy-on-write)*实现了高效的文件操作。
二、Linux内核的namespace机制
- namespace 机制提供一种资源隔离方案。
- PID,IPC,Network等系统资源不再是全局性的,而是属于某个特定的Namespace.
- 每个namespace下的资源对于其它的namespace下的资源是透明的,不可见的。
- Linux内核实现namespace的一个主要目的就是实现轻量级虚拟化(容器)服务,在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知,以达到独立和隔离的目的。
三、namespace(命名空间)可以隔离哪些?
- 文件系统需要是被隔离的
- 网络也是需要被隔离的
- 进程间的通信也要被隔离
- 针对权限,用户和用户组也需要隔离
- 进程内的PID也需要与宿主机中的PID进行隔离
- 容器也要有自己的主机名
有了以上的隔离,我们认为一个容器可以与宿主机和其他容器是隔离开的。 恰巧Linux 的namespace可以做到这些。
namespace | 隔离内容 | 系统调用参数 |
---|
UTS | 主机名与域名 | CLONE_NEWUTS | IPC | 信号量、消息队列和共享内存 | CLONE_NEWIPC | Network | 网络设备、网络栈、端口等 | CLONE_NEWNET | PID | 进程编号 | CLONE_NEWPID | Mount | 挂载点(文件系统) | CLONE_NEWNS | User | 用户和用户组 | CLONE_NEWUSER |
3.1、namespace的操作
- namespace的API包括clone() setns() unshare() 还有/proc下的部分文件
- 为了确定隔离的是那些namespace,需要指定以下6个参数的一个或多个 |进行分隔。6个参数就是上面表中提到的CLONE_NEWUTS、CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNET、CLONE_NEWUSER
3.2、clone()
- 使用clone() 来创建一个独立namespace的进程,是最常见的做法,也是Docker使用namespace最基本的方法。
- clone() 是 Linux 系统调用fork() 的一种更通用的实现方式,可以通过flags来控制使用多少功能。
3.3、/proc/[pid]/ns 用户可以在/proc/[pid]/ns文件下看到指向不同namespace的文件。
$ docker ps
$ docker exec -it host2-c10 hostname
$ ps -ef|grep f057b4b03eb8
$ ls -l /proc/3571/ns
中括号内的为namespace号。如果两个进程指向的namespace号相同,那么说明它们在同一个namespace。
设置link的作用是,即便该namespace下的所有进程都已经结束,这个namespace也会一直存在,后续的进程可以加入进来。
3.4、setns()
- Docker中 使用 docker exec命令在已经运行着的命令执行一个新的命令就需要使用setns() 。
- 通过setns()系统调用,进程从原来的的namespace加入某个已经存在的namespace
- 通常为了不影响进程的调用者,也为了使新加入的pid namespace生效,会在setns()函数执行后使用clone()创建子进程继续执行命令,让原先的进程结束运行。
int setns(int fd, in nstype);
为了把新加入的namespace利用起来,需要引入execve()系列函数,该函数可以执行用户命令,常用的就是调用/bin/bash并接受参数。
3.5、unshare()
- 通过unshare() 在原先的进程上namespace隔离
- unshare与clone很像,unshare不需要新启动一个进程,在原有的进程上就可以进行使用。但是docker并没有使用
3.6、fork() 系统调用
fork并不属于namespace的API
3.7、使用Namespace进行容器的隔离有什么缺点呢?
- 最大的缺点就是隔离不彻底,其它进程会跟docker抢占资源,虽然可以通过cgroups限制docker使用的资源,但是限制不了其它进程使用docker的资源。
- 在Linux内核中,有很多资源和对象是不能被Namespace化的,最典型的例子是:时间即如果某个容器修改了时间,那整个宿主机的时间都会随之修改
四、通过Linux的 cgroups控制docker进程资源
4.1、cgroups简介
cgroups是Linux的另外一个强大的内核工具,有了cgroups,不仅可以限制被namespace隔离起来的资源,还可以为资源设置权重、计算使用量、操控任务(进程或县城)启停等。说白了就是:cgroups可以限制、记录任务组所使用的物理资源(包括CPU,Memory,IO等),是构建Docker等一系列虚拟化管理工具的基石。
1)查看docker cgroups
$ ls -l /sys/fs/cgroup/*/docker -d
2)cgroups子系统介绍
cpu 子系统,主要限制进程的 cpu 使用率。
cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
memory 子系统,可以限制进程的 memory 使用量。
blkio 子系统,可以限制进程的块设备 io。
devices 子系统,可以控制进程能够访问某些设备。
net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。
3)cgroups 的作用
- 资源限制
cgroups可以对任务使用的资源(内存,CPU,磁盘等资源)总额进行限制。 如 设定应用运行时使用的内存上限,一旦超过配额就发出OOM提示
- 优先级分配
通过分配的CPU时间片数量以及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级
- 资源统计
cgroups可以统计系统的资源使用量 如CPU使用时长,内存用量等,这个功能非常适用于计费
- 任务控制
cgroups 可以对任务进行挂起、恢复等操作
4)使用stress工具压测CPU和内存 使用Dockerfile来创建一个基于Centos的stress工具镜像
$ mkdir -p /opt/stress
$ vi /opt/stress/Dockerfile
FROM centos:7
MAINTAINER chen "liugp@tom.com"
RUN yum install -y wget
RUN wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
RUN yum install -y stress
新建镜像
$ cd /opt/stress/
$ docker build -t centos:stress .
4.5、实验
4.5.1、内存限制
CPU 限制相关参数
选项 | 描述 |
---|
–cpuset-cpus="" | 允许使用的 CPU 集,值可以为 0-3,0,1 | -c,–cpu-shares=0 | CPU 共享权值(相对权重) | cpu-period=0 | 限制 CPU CFS 的周期,范围从 100ms~1s,即[1000, 1000000] | –cpu-quota=0 | 限制 CPU CFS 配额,必须不小于1ms,即 >= 1000 | –cpuset-mems="" | 允许在上执行的内存节点(MEMs),只对 NUMA 系统有效 |
其中–cpuset-cpus用于设置容器可以使用的 vCPU 核。-c,–cpu-shares用于设置多个容器竞争 CPU 时,各个容器相对能分配到的 CPU 时间比例。–cpu-period和–cpu-quata用于绝对设置容器能使用 CPU 时间。
1)创建容器的CPU权重控制
- 默认情况下,每个docker容器的cpu份额都是1024,单独一个容器的份额是没有意义的,只有在同时运行多个容器时,容器cpu的加权效果才能体现出现。
- cgroups只在容器分配的资源紧缺时,即需要对容器使用的资源进行限制时,才会生效。因此,无法单纯的根据某个容器的cpu份额来确定有多少cpu资源分配给它,可以通过cpu share参数可以设置容器使用cpu的优先级,比如启动了两个容器及运行查看cpu的cpu的使用百分比
创建两个容器,分别制定不同的权重比
$ docker run -itd --name cpu512 --cpu-shares 512 centos:stress stress -c 10
$ docker run -itd --name cpu1024 --cpu-shares 1024 centos:stress stress -c 10
$ docker exec -it cpu512 top
$ docker exec -it cpu1024 top
分别进入cpu512和cpu1024之后可以看到,%cpu的比例是1:2,符合我们设置的–cpu-shares参数。
2)cpu core控制
对于多核cpu的服务器,docker还可以控制容器运行使用那些cpu内核,以及使用–cpuset-cpus参数,这对于具有多cpu服务器尤其有用,可以对需要高性能计算的容器进行性能最优的配置。
执行以下命令需要宿主机为双核,表示创建的容器只能使用两个内核/使用哪几个CPU,最终生成cgroup的cpu内核配置如下:
$ docker run -itd --name cpu1 --cpus=2 centos:stress
$ docker run -itd --name cpu1 --cpuset-cpus="0,3" centos:stress
$ docker run -itd --name cpu1 --cpuset-cpus="0-2" centos:stress
查看
$ docker exec -it cpu1 bash
$ cat /sys/fs/cgroup/cpuset/cpuset.cpus
$ docker ps --no-trunc
$ cat /sys/fs/cgroup/cpuset/docker/7ca231149ecb0e06cbabda944697c0787c9a9e1f77565fd001aa978f4b3adede/cpuset.cpus
4.5.2、内存限制
与操作系统类似,容器可使用的内存包括两部分:物理内存和swap;Docker 默认容器交换分区的大小和内存相同;若没有设置memory和memory-swap选项,则该容器可以使用主机的所有内存,没有限制。
内存限制相关的参数
选项 | 描述 |
---|
-m,–memory | 内存限制,格式是数字加单位,单位可以为 b,k,m,g。最小为 4M | –memory-swap | 内存+交换分区大小总限制。格式同上。必须必-m设置的大 | –memory-reservation | 内存的软性限制。格式同上 | –oom-kill-disable | 是否阻止 OOM killer 杀死容器,默认没设置 | –oom-score-adj | 容器被 OOM killer 杀死的优先级,范围是[-1000, 1000],默认为 0 | –memory-swappiness | 用于设置容器的虚拟内存控制行为。值为 0~100 之间的整数 | –kernel-memory | 核心内存限制。格式同上,最小为 4M |
1)允许容器最多使用200M的内存和300M的swap
$ docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
2)容器中的进程最多能使用 500M 内存,在这 500M 中,最多只有 50M 核心内存。
docker run -it -m 500M --kernel-memory 50M ubuntu:16.04 /bin/bash
核心内存
核心内存和用户内存不同的地方在于核心内存不能被交换出。不能交换出去的特性使得容器可以通过消耗太多内存来堵塞一些系统服务。
4.5.3、磁盘IO配额控制
相对于CPU和内存的配额控制,docker对磁盘IO的控制相对不成熟,大多数都必须在有宿主机设备的情况下使用。主要包括以下参数:
选项 | 描述 |
---|
–device-read-bps | 限制此设备上的读速度(bytes per second),单位可以是kb、mb或者gb。 | –device-read-iops | 通过每秒读IO次数来限制指定设备的读速度。 | –device-write-bps | 限制此设备上的写速度(bytes per second),单位可以是kb、mb或者gb。 | –device-write-iops | 通过每秒写IO次数来限制指定设备的写速度。 | –blkio-weight | 容器默认磁盘IO的加权值,有效值范围为10-100。 | –blkio-weight-device | 针对特定设备的IO加权控制。其格式为DEVICE_NAME:WEIGHT |
1)Block IO的限制
默认情况下,所有容器平等地读写磁盘,可以通过设置–blkio-weight参数来改变容器block IO的优先级。
$ docker run -it --name container_A --blkio-weight 600 centos:stress
$ cat /sys/fs/cgroup/blkio/blkio.weight
|