跳转至

02 被隔离的进程:一起来看看容器的本质

你好,我是Chrono。

在上一次课里,我们初步了解了容器技术,在Linux虚拟机里安装了当前最流行的容器Docker,还使用 docker psdocker run等命令简单操作了容器。

广义上来说,容器技术是动态的容器、静态的镜像和远端的仓库这三者的组合。不过,“容器”这个术语作为容器技术里的核心概念,不仅是大多数初次接触这个领域的人,即使是一些已经有使用经验的人,想要准确地把握它们的内涵、本质都是比较困难的。

那么今天,我们就一起来看看究竟什么是容器(即狭义的、动态的容器)。

容器到底是什么

从字面上来看,容器就是Container,一般把它形象地比喻成现实世界里的集装箱,它也正好和Docker的现实含义相对应,因为码头工人(那只可爱的小鲸鱼)就是不停地在搬运集装箱。

图片

集装箱的作用是标准化封装各种货物,一旦打包完成之后,就可以从一个地方迁移到任意的其他地方。相比散装形式而言,集装箱隔离了箱内箱外两个世界,保持了货物的原始形态,避免了内外部相互干扰,极大地简化了商品的存储、运输、管理等工作。

再回到我们的计算机世界,容器也发挥着同样的作用,不过它封装的货物是运行中的应用程序,也就是进程,同样它也会把进程与外界隔离开,让进程与外部系统互不影响。

我们还是来实际操作一下吧,来看看在容器里运行的进程是个什么样子。

首先,我们使用 docker pull 命令,拉取一个新的镜像——操作系统Alpine:

docker pull alpine

然后我们使用 docker run 命令运行它的Shell程序:

docker run -it alpine sh

注意我们在这里多加了一个 -it 参数,这样我们就会暂时离开当前的Ubuntu操作系统,进入容器内部。

现在,让我们执行 cat /etc/os-release ,还有 ps 这两个命令,最后再使用 exit 退出,看看容器里与容器外有什么不同:

图片

就像这张截图里所显示的,在容器里查看系统信息,会发现已经不再是外面的Ubuntu系统了,而是变成了Alpine Linux 3.15,使用 ps 命令也只会看到一个完全“干净”的运行环境,除了Shell(即sh)没有其他的进程存在。

也就是说,在容器内部是一个全新的Alpine操作系统,在这里运行的应用程序完全看不到外面的Ubuntu系统,两个系统被互相“隔离”了,就像是一个“世外桃源”。

我们还可以再拉取一个Ubuntu 18.04的镜像,用同样的方式进入容器内部,然后执行 apt updateapt install 等命令来看看:

docker pull ubuntu:18.04
docker run -it ubuntu:18.04 sh

# 下面的命令都是在容器内执行
cat /etc/os-release
apt update
apt install -y wget redis
redis-server &

这里我就不截图了,具体的结果留给你课下去实际操作体会。可以看到的是,容器里是另一个完整的Ubuntu 18.04 系统,我们可以在这个“世外桃源”做任意的事情,比如安装应用、运行Redis服务等。但无论我们在容器里做什么,都不会影响外面的Ubuntu系统(当然不是绝对的)。

到这里,我们就可以得到一个初步的结论:容器,就是一个特殊的隔离环境,它能够让进程只看到这个环境里的有限信息,不能对外界环境施加影响

那么,很自然地,我们会产生另外一个问题:为什么需要创建这样的一个隔离环境,直接让进程在系统里运行不好吗?

为什么要隔离

相信因为这两年疫情,你对“隔离”这个词不会感觉到太陌生。为了防止疫情蔓延,我们需要建立方舱、定点医院,把患病人群控制在特定的区域内,更进一步还会实施封闭小区、关停商场等行动。虽然这些措施带来了一些不便,但都是为了整个社会更大范围的正常运转。

同样的,在计算机世界里的隔离也是出于同样的考虑,也就是系统安全

