06 | 白话容器基础(二):隔离与限制

本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。

一、隔离

上节说明了:Namespace技术实际上修改了应用进程看待整个计算机“视图”,即它的视线被操作系统限制,只能看到某些指定的内容。
对于宿主机来说,这些被隔离了的进程和其他进程并没有太大的区别。

在这里插入图片描述

在这个虚拟机和容器的技术对比图中,不应该把Docker Engine 或者任何容器管理工具放在跟Hypervisor 相同的位置。因为他们并不像Hypervisor 那样对应用的隔离环境负责,也不会创建任何实体的容器,真正对隔离环境负责的是宿主机操作系统本身。

所以在这个对比图中,应该把Docker 画在跟应用同级别并且靠边的位置。
这意味着,用户运行在容器中的进程,和宿主机上的其他进程一样,都是由宿主机操作系统统一管理的。
只不过这些被隔离的进程拥有额外设置过的Namespace 参数。
而Docker 扮演的角色就是,更多的旁路式的辅助和管理工作。

在后续分享CRI 和 容器运行时,像Docker这样的角色甚至可以去掉。


这样的架构也解释了为什么Docker 项目比虚拟机更加受到欢迎的原因。

虚拟化技术作为应用沙盒,必须由Hypervisor 来负责创建虚拟机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的 Guest OS 才能执行用户的应用进程。这就不可避免的带来了额外的资源消耗和占用。

根据实验一个运行着Centos 的KVM 虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用 100MB~200MB 的内存。
用户进程运行在虚拟机里面,它对宿主机操作系统的调用就不可避免的要经过虚拟化软件的拦截和处理。这本身又是一层性能消耗,尤其是对计算资源,网络和磁盘I/O的损耗很大。

而容器化后的用户应用,依然是宿主机上的一个普通进程,意味着虚拟化带来的性能损耗是不存在的。
另一方面使用Namespace 作为隔离手段的容器并不需要单独的Guest OS,这就使得容器额外的资源占用几乎可以忽略不计。

所以敏捷和高性能是容器相比较虚拟机的最大的优势,也是它能够在paas这种更细粒度的资源管理平台上大行其道的重要原因。

基于Linux Namespace 的隔离机制相比较虚拟化技术有很多不足之处,最主要的问题就是 隔离的不彻底。

既然容器是运行在宿主机上的一种特殊进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。

尽管你可以在容器里通过Mount Namespace 单独挂载不同操作系统的文件,比如Centos和Ubuntu,但这并不能改变共享宿主机内核的事实。
这也意味着,你在Windows 宿主机上运行Linux 容器,或者在低版本的Linux 宿主机上运行高版本的Linux 容器,都是行不通的。

而相比之下,拥有硬件虚拟化技术和独立Guest OS的虚拟机就要方便的多了。
例子是,Microsoft 的云计算平台 Azure,实际上就是运行在 Windows 服务器集群上的,但这并不妨碍你在它上面创建各种 Linux 虚拟机出来。

Linux 内核中,有很多资源对象是不能被Namespace 化的,比如时间
容器中程度调用 settimeofday(2)系统调用修改了时间,整个宿主机的时间也会被随之修改。
这显然没有在虚拟机里随便折腾的自由度高。

由于上述问题,尤其是共享宿主机内核的事实,容器给应用暴露出来的攻击面是相当大的,应用的越狱难度也比虚拟机低得多。

尽管在实践中我们可以使用Seccomp等技术,对容器内部发起的所有的系统调用进行过滤和甄别里进行安全加固,但这种方法因为多了一层对系统调用的过滤,必然会拖累容器的性能。
何况,在默认情况下,谁也不知道到底该开启哪些系统调用,关闭哪些。

所以没人敢把运行在物理机上的Linux 容器直接暴露到公网上,后续会讲到的基于虚拟化或者独立内核技术的容器实现,则可以比较好的在隔离与性能之间做出平衡。

二、限制

2.1、为什么需要限制呢?

虽然进程之间被隔离了,但是在宿主机上进程之间依然是平等的竞争关系。
所有的进程都可以使用宿主机上的资源(CPU\内存),某个进程也可以把宿主机上的所有资源都占用光。这样就会影响其他进程。

Linux Cgroups 就是Linux 内核中用来为进程设置资源限制的一个重要功能。
Linux Cgroups 的全称是Linux Control Group,它最主要的作用,就是限制一个进程组能够使用的资源上限,包括CPU,内存,磁盘,网络等

Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。今天先不说这些。

在Linux中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

在机器上,使用 mount 指令把他们展示出来。

$ mount -t cgroup 
cpuset on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
cpuacct on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct)
blkio on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
...

