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

容器到底是什么?

根据官方的解释,我们可以把容器(Container),比作现实世界里的集装箱,集装箱的作用是标准化封装各种货物,一旦打包完成之后,就可以从一个地方迁移到任意的其他地方。相比散装形式而言,集装箱隔离了箱内箱外两个世界,保持了货物的原始形态,避免了内外部相互干扰,极大地简化了商品的存储、运输、管理等工作。
再回到我们的计算机世界,容器也发挥着同样的作用,不过它封装的货物是运行中的应用程序,也就是进程,同样它也会把进程与外界隔离开,让进程与外部系统互不影响。

结合实际操作

使用 docker pull 拉取操作系统Alpine的镜像,再使用 docker run -it alpine sh 命令来运行这个操作系统的 shell 程序,这里添加了 -it 参数,表示我们会暂时离开 Ubuntu 操作系统,进入容器内部。
在容器内部使用 cat /etc/os-release 和 ps 命令查看系统信息和进程:
image.png
通过查看系统信息和进程列表得知:当前的系统不在是 Ubuntu 系统,而是 Alpine Linux 3.15,进程列表也只有 shell 进程运行。
也就是说在容器内部是一个全新的 Alpine 操作系统,在这里运行的应用程序完全看不到外面的 Ubuntu 系统,两个系统是互相隔离的。
我们也可以再拉取一个 Ubuntu 系统的镜像,用同样的方式可以看到容器里是一个全新的完整的 Ubuntu 系统,我们同样可以在这个系统里进行服务的部署,无论我们做什么,都不会影响外面的 Ubuntu 系统。
到这里我们可以得出一个结论:容器,就是一个特殊的隔离环境,它能够让进程只看到这个环境里的有限信息,并且不会影响外界环境。

为什么要隔离?

作者举了疫情隔离的例子感觉非常贴切,为了防止疫情蔓延,影响社会的正常运转,把病例隔离在特定的场所。
对于计算机而言,隔离也是出于对系统安全的考虑。
image.png
使用容器技术,我们就可以让应用程序运行在一个有严密防护的“沙盒(Sandbox)”环境内,就好像是把进程请进了“隔离酒店”,它可以在这个环境里自由活动,但绝不允许“越界”,从而保证了容器外系统的安全。
容器技术的另一个本领就是为应用程序加上资源隔离,在系统里切分出一部分资源,让它只能使用指定的配额,比如只能使用一个 CPU,只能使用 1GB 内存等等。
这样就可以避免容器内进程的过度系统消耗,充分利用计算机硬件,让有限的资源能够提供稳定可靠的服务。所以,虽然进程被“关”在了容器里,损失了一些自由,但却保证了整个系统的安全。而且只要进程遵守隔离规定,不做什么出格的事情,也完全是可以正常运行的。

与虚拟机的区别是什么?

参看 Docker 官网的一张对比图:
image.png

相同与不同点:

  • 容器和虚拟机的目的都是隔离资源,保证系统安全,然后是尽量提高资源的利用率。
  • 虚拟机能够在宿主机系统里完整虚拟化出一套计算机硬件,在里面还能够安装任意的操作系统,这内外两个系统也同样是完全隔离,互不干扰。
  • 从实现的角度来看,虚拟机虚拟化出来的是硬件,需要在上面再安装一个操作系统后才能够运行应用程序,而硬件虚拟化和操作系统都比较“重”,会消耗大量的 CPU、内存、硬盘等系统资源,但这些消耗其实并没有带来什么价值,属于“重复劳动”和“无用功”,不过好处就是隔离程度非常高,每个虚拟机之间可以做到完全无干扰。
  • 容器(即图中的 Docker),它直接利用了下层的计算机硬件和操作系统,因为比虚拟机少了一层,所以自然就会节约 CPU 和内存,显得非常轻量级,能够更高效地利用硬件资源。不过,因为多个容器共用操作系统内核,应用程序的隔离程度就没有虚拟机那么高了。
  • 运行效率,可以说是容器相比于虚拟机最大的优势,在这个对比图中就可以看到,同样的系统资源,虚拟机只能跑 3 个应用,其他的资源都用来支持虚拟机运行了,而容器则能够把这部分资源释放出来,同时运行 6 个应用。
  • 一个普通的 Ubuntu 虚拟机安装完成之后,体积都是 GB 级别的,再安装一些应用很容易就会上到 10GB,启动的时间通常需要几分钟,我们的电脑上同时运行十来个虚拟机可能就是极限了。而一个 Ubuntu 镜像大小则只有几十 MB,启动起来更是非常快,基本上不超过一秒钟,同时跑上百个容器也毫无问题。

    隔离是怎么实现的?

    我们知道虚拟机使用的是 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 就已经出现了,它可以更改进程的根目录,也就是限制访问文件系统,相当于给进程的小板房铺上了地砖

    总结:

  • 容器就是操作系统里一个特殊的“沙盒”环境,里面运行的进程只能看到受限的信息,与外部系统实现了隔离。

  • 容器隔离的目的是为了系统安全,限制了进程能够访问的各种资源。
  • 相比虚拟机技术,容器更加轻巧、更加高效,消耗的系统资源非常少,在云计算时代极具优势。
  • 容器的基本实现技术是 Linux 系统里的 namespace、cgroup、chroot。