对于Linux操作系统来说,一个不受任何限制的应用程序是十分危险的。这个进程能够看到系统里所有的文件、所有的进程、所有的网络流量,访问内存里的任何数据,那么恶意程序很容易就会把系统搞瘫痪,正常程序也可能会因为无意的Bug导致信息泄漏或者其他安全事故。虽然Linux提供了用户权限控制,能够限制进程只访问某些资源,但这个机制还是比较薄弱的,和真正的“隔离”需求相差得很远。

而现在,使用容器技术,我们就可以让应用程序运行在一个有严密防护的“沙盒”(Sandbox)环境之内,就好像是把进程请进了“隔离酒店”,它可以在这个环境里自由活动,但绝不允许“越界”,从而保证了容器外系统的安全。

图片

另外,在计算机里有各种各样的资源,CPU、内存、硬盘、网卡,虽然目前的高性能服务器都是几十核CPU、上百GB的内存、数TB的硬盘、万兆网卡,但这些资源终究是有限的,而且考虑到成本,也不允许某个应用程序无限制地占用。

容器技术的另一个本领就是为应用程序加上资源隔离,在系统里切分出一部分资源,让它只能使用指定的配额,比如只能使用一个CPU,只能使用1GB内存等等,就好像在隔离酒店里保证一日三餐,但想要吃山珍海味那是不行的。这样就可以避免容器内进程的过度系统消耗,充分利用计算机硬件,让有限的资源能够提供稳定可靠的服务。

所以,虽然进程被“关”在了容器里,损失了一些自由,但却保证了整个系统的安全。而且只要进程遵守隔离规定,不做什么出格的事情,也完全是可以正常运行的。

与虚拟机的区别是什么

你也许会说,这么看来,容器不过就是常见的“沙盒”技术中的一种,和虚拟机差不了多少,那么它与虚拟机的区别在哪里呢?又有什么样的优势呢?

在我看来,其实容器和虚拟机面对的都是相同的问题,使用的也都是虚拟化技术,只是所在的层次不同,我们可以参考Docker官网上的两张图,把这两者对比起来会更利于学习理解。

图片

(Docker官网的图示其实并不太准确,容器并不直接运行在Docker上,Docker只是辅助建立隔离环境,让容器基于Linux操作系统运行)

首先,容器和虚拟机的目的都是隔离资源,保证系统安全,然后是尽量提高资源的利用率。

之前在使用VirtualBox/VMware创建虚拟机的时候,你也应该看到了,它们能够在宿主机系统里完整虚拟化出一套计算机硬件,在里面还能够安装任意的操作系统,这内外两个系统也同样是完全隔离,互不干扰。

而在数据中心的服务器上,虚拟机软件(即图中的Hypervisor)同样可以把一台物理服务器虚拟成多台逻辑服务器,这些逻辑服务器彼此独立,可以按需分隔物理服务器的资源,为不同的用户所使用。

从实现的角度来看,虚拟机虚拟化出来的是硬件,需要在上面再安装一个操作系统后才能够运行应用程序,而硬件虚拟化和操作系统都比较“重”,会消耗大量的CPU、内存、硬盘等系统资源,但这些消耗其实并没有带来什么价值,属于“重复劳动”和“无用功”,不过好处就是隔离程度非常高,每个虚拟机之间可以做到完全无干扰。

我们再来看容器(即图中的Docker),它直接利用了下层的计算机硬件和操作系统,因为比虚拟机少了一层,所以自然就会节约CPU和内存,显得非常轻量级,能够更高效地利用硬件资源。不过,因为多个容器共用操作系统内核,应用程序的隔离程度就没有虚拟机那么高了。

运行效率,可以说是容器相比于虚拟机最大的优势,在这个对比图中就可以看到,同样的系统资源,虚拟机只能跑3个应用,其他的资源都用来支持虚拟机运行了,而容器则能够把这部分资源释放出来,同时运行6个应用。

图片

当然,这个对比图只是一个形象的展示,不是严谨的数值比较,不过我们还可以用手里现有的VirtualBox/VMware虚拟机与Docker容器做个简单对比。