它的输出结果是一系列文件系统目录,如果在自己的机器上没有看到这个,就需要先去自己挂载Cgroups。 一般正常的机器都是有的。

/sys/fs/cgroup 下面有很多cpuset,cpu,memory 这样的子目录,也叫子系统。
这些都是这台机器可以被Cgroups 进行限制的资源种类。

在具体的种类的目录下面,有不同的文件里可以设置具体的限制方法。例如CPU这个

$ ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us  cpu.shares notify_on_release
cgroup.procs      cpu.cfs_quota_us  cpu.rt_runtime_us cpu.stat  tasks

例如cfs_period 和 cfs_quota 两个配置需要组合使用,用来限制进程在长度为cfs_period的一段时间内,只能被分配到总量为 cfs_quota 的CPU时间。

2.2、如何使用呢?

在对应的子系统目录中,创建一个目录

root@ubuntu:/sys/fs/cgroup/cpu$ mkdir container
root@ubuntu:/sys/fs/cgroup/cpu$ ls container/
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us  cpu.shares notify_on_release
cgroup.procs      cpu.cfs_quota_us  cpu.rt_runtime_us cpu.stat  tasks

container 这个目录就称为一个控制组,这个目录下回自动生成该子系统对应的资源限制文件。

在后台执行一条脚本:

$ while : ; do : ; done &
[1] 226

这是个死循环,可以把CPU吃到100%,进程的PID 是 226.

$ top
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

查看CPU 是否被打满。

此时查看 container 控制组中的CPU 设置,quota 没有任何限制(-1),CPU period 是默认的 100ms (100000 us)

$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us 
100000

接下来,我们可以通过修改这些文件的内容来设置限制。

向 container 组里的cfs_quota 文件写入 20ms(20000us):

$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

该设置表示:
在每100ms的时间里,被该控制组限制的进程只能使用20ms 的CPU时间,也就是这个进程只能使用20%的CPU资源。

接下来,把限制的进程的PID 写入到container 组里的tasks 文件,上面的设置就会对该进程失效了。

$ echo 226 > /sys/fs/cgroup/cpu/container/tasks 

然后使用 top 查看下CPU 使用率:

$ top
%Cpu0 : 20.3 us, 0.0 sy, 0.0 ni, 79.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

可以看到CPU 使用率立刻降到了20%(%Cpu0 : 20.3 us)。

除了CPU在系统之外,Cgroups 每一个子系统都有独有的资源限制的能力,比如:

  • blkio :为块设备设定I/O限制,一般用于磁盘等设备
  • cpuset:为进程分配单独的CPU核和对应的内存节点
  • memory:为进程设定内存使用的限制

简单粗暴的理解,它是一个子系统目录加上一组资源限制文件的组合。

对于Docker 等容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的PID填写到对应控制组中的tasks文件中。

在启动docker容器的时候,可以使用命令来设置资源的限制值:

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

然后就可以到对应的容器中的控制组中查看:

$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us 
100000
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us 
20000

这就意味着这个 Docker 容器,只能使用到 20% 的 CPU 带宽。

三、总结

一个正在运行的docker容器,其实就是一个启用了多个Linux Namespace 的应用进程,而这个进程能使用到的资源量,则受到Cgroups的限制

容器是一个单进程模型

用户的应用进程实际上就是容器里PID=1 的进程,也是其他后续创建的所有进程的父进程。

这意味着,你没办法同时运行两个不同的应用。
除非这个PID=1的进程充当两个不同应用的父进程。

这也是为什么有些人使用 systemd 或者 supervisord 这样的软件来代替应用本省作为容器的启动进程。

容器本身的设计,就是希望容器和应用能够同生命周期。

众所周知,Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如 CPU 使用情况、内存占用率等,这些文件也是 top 指令查看系统信息的主要数据来源。

但是,你如果在容器里执行 top 指令,就会发现,它显示的信息居然是宿主机的 CPU 和内存数据,而不是当前容器的数据。

造成这个问题的原因就是,/proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在。

在生产环境中,这个问题必须进行修正,否则应用程序在容器里读取到的 CPU 核数、可用内存等信息都是宿主机上的数据,这会给应用的运行带来非常大的困惑和风险。

这也是在企业中,容器化应用碰到的一个常见问题,也是容器相较于虚拟机另一个不尽如人意的地方。

四、评论

