大白话 Docker (新手入门必读)

Docker

介绍

自2013年开放源代码发布以来,Docker成为一项最受欢迎的技术之一。许多公司都在为此做出贡献,并且大量的人正在使用和采用它。但是为什么如此受欢迎?它提供了以前没有的功能?在此博客文章中,我们想更深入地研究Docker内部,以了解其工作原理。

这篇文章的第一部分将简要介绍基本的架构概念。在第二部分中,我们将介绍四个主要功能,这些功能构成了Docker容器中隔离的基础:1.用户组,2.命名空间,3.可堆叠的图像层和写时复制,4.虚拟网络桥。在第三部分中,将讨论使用容器和Docker时的机遇与挑战。最后,我们回答一些有关Docker的常见问题。

基本架构

"Docker是一个开源项目,可以自动在软件容器内部署应用程序。"- 维基百科

人们在谈论操作系统级虚拟化时通常会引用容器。操作系统级虚拟化是一种方法,其中操作系统的内核允许存在多个隔离的应用程序实例。有许多可用的容器实现,其中之一是Docker。

Docker根据镜像定义了在创建容器时应在容器中放置的内容。定义镜像是通过Dockerfile文件来实现的。Dockerfile文件包含有关如何逐步构建映像的说明(不要担心,稍后您将了解有关内部正在进行的操作的更多信息)。例如,以下Dockerfile将从包含OpenJDK的映像开始,在其中安装Python 3, 复制镜像中的requirements.txt文件,然后安装改文件需求的所有Python软件包。