一个普通的Ubuntu虚拟机安装完成之后,体积都是GB级别的,再安装一些应用很容易就会上到10GB,启动的时间通常需要几分钟,我们的电脑上同时运行十来个虚拟机可能就是极限了。而一个Ubuntu镜像大小则只有几十MB,启动起来更是非常快,基本上不超过一秒钟,同时跑上百个容器也毫无问题。

不过,虚拟机和容器这两种技术也不是互相排斥的,它们完全可以结合起来使用,就像我们的课程里一样,用虚拟机实现与宿主机的强隔离,然后在虚拟机里使用Docker容器来快速运行应用程序。

隔离是怎么实现的

我们知道虚拟机使用的是Hypervisor(KVM、Xen等),那么,容器是怎么实现和下层计算机硬件和操作系统交互的呢?为什么它会具有高效轻便的隔离特性呢?

其实奥秘就在于Linux操作系统内核之中,为资源隔离提供了三种技术:namespace、cgroup、chroot,虽然这三种技术的初衷并不是为了实现容器,但它们三个结合在一起就会发生奇妙的“化学反应”。

namespace是2002年从Linux 2.4.19开始出现的,和编程语言里的namespace有点类似,它可以创建出独立的文件系统、主机名、进程号、网络等资源空间,相当于给进程盖了一间小板房,这样就实现了系统全局资源和进程局部资源的隔离。

cgroup是2008年从Linux 2.6.24开始出现的,它的全称是Linux Control Group,用来实现对进程的CPU、内存等资源的优先级和配额限制,相当于给进程的小板房加了一个天花板。

chroot的历史则要比前面的namespace、cgroup要古老得多,早在1979年的UNIX V7就已经出现了,它可以更改进程的根目录,也就是限制访问文件系统,相当于给进程的小板房铺上了地砖。

你看,综合运用这三种技术,一个四四方方、具有完善的隔离特性的容器就此出现了,进程就可以搬进这个小房间,过它的“快乐生活”了。我觉得用鲁迅先生的一句诗来描述这个情景最为恰当:躲进小楼成一统,管他冬夏与春秋

小结

好了,今天我们一起学习了容器技术中最关键的概念:动态的容器,再简单小结一下课程的要点:

  1. 容器就是操作系统里一个特殊的“沙盒”环境,里面运行的进程只能看到受限的信息,与外部系统实现了隔离。
  2. 容器隔离的目的是为了系统安全,限制了进程能够访问的各种资源。
  3. 相比虚拟机技术,容器更加轻巧、更加高效,消耗的系统资源非常少,在云计算时代极具优势。
  4. 容器的基本实现技术是Linux系统里的namespace、cgroup、chroot。

课下作业

最后是课下作业时间,给你留两个思考题:

  1. 你能够对比现实中的集装箱,说出容器技术更多的优点吗?
  2. 有一种说法:容器就是轻量级的虚拟机,你认为这种说法正确吗?

欢迎在留言区发言参与讨论,如果你觉得有收获,也欢迎转发给身边的朋友一起学习。我们下节课见。