1、
问题:
1 之前遇到过,但是没有考虑如何解决,临时抱佛脚,查了 lxcfs,尝试回答一下。top 是从 /prof/stats 目录下获取数据,所以道理上来讲,容器不挂载宿主机的该目录就可以了。lxcfs就是来实现这个功能的,做法是把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到Docker容器的/proc/meminfo位置后。容器中进程读取相应文件内容时,LXCFS的FUSE实现会从容器对应的Cgroup中读取正确的内存限制。从而使得应用获得正确的资源约束设定。kubernetes环境下,也能用,以ds 方式运行 lxcfs ,自动给容器注入争取的 proc 信息。
2 用的是vanilla kubernetes,遇到的主要挑战就是性能损失和多租户隔离问题,性能损失目前没想到好办法,可能的方案是用ipvs 替换iptables ,以及用 RPC 替换 rest。多租户隔离也没有很好的方法,现在是让不同的namespace调度到不同的物理机上。也许 rancher和openshift已经多租户隔离。

回复:
课代表出现了!

2、
问题:
/proc文件系统的问题我好像遇到过这个坑…当时在容器上运行的java应用,由于当时jvm参数没正确配置上,就用默认的,而容器设置的内存为4g,最后oom了,当时用命令查看容器的内存占用情况,竟然发现内存竟然有60多g。 那应该显示的是宿主机的内存了,jvm按照宿主机内存大小分配的默认内存应该大于4g 所以还没full gc 就oom了

回复:
这个问题确实很普遍

3、
问题:
是不是可以理解:一个docker里面跑的进程都是docker这个进程的子进程?

回复:
不。是entrypoint进程的子进程。docker基本上是旁路控制的作用。

4、
问题:
请教个问题,cgroups除了限制资源上限,能否锁定下限?如果不能,那不是很容易被抢资源?

回复:
只有上限。所以才需要kubernetes 来帮你做调度嘛。

5、
问题:
为什么说容器中无法同时运行两个不同的应用?实际上可以的。设置CMD参数为一个脚本,脚本中启动多个不同的进程。或者通过exec执行bash进入容器后,手动启动多个在后台运行的进程。

回复:
试想一下,你这么运行起来的后台进程异常退出后,你如何知晓?由于没没有真正的init进程,贸然这么运行起来的孤儿进程是非常棘手的

6、
问题:
容器内部还能再做namespace和cgroup么?也就是容器中再做docker

回复:
没问题。但是要挂载对应的文件系统,开启需要的权限。

7、
问题:
老师,讲的太好了,言简易懂,每天都在期待新的一节课。 请教一个问题我们通常dockerpull下来的基础镜像比如:centos 7,这个镜像里面没有内核信息是吧?但是封装了systemd等进程管理工具,使得产生的新的进程都是一号进程的子进程,执行ping netstat命令,也会是这个容器一号进程的子进程? 老师这样理解对吗?

回复: 没毛病

8、
问题:
docker的实现原理也仅仅是调用了底层的namespace和cgroup吗,然后加上一些其他的优化和特性。

回复:
准确的说,是调用containerd + runc。runc干你说的这些事儿。

9、
问题:
如果docker run时没有指定cpu quota,默认是不限制的吧 (-1)

回复:

10、
问题:
一个容器本质上只是一个进程,那么不能同时启动两个应用?这个能解释一下吗? 如果我打包一个docker镜像 里面安装了redis和tomcat 那就不能跑在容器里了?

回复:
能。但至少一个进程会无法管理。

11、
问题:
想问下,windows10中的linux子系统是如何实现的?

回复:
应该是API翻译,即,把linux syscall翻译成windows syscall

12、
问题:
其实,虚拟机对于宿主机来说也是进程,限制虚拟机的资源使用也可以用cgroup.

回复:
不。宿主机看到的qemu进程,而不是应用的进程。这是有本质区别的。

13、
问题:
老师您好,“这意味着,如果你要在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。”这段话有些不解,我的macbook pro可以运行最新版本的linux的docker,感觉是不是跟您这句话有冲突

回复:
mac docker其实是个虚拟机。你不觉得它卡吗?哈哈。

14、
问题:
“用户的应用进程实际上就是容器里 PID=1 的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的 PID=1 的程序来充当两个不同应用的父进程”,这里不是很懂,为啥找到一个公共的pid=1的夫进程就行了呢?即使找到了,两个子进程也不能作为同一个容器管理吧?还有这个公共的pid=1中所谓的“公共”指的是在容器中还是在宿主机中的公共呀?求指导~

回复:
当然是容器里。比如用systemd当公共父进程

15、
问题:
我看到docker可以Windows上运行linux容器,在linux上运行Windows容器的情况,如果本质上都是使用宿主机的内核请问它是怎样做到的呢

回复:
其实是个虚拟机

16、
问题:
请问宿主机的/etc/hosts文件也和/proc类似么,最近遇到个问题,在init-container里修改了hosts文件的权限,在同一个pod中的container中hosts权限也发生了改变,但是在init-container中修改hosts文件内容的话,同一个pod中的container中的hosts文件内容却没有发生改变,这怎么解释?
2018-09-26
 作者回复