FROM openjdk:8u212-jdk-slim

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    Python3=3.5.3-1 \
    Python3-pip=9.0.1-2+deb9u1 \
  && rm -rf /var/lib/apt/lists/*

COPY requirements.txt requirements.txt
RUN pip3 install --upgrade -r requirements.txt

映像通常存储在称为Docker registries的映像存储库中。Dockerhub是公共Docker注册中心。为了下载映像并启动容器,您需要拥有Docker 主机。Docker主机是一台运行Docker 守护程序的Linux机器(守护程序是一个始终在运行,等待工作完成的后台进程)。

为了启动容器,您可以使用Docker客户端,该客户端将必要的指令提交给Docker守护程序。如果Docker守护程序无法在本地找到所请求的映像,它也正在与Docker注册表进行对话。下图说明了Docker 的基本架构 :

docker architecture

需要注意的一点是,Docker本身并不提供实际的容器化,仅使用Linux中可用的容器化。接下来让我们深入了解下其中的技术细节。

容器隔离

Docker通过四个主要概念的组合来实现不同容器的隔离:1)cgroups,2)namespaces,3)stackableimage layerscopy on write,4)virtual network bridges。在下面的小节中,我们将详细解释这些概念。

控制组(Cgroups)

Linux操作系统管理可用的硬件资源(内存、CPU、磁盘I/O、网络I/O,…),并为进程访问和利用它们提供一种方便的方式。例如,Linux的CPU调度程序会注意每个线程最终都会在CPU核心上获得一些时间,这样就不会有应用程序被困在等待CPU时间。

控制组(cgroup)是一种将资源子集分配给特定进程组的方法。这可以用来,例如,确保即使你的CPU忙于Python脚本,你的PostgreSQL数据库仍然有专用的CPU和RAM。下图在一个4个CPU核和16 GB RAM的示例场景中说明了这一点。

cgroups

在Zeppelin中启动的所有 zeppelin-grp 笔记本将仅使用Core1和Core2,而PostgreSQL进程共享Core3和Core4。这同样适用于内存。cGroup是容器隔离中的一个重要构建块,因为它们允许硬件资源隔离

命名空间

当 cgroups 隔离硬件资源时,命名空间会隔离和虚拟化系统资源。可以虚拟化的系统资源包括进程 ID 、主机名、用户 ID 、网络访问、进程间通信和文件系统。让我们首先深入到一个进程 ID (PID) 命名空间的例子,以使这一点更清楚,然后简要讨论其他命名空间。

PID 命名空间

Linux 操作系统将进程组织在所谓的进程树中。树根是操作系统启动后运行的第一个进程,它的 PID 为 1 。 由于只能存在一个进程树,所有其它的进程(例如:火狐,终端模拟器, SSH 服务器等等)需要(直接或间接)由根进程启动。由于根进程初始化了所有其它进程,所以它通常被称为 init 进程。

下图说明了典型进程树的部分功能,其中 init 进程启动了日志服务( syslogd )、调度程序( cron )以及登录 shell ( bash ):

1 /sbin/init
+-- 196 /usr/sbin/syslogd -s
+-- 354 /usr/sbin/cron -s
+-- 391 login
    +-- 400 bash
        +-- 701 /usr/local/bin/pstree

在进程树内,每个进程都可以看到其他进程,如果他们愿意的话,进程之间可以互发信号(例如,请求进程停止)。 使用 PID 命名空间虚拟化特定进程及其所有子进程的 PID ,使其认为它有 PID 1 。 之后它将无法看到除自己的子进程之外的其他进程。下图说明了不同的 PID 命名空间是如何隔离两个 Zeppelin 进程的子进程树。

1 /sbin/init
|
+ ...
|
+-- 506 /usr/local/zeppelin
    1 /usr/local/zeppelin
    +-- 2 interpreter.sh
    +-- 3 interpreter.sh
+-- 511 /usr/local/zeppelin
    1 /usr/local/zeppelin
    +-- 2 java

文件系统命名空间

命名空间的另一个用例是 Linux 文件系统。类似于 PID 命名空间,文件系统命名空间虚拟化和隔离树的部分-在这种情况下即文件系统树。 Linux 文件系统被组织为一棵树的形式,它有一个树根,通常指 /

为了在文件系统级别上实现隔离,命名空间将文件系统树中的节点映射到该命名空间内的虚拟根目录。在这个命名空间中浏览文件系统, Linux 不允许您超出虚拟化的根目录。下面的绘图显示了文件系统的一部分,其中包含了 /drives/xx 文件夹中的多个“虚拟”文件系统根目录,每个文件夹包含不同的数据。

filesystem namespace example

其它命名空间

除了 PID 和文件系统命名空间之外,还有 其他类型的名称空间 。 Docker 允许您使用它们以实现所需的隔离量。 例如,用户命名空间允许您将容器内的用户映射到外部的不同用户。这可以用于将容器内的根用户映射到外部的非根用户,因此容器内的进程在内部就像管理员一样,在外部它没有特权。

可堆叠图像层和写时复制

现在,我们对硬件和系统资源隔离如何帮助我们构建容器有了更详细的了解,我们将研究Docker存储映像的方式。如前所述,Docker映像就像是容器的蓝图。它带有启动包含它的应用程序所需的所有依赖关系。但是如何存储这些依赖关系?

Docker将图像持久保存在可堆叠的层中。一层包含对上一层所做的更改。例如,如果您先安装Python,然后复制Python脚本,则映像将具有两个附加层:一层包含Python可执行文件,另一层包含脚本。下图显示了全部基于Ubuntu的Zeppelin,Spring和PHP映像。

可堆叠图像层

为了不存储三次Ubuntu,层是不可变的并且是共享的。 如果发生改变时,Docker使用写时复制去复制文件。

基于镜像启动 Docker 容器时,Docker 守护进程将为您提供该镜像中包含的所有层,并将其放入该容器的隔离文件系统命名空间中。可堆叠层、写时复制和文件系统命名空间的组合使您能够完全独立于「安装」在Docker主机上的内容来运行容器,而不会浪费大量空间。这就是为什么容器比虚拟机更轻量级的原因之一。

虚拟网桥

现在,我们知道了隔离硬件资源(控制组)和系统资源(命名空间)的方法,以及如何为每个容器提供独立于主机系统(镜像层)的预定义依赖集。最后一个构建模块,即「虚拟网桥」,它可以帮助我们隔离容器内的网络堆栈。

网桥是从多个通信网络或网段创建单个聚合网络的计算机网络设备。让我们看看连接两个网段(LAN 1和LAN 2)的物理网桥的典型设置:

network bridge

通常我们在Docker主机上只有有限的网络接口(例如物理网卡),所有进程都需要共享对它的访问。为了隔离容器的网络,Docker允许每个容器创建一个虚拟网络接口。然后,将所有虚拟网络接口连接到主机网络适配器,如下图所示:

docker virtual network bridge

本例中的两个容器在其网络命名空间中有自己的eth0网络接口。它映射到Docker主机上相应的虚拟网络接口veth0veth1。虚拟网桥docker0将主机网络接口eth0连接到所有容器网络接口。

Docker为您提供了配置网桥的自由,因此,您可以只向外部公开特定端口,或直接将两个容器连接在一起(例如,数据库容器和需要访问它的应用程序),而不向外部公开任何内容。

连接点

利用前面描述的技术和功能,我们现在可以“容器化”我们的应用程序。虽然可以使用 cgroup、命名空间、虚拟网络适配器等手动创建容器,但Docker是一个方便且几乎没有开销的工具。它处理所有手动、配置密集型任务,使软件开发人员而不仅仅是Linux专家可以访问容器。

实际上有一位Docker工程师提供了一篇“不错的演讲”,他演示了如何手动创建容器,也解释了我们在这一小节中介绍的细节。

Docker的机遇和挑战

到现在,很多人每天都在使用Docker。容器增加了什么好处?Docker提供了以前没有的东西吗?最后,您对应用程序进行容器化所需的一切在Linux中已经存在很长时间了,不是吗?

让我们看看在迁移到基于容器的设置时您所拥有的一些机会(当然不是详尽的列表)。当然,在采用Docker时,不仅有机会,也有挑战,可能会给您带来困难。我们还将在本节中列举一些。

Opportunities

Docker支持DevOps。DevOps理念试图将开发和操作活动连接起来,使开发人员能够自行部署其应用程序。您构建它、运行它。通过基于Docker的部署,开发人员可以直接将其工件和所需的依赖项一起发布不用担心依赖冲突。它还允许开发人员编写更复杂的测试并更快地执行它们,例如,在另一个容器中创建一个真实的数据库,并在几秒钟内将其链接到笔记本电脑上的应用程序(请参见Testcontainers)。

容器提高了部署的可预测性。不再“运行”在我的机器上”。不再有失败的应用程序部署,因为一台计算机安装了不同版本的Java。您只需构建一次映像,就可以在任何地方运行它(假设安装了Linux内核和Docker)。

采用率高,与许多著名的集群管理器很好地集成。使用Docker的一个重要方面是其周围的软件生态系统。如果您计划进行大规模操作,那么您将无法使用一个或另一个集群管理器。如果你决定让其他人来管理你的部署(如Google Cloud、Docker Cloud、Heroku、AWS等等),或者想维护你自己的集群管理器(如Kubernetes、Nomad、Mesos),有很多解决方案可供选择。

轻量级容器支持快速故障恢复或自动扩展。想象一下运行一个在线商店。在圣诞节期间,人们将开始攻击您的web服务器,而您当前的设置在容量方面可能不够。考虑到您有足够的免费硬件资源,再启动几个承载web应用程序的容器只需几秒钟。也可以通过将容器迁移到新机器来恢复发生故障的机器。

挑战

容器给人一种错误的安全感。在保护应用程序安全方面有许多陷阱。认为把它们放在容器里是保护它们的一种方法是错误的。容器本身并不能保护任何东西。如果有人破解了您的容器化web应用程序,他可能会被锁定到名称空间中,但根据设置的不同,有几种方法可以避免这种情况。意识到这一点,并尽可能多地投入到安全性上,就像没有Docker一样。

Docker使人们可以轻松地部署半生不熟的解决方案。选择您最喜欢的软件,并将其名称输入谷歌搜索栏,添加“Docker”。你可能会在Dockerhub上找到至少一个甚至几十个已经公开的包含你的软件的图片。所以为什么不直接执行它,给它一个机会呢?什么会出错?很多事情都会出错。当把东西放进容器里时,它看起来会非常闪亮,令人惊叹,人们也不再关注里面的实际软件和配置了。

(fat)容器反模式会导致大型的、难以管理的部署构件。 我看到过Docker映像,这些映像要求您在创建容器时为内部不同的应用程序公开20多个端口。Docker的哲学是一个容器应该做一项工作,你应该把它们组合起来,而不是让它们变得更重。如果您最终将所有工具放在一个容器中,您将失去所有优势,内部可能有不同版本的Java或Python,并最终得到20 GB无法管理的映像。
调试某些情况可能仍需要深入的Linux知识。您可能听到同事说XXX不能与Docker一起工作。之所以会发生这种情况,有多种原因。如果某些应用程序不能正确区分它们绑定到的网络接口和它们通告的网络接口,则它们在桥接网络命名空间中运行时会出现问题。另一个问题可能与cgroups和名称空间有关,其中共享内存方面的默认设置与您最喜欢的Linux发行版上的设置不同,导致在容器内运行时出现OOM错误。然而,大多数问题实际上并不与Docker相关,而是与应用程序设计不当有关,并且它们并不是那么频繁。但它们仍然需要对Linux和Docker如何工作有一些更深入的了解,这并不是每个Docker用户都有的。

常见问题

容器和虚拟机有什么区别?

在不太深入了解虚拟机(VM)体系结构的情况下,让我们从概念层面看两者之间的主要区别。容器在操作系统内部运行,使用内核功能来隔离应用程序。另一方面,VM需要在操作系统内部运行的管理程序。然后,管理程序创建可由另一组操作系统访问的虚拟硬件。下图比较了基于虚拟机的应用程序设置和基于容器的设置。

vm vs container

如您所见,基于容器的设置具有较小的开销,因为它不需要为每个应用程序添加一个额外的操作系统。这是因为容器管理器(例如Docker)直接使用操作系统功能以更轻量级的方式隔离应用程序。

这是否意味着容器优于虚拟机?这要看情况。这两种技术都有各自的用例,有时甚至可以将它们结合起来,在VM中运行容器管理器。有很多博客文章在讨论这两种解决方案的利弊,所以我们现在不打算详细讨论。重要的是要了解差异,不要将容器视为某种“轻量级虚拟机”,因为它们在内部是不同的。

容器包含什么?

看看容器的定义以及我们到目前为止所学到的知识,我们可以有把握地说,可以使用 Docker 来部署隔离的应用程序。通过将控制组和命名空间与可堆叠的图像层和虚拟网络接口以及虚拟网桥相结合,我们拥有了完全隔离应用程序所需的所有工具,还可能将进程锁定在容器中。现实表明,这并不容易。首先,它需要正确配置,其次,您会注意到完全隔离的容器在大多数情况下没有多大意义。

最后,您的应用程序需要以某种方式产生一些副作用(将数据保存到磁盘,通过网络发送数据包,...)。因此,您最终将通过转发网络流量或将主机卷装入您的文件系统命名空间来打破隔离。也不需要使用所有可用的命名空间功能。虽然默认启用网络、PID 和文件系统命名空间功能,但使用用户 ID 命名空间需要您添加额外的配置选项。

所以假设仅仅通过将某些东西放入容器中就可以保证容器的安全是错误的。例如,AWS使用一个名为Firecracker的轻量级虚拟机引擎来安全和多租户执行短期工作负载。

容器是否能使我的生产环境更稳定?

有些人认为容器可以提高稳定性,因为它们可以隔离错误。如果正确配置的命名空间和Cgroups将限制一个进程出现恶意的副作用,那么这是正确的,但实际上需要记住一些事情。

如前所述,容器仅在正确配置时才包含,而且大多数时候您希望它们与系统的其他部分交互。因此,可以说容器有助于提高部署的稳定性,但您应该始终记住,它不会保护您的应用程序不受失败的影响。

结论

Docker是以或多或少可重现和隔离的方式独立部署应用程序的伟大技术。一如既往,没有万能的解决方案,在选择Docker作为您选择的工具之前,您应该了解您在安全性、性能、可部署性、可观察性等方面的要求。

幸运的是,Docker周围已经有一个巨大的工具生态系统。可以根据需要添加服务发现、容器编排、日志转发、加密等用例的解决方案。我想引用我最喜欢的一条推文来结束这篇帖子:

"把已经坏掉的软件放进Docker容器并不会减少它的损坏。" - @sadserver

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://dev.to/frosnerd/docker-demystifi...

译文地址:https://learnku.com/server/t/43252

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 3

收藏了,隔了一个礼拜,还是看不了,改天有空再来翻译 :joy:

3年前 评论
秦晓武

感谢。期待更新。

3年前 评论
wangchunbo

@秦晓武 右击浏览器,翻译为中文。。。。

3年前 评论
秦晓武 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!