我一直在反复阅读Docker文档,试图理解Docker和完整虚拟机之间的区别。它如何在不过重的情况下提供完整的文件系统、隔离的网络环境等等。
为什么将软件部署到Docker镜像(如果这是正确的术语)比仅仅部署到一致的生产环境更容易呢?
我一直在反复阅读Docker文档,试图理解Docker和完整虚拟机之间的区别。它如何在不过重的情况下提供完整的文件系统、隔离的网络环境等等。
为什么将软件部署到Docker镜像(如果这是正确的术语)比仅仅部署到一致的生产环境更容易呢?
好的,让我来解释一下。你需要一个基础镜像,然后进行更改,并使用Docker提交这些更改,它会创建一个新的镜像。这个镜像只包含与基础镜像不同的部分。当你想要运行这个镜像时,你还需要基础镜像,并使用分层文件系统将你的镜像叠加在基础镜像之上:正如上面提到的,Docker使用AuFS。AuFS将不同的层合并在一起,你就可以得到想要的内容;你只需要运行它即可。你可以继续添加更多的镜像(层),它将继续仅保存差异。由于Docker通常是在registry中构建基于现成镜像的,因此你很少需要自己“快照”整个操作系统。
了解虚拟化和容器在低层级上的工作原理可能会很有帮助。这将澄清很多事情。
注意:下面的描述有些简化。更多信息请参见参考资料。
虚拟化在低层级上是如何工作的?
在这种情况下,虚拟机管理器接管CPU环 0(或新型CPU中的“根模式”)并截获客户操作系统发出的所有特权调用,以创建客户操作系统拥有自己硬件的假象。有趣的事实:在1998年之前,人们认为在x86架构上无法实现这一点,因为没有办法进行此类截获。VMware团队 是第一个 想到了重写客户操作系统特权调用的内存可执行字节来实现这一点。
总体效果是,虚拟化允许您在同一硬件上运行两个完全不同的操作系统。每个客户操作系统都经历引导、加载内核等所有过程。您可以拥有非常严格的安全性。例如,客户操作系统无法完全访问主机操作系统或其他客户操作系统并破坏其中的内容。
容器在低层级别如何工作?
大约在 2006 年,一些谷歌员工们实现了一个新的内核级特性,叫做 命名空间(然而这个想法早在之前就在FreeBSD中存在)。操作系统的一个功能是允许进程共享全局资源,比如网络和磁盘。如果这些全局资源被包装在命名空间中,那么它们只能被运行在同一命名空间的进程所看到。例如,你可以获得一块磁盘并将其放入命名空间X中,然后运行在命名空间Y中的进程无法看到或访问它。同样地,运行在命名空间X中的进程无法访问分配给命名空间Y的任何内存。当然,命名空间X中的进程也无法看到或与命名空间Y中的进程交互。这为全局资源提供了一种虚拟化和隔离方法。这就是 Docker 的工作原理:每个容器都在自己的命名空间中运行,但使用的是完全相同的内核。隔离是因为内核知道分配给进程的命名空间,在 API 调用过程中,它确保进程只能访问自己命名空间中的资源。
容器与虚拟机的限制现在应该很明显了:你不能像在虚拟机中那样在容器中运行完全不同的操作系统。但是,你可以运行不同的Linux发行版,因为它们共享相同的内核。隔离级别不如虚拟机强。事实上,在早期实现中,“客户”容器有一种方式可以接管主机。此外,当你加载一个新容器时,整个操作系统的副本并不像在虚拟机中那样启动。所有容器共享相同的内核。这就是为什么容器非常轻量的原因。与虚拟机不同的是,你不必为容器预先分配大量内存,因为我们不运行操作系统的新副本。这使得在一个操作系统上运行数千个容器成为可能,同时对它们进行沙箱处理,如果我们在自己的虚拟机中运行单独的操作系统副本,则可能不可能。我喜欢 Ken Cochrane 的回答。
但我想增加另外一个角度,这里没有详细讨论。在我的观点中,Docker 的整个过程也有所不同。相较于虚拟机,Docker 不仅仅是关于硬件资源的最佳共享,更重要的是它提供了一个打包应用的“系统”(最好是一组微服务,但不是必须的)。
对我来说,它填补了开发者工具(如rpm、Debian软件包、Maven、npm + Git 等)和运维工具(如Puppet、VMware、Xen 等)之间的空缺。
为什么将软件部署到 Docker 镜像中(如果这是正确的术语)比仅部署到一致的生产环境更容易?
你的问题假设存在一致的生产环境。但如何保持其一致性呢? 考虑一些数量(>10)的服务器和应用程序,以及管道中的阶段。
为了保持同步,你将开始使用诸如 Puppet、Chef 或自己的配置脚本、未发布的规则和/或大量文档... 理论上,服务器可以无限期地运行,并保持完全一致和最新。实践失败了,无法完全管理服务器的配置,因此存在相当大的配置漂移范围,以及对正在运行的服务器的意外更改。
因此,有一种被称为不可变服务器的已知模式可以避免这种情况。但是,由于 Docker 之前使用的 VM 的限制,不可变服务器模式并不受欢迎。处理几个 GB 大小的镜像、移动这些大型镜像以更改应用程序中的某些字段非常麻烦,这是可以理解的...
在 Docker 生态系统中,您永远不需要在“小更改”时移动几个 GB(感谢 aufs 和 Registry),而且您不需要担心将应用程序打包到 Docker 容器中会降低性能。您也不需要担心该镜像的版本。
最后,您甚至通常可以在 Linux 笔记本电脑上重现复杂的生产环境(如果在您的情况下无法工作,请勿联系我 ;))
当然,您可以在 VM 中启动 Docker 容器(这是一个好主意)。在 VM 级别上减少服务器配置。所有这些都可以由 Docker 管理。
P.S. 同时,Docker 使用其自己的实现“libcontainer”,而不是 LXC。但仍可使用 LXC。
虚拟机监控程序负责创建虚拟环境,供客户虚拟机运行。它监督客户系统并确保资源按需分配给客户。虚拟机监控程序位于物理机和虚拟机之间,并为虚拟机提供虚拟化服务。为了实现这一点,它拦截虚拟机上的客户操作系统操作,并在主机操作系统上模拟操作。
云计算等虚拟化技术的快速发展推动了虚拟化的进一步应用,使用诸如Xen、VMware Player、KVM等虚拟化程序以及在普通处理器中加入硬件支持(例如Intel VT和AMD-V),使得多个虚拟服务器可以在单个物理服务器上创建。
虚拟化类型
虚拟化方法可以根据模拟硬件到客户操作系统和模拟客户操作环境的方式进行分类。主要有三种类型的虚拟化:
仿真
仿真,也称为完全虚拟化,在软件中完全运行虚拟机操作系统内核。这种类型使用的虚拟化程序被称为第二类虚拟化程序。它安装在主机操作系统的顶部,负责将客户操作系统内核代码转换为软件指令。翻译完全是通过软件完成的,不需要硬件参与。仿真使得可以运行任何支持被模拟环境的未修改操作系统。这种虚拟化的缺点是增加了额外的系统资源开销,导致性能比其他虚拟化类型下降。
此类软件包括VMware Player、VirtualBox、QEMU、Bochs、Parallels等。
半虚拟化
半虚拟化,也称为Type 1 hypervisor,直接运行在硬件上或“裸机”上,并直接向运行在其上的虚拟机提供虚拟化服务。它帮助操作系统、虚拟化硬件和真实硬件协作以达到最佳性能。这些hypervisors通常具有较小的占用空间,本身不需要大量资源。
此类软件包括Xen、KVM等。
基于容器的虚拟化
基于容器的虚拟化,也被称为操作系统级别的虚拟化,可以在单个操作系统内核中实现多个隔离执行。它具有最佳的性能和密度,并且具有动态资源管理功能。这种类型的虚拟化提供的隔离虚拟执行环境被称为容器,并且可以视为一组被跟踪的进程。
容器的概念是由Linux内核2.6.24版本添加的命名空间功能实现的。容器将其ID添加到每个进程中,并为每个系统调用添加新的访问控制检查。它通过clone()系统调用访问,允许创建以前全局命名空间的单独实例。
命名空间可以以许多不同的方式使用,但最常见的方法是创建一个隔离的容器,该容器对容器外部的对象没有可见性或访问权限。在容器内运行的进程似乎正在运行普通的Linux系统,尽管它们与位于其他命名空间中的进程共享底层内核,其他类型的对象也是如此。例如,当使用命名空间时,容器内的root用户不会被视为容器外的root用户,从而增加了额外的安全性。
Linux Control Groups(cgroups)子系统是启用基于容器的虚拟化的下一个主要组件,用于分组进程并管理其聚合资源消耗。它通常用于限制容器的内存和CPU消耗。由于容器化的Linux系统只有一个内核,并且内核完全看到容器,因此只有一级资源分配和调度。
Linux容器有几个管理工具,包括LXC、LXD、systemd-nspawn、lmctfy、Warden、Linux-VServer、OpenVZ、Docker等。
容器 vs 虚拟机
与虚拟机不同,容器无需启动操作系统内核,因此可以在不到一秒的时间内创建容器。这个特性使基于容器的虚拟化比其他虚拟化方法更具有独特性和吸引力。
由于基于容器的虚拟化对主机机器几乎没有额外开销,因此具有接近本地性能。
对于基于容器的虚拟化,不需要额外的软件,不像其他虚拟化方法。
主机机器上的所有容器共享调度程序,节省了额外资源的需求。
与虚拟机镜像相比,容器状态(Docker 或 LXC 镜像)体积小,因此容器镜像易于分发。
通过 cgroups 实现容器中的资源管理。cgroups 不允许容器消耗超过分配给它们的资源。然而,目前为止,虚拟机中可见主机机器的所有资源,但不能使用。可以通过同时在容器和主机机器上运行 top
或 htop
来实现此功能。所有环境的输出将看起来类似。
更新:
Docker 如何在非 Linux 系统中运行容器?
如果容器之所以可行,是因为 Linux 内核中可用的特性,那么显然的问题是非 Linux 系统如何运行容器。Docker for Mac 和 Windows 都使用 Linux VM 来运行容器。Docker 工具箱曾经在 Virtual Box VM 中运行容器。但是最新版本的 Docker 在 Windows 上使用 Hyper-V,在 Mac 上使用 Hypervisor.framework。screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
现在,我们甚至可以检查此 VM 的内核版本:
# uname -a
Linux linuxkit-025000000001 4.9.93-linuxkit-aufs #1 SMP Wed Jun 6 16:86_64 Linux
所有容器都在这个 VM 中运行。nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock
- user1503941这里大部分回答都谈到了虚拟机。我会给你一个简短的回答,这个回答在我使用 Docker 的过去几年中帮助我最多。它就是:
Docker 只是运行一个进程的高级方法,而不是虚拟机。
现在,让我更详细地解释一下这是什么意思。虚拟机是完全不同的东西。我觉得解释一下 Docker 会比解释虚拟机更有用。特别是因为在这里有很多好的答案告诉你当别人说“虚拟机”时确切的意思。所以...
Docker 容器只是一个 (以及它的子进程) 在主机系统内核中使用cgroups 进行隔离的进程。你可以通过在主机上运行 ps aux
命令来查看 Docker 容器进程。例如,在“容器”内启动 apache2
只是在主机上启动 apache2
的一个特殊进程。它只是被隔离在机器上的其他进程之外。需要注意的是,容器不会存在于容器化进程之外。当你的进程结束时,你的容器也会结束。这是因为 Docker 使用你的应用程序替换了容器内的 pid 1
(pid 1
通常是 init 系统)。最后关于 pid 1
的这一点非常重要。
docker pull ubuntu
时,下载的就是它。每个“镜像”实际上都是一系列层及其相关元数据。层次结构的概念在此非常重要。每个层次结构只是对其下面层次结构所做更改的记录。例如,在构建Docker容器时,如果您在Dockerfile中删除文件,实际上是在最后一层之上创建了一层,该层表示“已删除此文件”。顺带提一下,这就是为什么您可以从文件系统中删除大文件,但镜像的磁盘空间仍然相同的原因。该文件仍然存在在当前层以下的层中。层级本身只是文件的tar包。您可以使用docker save --output /tmp/ubuntu.tar ubuntu
测试这一点,然后使用cd /tmp && tar xvf ubuntu.tar
查看。然后您可以四处看看。所有看起来像长哈希值的目录实际上都是各自的单独层。每个层包含文件(layer.tar
)和有关该特定层次结构的元数据(json
)。这些层仅描述对文件系统所做的更改,这些更改作为“在其原始状态之上”的层级进行保存。读取“当前”数据时,文件系统会将数据读取为仅查看顶部层次结构的样子。这就是为什么该文件似乎已被删除,即使它仍然存在于“先前”的层中,因为文件系统仅查看最上面的层次结构。这使得完全不同的容器可以共享它们的文件系统层,即使每个容器的最上面的层次结构都发生了重大变化。当容器共享它们的基础映像层时,这可以节省大量磁盘空间。但是,当您通过卷将主机系统中的目录和文件挂载到容器中时,这些卷会“绕过”UnionFS,因此更改不会存储在层级结构中。Docker中的网络连接是通过使用以太网桥(在主机上称为docker0
)和为主机上的每个容器创建的虚拟接口来实现的。它在docker0
上创建了一个虚拟子网,使您的容器可以“之间”相互通信。这里有许多网络选项,包括为您的容器创建自定义子网,以及共享主机的网络堆栈以供您的容器直接访问。
Docker正在快速发展。它的文档是我见过的最好的文档之一。它通常写得很好,简明准确。我建议您查看可用的文档以获取更多信息,并相信文档比您在网上阅读的任何其他内容(包括Stack Overflow)更可靠。如果您有具体问题,强烈建议加入Freenode IRC上的#docker
并在那里提问(您甚至可以使用Freenode的Web聊天工具进行提问!)。
Docker用于封装应用程序及其所有依赖项。
虚拟化程序则封装了一个操作系统,能够在其上运行任何它本来可以在物理计算机上运行的应用程序。
使用普通的LXC需要提供一些rootfs或共享rootfs,当共享时,更改会反映在其他容器中。由于增加了许多这些功能,Docker比LXC更受欢迎。在嵌入式环境中,LXC流行用于实现围绕暴露给外部实体(如网络和UI)的进程的安全性。在期望一致的生产环境的云多租户环境中,Docker很受欢迎。
普通的虚拟机(例如VirtualBox和VMware)使用虚拟化程序和相关技术,其具有专用固件,成为第一个操作系统(主机操作系统或客户机操作系统0)的第一层,或者是在主机操作系统上运行的软件,以提供硬件仿真,如CPU、USB/附件、内存、网络等,对于客户机操作系统。截至2015年,虚拟机在高安全性的多租户环境中仍然很受欢迎。
Docker/LXC几乎可以在任何便宜的硬件上运行(少于1GB的内存也可以,只要您有更新的内核),而普通的虚拟机需要至少2GB的内存等才能进行有意义的操作。但是,在Windows等操作系统上不支持在主机操作系统上运行Docker(截至2014年11月),而可以在Windows、Linux和Mac上运行多种类型的虚拟机。
这是来自docker/rightscale的图片:这可能是许多Docker学习者的第一印象。
首先,Docker镜像通常比虚拟机镜像小得多,便于构建、复制和共享。
其次,Docker容器可以在几毫秒内启动,而虚拟机需要数秒钟。
这是Docker的另一个关键特性。镜像有层,不同的镜像可以共享层,使其更节省空间,构建更快。
如果所有容器都使用Ubuntu作为它们的基本镜像,则不是每个镜像都有自己的文件系统,而是共享相同的底层Ubuntu文件,并且只有它们自己的应用程序数据不同。
将容器看作进程!
在主机上运行的所有容器实际上是带有不同文件系统的一堆进程。它们共享同一个操作系统内核,仅封装系统库和依赖项。
这对大多数情况很好(不需要额外的操作系统内核维护),但在容器之间需要严格隔离时可能会出问题。
所有这些看起来都像是改进,而不是革命。好吧,量的积累会导致质的转变。
想想应用程序部署。如果我们想要部署新软件(服务)或升级一个现有的,最好更改配置文件和进程,而不是创建一个新的虚拟机。因为创建带有更新服务的虚拟机,测试它(在开发和QA之间共享),并将其部署到生产环境需要数小时,甚至数天。如果出了任何问题,就必须重新开始,浪费更多时间。所以,使用配置管理工具(puppet、saltstack、chef等)安装新软件,下载新文件更好。
当涉及到Docker时,用新创建的Docker容器代替旧容器是不可能的。维护更容易!构建新镜像,与QA共享,测试它,部署它只需要几分钟(如果一切都自动化了),在最坏的情况下需要几个小时。这被称为不可变基础设施:不要维护(升级)软件,创建一个新的。
它改变了服务的交付方式。我们需要应用程序,但必须维护虚拟机(这很麻烦,与我们的应用程序关系不大)。Docker让您专注于应用程序并使一切变得更加顺畅。