不通同容器的文件系统是隔离的啊。至于权限,你想想它该归哪个namespace 管呢?

2018-09-27

还有有点不太明白,我理解同一个pod中的container只有网络命名空间是共享的啊,难道容器镜像init层的文件也位于同一个volume命名空间吗

回复:
你的实验不是已经验证了,容器之间的文件系统就是隔离的啊。但是,既然要共享network ns,那多个容器的hosts文件得一样啊。所以,pod里的hosts其实是一个宿主机上的文件,容器一起bind mount这个文件即可。service account的道理也是类似的。

17、
问题:
systemd 或者 supervisord 这样的软件来代替应用本身作为容器的启动进程
这样启动的容器,Cgroup限制的只是supervisord的资源呢?还是容器内所有进程的总资源?

回复:
看你设置的被限制进程号了

18、
问题:
我创建container目录实验之后直接使用rm -rf无法删除container目录,我想知道如果是在docker run的时候限制了cpu使用,当删除这个容器,它对应的cpu限制的文件也会删掉吗?不删除的话会不会这个目录下的文件会越来越多

回复:
要unmount

19、
问题:
不好意思,前面的留言有些错误,我的意思是Dockerfile中一条CMD运行一个脚本,脚本里有两条运行进程的指令,前一条后台运行,第二条前台运行,这样应该可以实现容器在建立的时候运行两个进程了吧?还是说脚本执行的时候其实是一个父进程fork出了两个子进程?正好符合了你说的第二句话“除非你能事先找到一个公共的PID=1的程序来充当两个不同应用的父进程”

回复:
都是子进程。但后台运行的那个进程要管理起来就麻烦了。

20、
问题:
为什么说容器中无法同时运行两个不同的应用?实际上可以的。设置CMD参数为一个脚本,脚本中启动多个不同的进程。或者通过exec执行bash进入容器后,手动启动多个在后台运行的进程。

这个问题的话,可以使用健康检查来进行判断吗?

回复:
当然不可以,您这PID=1的进程根本没管理后台进程的能力。

21、
问题:
cgroups目录留下的文件如何有效的清理呢?

回复:
umount后删了即可

22、
问题:
张老师,请问利用cgroup做内存限制,如果容器内的进程消耗的内存比较高,会导致容器进程假死吗?有什么更好的解决方法呢?谢谢

回复:
会的,做好健康检查

23、
问题:
也就是对于/etc/hosts这个文件而言,同一个pod中不同container修改权限时是修改的宿主机的文件权限,而修改hosts文件内容时,修改的是各自的文件么?

回复:
不是……hosts跟volume是一样的,所有容器共同mount了同一份。但是这个文件的内容,是kubelet维护的,在每个容器创建的时候,kubelet会重写它里面的内容。这就是为啥我说不让你手动改hosts……

24、
问题:
试想一下,你这么运行起来的后台进程异常退出后,你如何知晓?由于没有真正的init进程,贸然这么运行起来的孤儿进程是非常棘手的

  1. 正常是如何知晓的呢,应用退出直接导致容器退出?
  2. 正常情况下我们在容器里可以随便启动各种后台进程,所以那是不建议的?
  3. 请问对富容器有何看法呢?

回复:
容器直接退出并且可以收到报错。不建议。属于过渡时期遗留环境下的特殊方案。

25、
问题:
老师,云主机有共享和独享,独享的就是实打实分配指定的cpu和内存,不会受其他租户影响。而容器能这样分配吗?

回复:
其实,无论虚拟机还是容器,都不可能实打实。当然,容器的更 虚。

26、
问题:

  1. 所以除了时间,还有什么比较常见是不能被Namespace化的呢?
  2. 所以平时我们 docker run 的时候,是默认本身就有一些 Cgroups 的限制,还是我们指定了参数才会收到限制?

回复:
再比如kernel keyring。指定了才有限制。

27、
问题:
容器的本质是一个进程,那所有的容器的image是不是都基于一个比较小的linux内核?因为进去容器之后能看到类似文件系统目录,还能执行很多操作系统命令。

回复:
没有内核。详见下一节镜像的讲解。

28、
问题:
请问宿主机的/etc/hosts文件也和/proc类似么,最近遇到个问题,在init-container里修改了hosts文件的权限,在同一个pod中的container中hosts权限也发生了改变,但是在init-container中修改hosts文件内容的话,同一个pod中的container中的hosts文件内容却没有发生改变,这怎么解释?

回复:
不通同容器的文件系统是隔离的啊。至于权限,你想想它该归哪个namespace 管呢?

29、
问题:
docker使用cgroup限制资源和K8s提供限制资源的功能有什么区别呢?

回复:
一个东西啊,为啥会觉得kubernetes 不用docker呢?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值