精选留言(15)
  • Geek_b213f7 👍(82) 💬(1)

    chroot 是在 Unix 和 Linux 系统的一个操作,针对正在运作的软件行程和它的子进程,改变它外显的根目录。一个运行在这个环境下,经由 chroot 设置根目录的程序,它不能够对这个指定根目录之外的文件进行访问动作,不能读取,也不能更改它的内容。 首先创建一个目录rootfs mkdir rootfs 然后在rootfs目录下新建三个文件 cd rootfs touch a b c 此时执行一条命令:chroot /home/centos/rootfs /bin/sh 这条命令意思是启动一个 sh 进程,并且把 /home/centos/rootfs 作为 sh 进程的根目录 此时执行命令:/bin/ls / 就可以显示a b c三个文件,说明当前进程的根目录已经变成了主机上的 /home/centos/rootfs 目录。这样就实现了当前进程与主机的隔离 Namespace Namespace 是 Linux 内核的一项功能,该功能对内核资源进行隔离,使得容器中的进程都可以在单独的命名空间中运行,并且只可以访问当前容器命名空间的资源。Namespace 可以隔离进程 ID、主机名、用户 ID、文件名、网络访问和进程间通信等相关资源。 Docker 主要用到以下五种命名空间。 pid namespace:用于隔离进程 ID。 net namespace:隔离网络接口,在虚拟的 net namespace 内用户可以拥有自己独立的 IP、路由、端口等。 mnt namespace:文件系统挂载点隔离。 ipc namespace:信号量,消息队列和共享内存的隔离。 uts namespace:主机名和域名的隔离。 Cgroups Cgroups 是一种 Linux 内核功能,可以限制和隔离进程的资源使用情况(CPU、内存、磁盘 I/O、网络等)。在容器的实现中,Cgroups 通常用来限制容器的 CPU 和内存等资源的使用。 联合文件系统 联合文件系统,又叫 UnionFS,是一种通过创建文件层进程操作的文件系统,因此,联合文件系统非常轻快。Docker 使用联合文件系统为容器提供构建层,使得容器可以实现写时复制以及镜像的分层构建和存储。常用的联合文件系统有 AUFS、Overlay 和 Devicemapper 等。

    2022-07-13

  • lesserror 👍(45) 💬(2)

    老师,以下这些内容会讲到吗? 我之前在了解这些概念的时候云里雾里的,希望能看到老师更加形象的讲解。 1. 容器中的数据。 2. 使用已命名的卷永久保存数据。 3. 使用绑定装载。 4. 查看映像层。 5. 缓存依赖项。 6 .了解多阶段生成。

    2022-06-24

  • YueShi 👍(27) 💬(1)

    普通的进程 + namespace(一重枷锁,能看到什么进程) + cgroup(二重枷锁,能用多少资源,内存/磁盘。cpu等) + chroot(三重枷锁,能看到什么文件)= 特殊的进程 = 容器

    2022-09-18

  • hiDaLao 👍(16) 💬(1)

    执行docker run -it ubuntu:18.04 sh后,在容器中执行ps,发现sh的pid为1,在宿主机上执行ps auxf | grep sh,发现是能看到容器内的这个sh进程的,但pid不同。此时在容器内执行apt update,宿主机上ps同样能看到,而且是sh的子进程。说明用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过通过namespace对这些进程做了特殊的隔离

    2022-07-23

  • 罗耀龙@坐忘 👍(11) 💬(1)

    认同“容器就是轻量级的虚拟机”?不 因为容器本质是一个进程,带着一组“隔离与限制”的进程,与虚拟机差的太远了。

    2022-06-24

  • 马以 👍(6) 💬(1)

    1:集装箱在码头装船的时候,只要我的船只是匹配集装箱标准的船,就能无缝的装载集装箱,同样容器也是一种标准,我的船(组件)符合这种标准,我就能和容器无缝链接;比如docker-compose和后面的kubernetes,我只需要实现我的容器接口来对接容器就可以了; 2:我觉得容器和虚拟机有着本质的区别,虚拟机是虚拟出一套软硬件系统环境,我们的应用跑在虚拟机中,可以大致看作是跑在一个独立的服务器中,而容器只是一个进程,他们存在本质上的区别;如果硬要说他们的相同点,那么只是在隔离性这个广义的角度上,他们所做的事情是类似的。

    2022-06-27

  • Geek_093b9e 👍(5) 💬(1)

    罗老师, 您好, 我读了一些文章,chroot 这个系统调用应该是虚拟化技术的开端(1979年), 后来慢慢有个更好的在内核层面的虚拟化技术,比如出现于2008年的LXC(基于namespace, cgroup), docker在LXC上面做了又一层封装,所以我理解当前docker底层没有用到chroot。 如有错误,请您指正。 谢谢 参考连接: https://www.codementor.io/blog/docker-technology-5x1kilcbow https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016

    2022-08-07

  • Andrew 👍(5) 💬(2)

    老师,如果镜像的大小是几十MB,再基于这个镜像运行10个容器所占用的硬盘大小就是*10 ?

    2022-06-28

  • M 👍(5) 💬(4)

    没有介绍如何使用,这三个技术具体实现隔离效果吗?

    2022-06-24

  • 星垂平野阔 👍(4) 💬(4)

    写作业: 1.容器就像预备装船的成堆集装箱,能够把船舱塞得满满当当的,因为它是直接运行在主机上的进程,主机有多少资源它就能用多少资源—等价于船舱有多大它能装多大。 2.容器不是轻量级的虚拟机,它并不是在主机上再抽象出一层操作系统。 课后感想: 运用容器也有好几年了,始终觉得这套技术非常有特色,能够把G级别的操作系统用分层文件的形式压缩成几十M的一个小进程,那么对比下载的iso操作系统它到底是少了哪些部分呢?

    2022-06-24

  • 温雅小公子 👍(3) 💬(1)

    如果我在容器里 rm -rf/*,会对外面的系统有影响吗

    2022-10-25

  • Demon.Lee 👍(2) 💬(1)

    1、对比集装箱,容器技术的优点就是标准化,有了标准化,后面的就能实现自动化,规模化:具体(样例)--> 抽象(模型)--> 简单 --> 标准(流程)--> 自动(程序)--> 规模。 2、容器就是轻量级的虚拟机,可以这么认为,不过也不必咬文嚼字,我们理解它和虚拟机之间的区别就好。 3、chroot,Namespace 应该属于“看起来”隔离的技术,而 Cgroups 则属于“用起来”隔离的技术。 4、李晓峰老师在《虚拟机设计与实现》提到了虚拟机的几种类型,部分摘录如下: 1)ISA Level Virtualization,指令集虚拟化,即仿真。通过软件来模拟 ISA 架构的处理过程,该软件会将虚拟机发出的指令转换为本机 ISA 的指令,比如 QEMU,Bochs; 2)Hardware Abstraction Level Virtualization:硬件抽象层虚拟化,即以软件或硬件来模拟计算机架构(cpu、memory、显卡、磁盘控制器和芯片组等)的工作过程,比如 VMware,VirtualBox; 3)OS Level Virtualization:操作系统层虚拟化,即容器化。各个进程共享内核,但拥有独立的系统资源,比如 Docker, containerd; 4)Library Level Virtualization:运行库虚拟化,用软件翻译的方法来模拟系统,通过一个独立进程来代替操作系统内核,比如 WINE,WSL; 5)Programming Language Level Virtualization:语言层虚拟化,即由虚拟机将高级语言生成的中间代码,转换为目标机器可以直接执行的指令,比如 Java 的 JVM,.NET 的 CLR。

    2022-11-16

  • Amark 👍(2) 💬(2)

    老师,有一个问题,docker 基于cgoup与namespace技术是操作系统本身就有的功能,那为啥docker在十年内才被创造出来,而不是在虚拟机之前呢,这中间还有什么技术没有成熟吗

    2022-07-11

  • Even 👍(1) 💬(1)

    1. 类比集装箱,集装箱就是为了标准化,能够在各种船舶上都放置,搬动,迁移方便,不依赖于具体宿主(船舶)。容器也是这样,跨平台,标准化,方便环境的部署,迁移。 2. 虚拟机跟容器如果只是说隔离性等,可以模糊的等于。或者说容器是更轻量化的虚拟机。都是属于不同时代下的进化产物。

    2022-10-08

  • pythonbug 👍(1) 💬(1)

    1、像现实中集装箱的话,我认为一个最大的特点就是适合搬运,说明容器里面装的东西迁移起来会十分方便。 2、我觉得轻量级虚拟机这种说法不太准确,按照那个图,容器像是共用了宿主机的底层文件系统,和虚拟机那种完全从虚拟机开始模拟还是有很大区别的,不是一回事。

    2022-07-06