您已经了解到 pod 是一组位于同一位置的容器,也是 Kubernetes 中的基本构建块。您无需单独部署容器,而是将一组容器作为一个单元进行部署和管理——一个 pod。尽管 pod 可能包含多个,但一个 pod 只包含一个容器的情况并不少见。当一个 pod 有多个容器时,它们都运行在同一个工作节点上——一个 pod 实例永远不会跨越多个节点。图 5.2 将帮助您可视化这些信息。
image.png

理解我们为什么需要 pods

让我们讨论一下为什么我们需要一起运行多个容器,而不是例如在同一个容器中运行多个进程。

理解为什么一个容器不应该包含多个进程

想象一个应用程序由多个进程组成,这些进程通过 IPC(进程间通信)或共享文件相互通信,这要求它们在同一台计算机上运行。在第 2 章中,您了解到每个容器就像一台独立的计算机或虚拟机。一台计算机通常运行多个进程;容器也可以做到这一点。您可以在一个容器中运行构成应用程序的所有进程,但这使得容器非常难以管理。

容器被设计为只运行一个进程,不计算它产生的任何子进程。容器工具和 Kubernetes 都是围绕这一事实开发的。例如,在容器中运行的进程应将其日志写入标准输出。用于显示日志的 Docker 和 Kubernetes 命令仅显示从此输出中捕获的内容。如果单个进程在容器中运行,它是唯一的写入器,但如果您在容器中运行多个进程,它们都会写入相同的输出。它们的日志因此交织在一起,很难分辨每条记录的行属于哪个进程。

容器应该只运行单个进程的另一个迹象是容器运行时仅在容器的根进程死亡时重新启动容器。它不关心这个根进程创建的任何子进程。如果它产生子进程,它独自负责保持所有这些进程运行。

要充分利用容器运行时提供的功能,您应该考虑在每个容器中只运行一个进程。

了解 pod 如何组合多个容器

由于您不应该在单个容器中运行多个进程,因此很明显您需要另一个更高级别的构造,它允许您一起运行相关进程,即使被划分为多个容器。这些进程必须能够像普通计算机中的进程一样相互通信。这就是引入 pod 的原因。

使用 pod,您可以一起运行密切相关的进程,为它们(几乎)提供相同的环境,就好像它们都在单个容器中运行一样。这些进程有独立的步伐,但并不完全——它们共享一些资源。这为您提供了两全其美的体验。您可以使用容器提供的所有功能,还可以让进程协同工作。 Pod 使这些互连的容器可以作为一个单元进行管理。

在第二章中,您了解到容器使用自己的一组 Linux 名称空间,但它也可以与其他容器共享一些名称空间。命名空间的这种共享正是 Kubernetes 和容器运行时将容器组合成 pod 的方式。

如图所示,一个 pod 中的所有容器共享相同的网络命名空间,因此属于它的网络接口、IP 地址和端口空间。
image.png
由于共享端口空间,同一个 pod 的容器中运行的进程不能绑定相同的端口号,而其他 pod 中的进程有自己的网络接口和端口空间,消除了不同 pod 之间的端口冲突。

一个 pod 中的所有容器也看到相同的系统主机名,因为它们共享 UTS 命名空间,并且可以通过通常的 IPC 机制进行通信,因为它们共享 IPC 命名空间。也可以将 pod 配置为对其所有容器使用单个 PID 命名空间,这使它们共享一个进程树,但您必须为每个 pod 单独显式启用此功能。

当同一个 pod 的容器使用不同的 PID 命名空间时,它们无法看到彼此或在它们之间发送 SIGTERM 或 SIGINT 等进程信号。

正是这种特定命名空间的共享,让运行在 pod 中的进程感觉它们是一起运行的印象,即使它们在不同的容器中运行。

相比之下,每个容器总是有自己的 Mount 命名空间,赋予它自己的文件系统,但是当两个容器必须共享文件系统的一部分时,您可以向 pod 添加一个卷并将其挂载到两个容器中。这两个容器仍然使用两个独立的 Mount 命名空间,但共享卷被挂载到两者中。您将在第 7 章中了解有关卷(volumes)的更多信息。

将容器组织成 pods

您可以将每个 pod 视为一台单独的计算机。与通常托管多个应用程序的虚拟机不同,您通常在每个 pod 中只运行一个应用程序。您永远不需要在单个 pod 中组合多个应用程序,因为 pod 几乎没有资源开销。您可以拥有任意数量的 pod,因此与其将所有应用程序塞入一个 pod,不如将它们分开,以便每个 pod 只运行密切相关的应用程序进程。

让我用一个具体的例子来说明这一点。

将多层应用程序拆分为多个 pod

想象一个由前端 Web 服务器和后端数据库组成的简单系统。我已经解释过前端服务器和数据库不应该在同一个容器中运行,因为容器中内置的所有功能都是围绕容器中运行不超过一个进程的期望而设计的。如果不在单个容器中,那么您是否应该在都位于同一个 pod 中的单独容器中运行它们?

尽管没有什么可以阻止您在单个 pod 中同时运行前端服务器和数据库,但这并不是最好的方法。我已经解释过,一个 pod 的所有容器总是在同一位置运行,但是 Web 服务器和数据库是否必须在同一台计算机上运行?答案显然是否定的,因为他们可以轻松地通过网络进行通信。因此,您不应该在同一个 pod 中运行它们。

如果前端和后端都在同一个 pod 中,则两者都运行在同一个集群节点上。如果您有一个双节点集群并且只创建一个 pod,那么您只使用一个工作节点,并且没有利用第二个节点上可用的计算资源。这意味着浪费了 CPU、内存、磁盘存储和带宽。将容器拆分为两个 pod 允许 Kubernetes 将前端 pod 放置在一个节点上,将后端 pod 放置在另一个节点上,从而提高硬件的利用率。

拆分成多个 pod 以实现单独的扩展

不使用单个 pod 的另一个原因与水平缩放有关。 Pod 不仅是部署的基本单元,也是扩展的基本单元。在第 2 章中,您扩展了 Deployment 对象,并且 Kubernetes 创建了额外的 pod——应用程序的额外副本。 Kubernetes 不会在 pod 中复制容器。它复制了整个 pod。

前端组件通常具有与后端组件不同的缩放要求,因此我们通常单独缩放它们。当您的 pod 同时包含前端和后端容器并且 Kubernetes 复制它时,您最终会得到前端和后端容器的多个实例,这并不总是您想要的。有状态的后端,例如数据库,通常无法扩展。至少不像无状态前端那么容易。如果容器必须与其他组件分开扩展,这清楚地表明它必须部署在单独的 pod 中。

下图说明了刚刚解释的内容。
image.png
将应用程序系统拆分为多个 pod 是正确的方法。但是,什么时候在同一个 pod 中运行多个容器呢?

引入 sidecar 容器

仅当应用程序由一个主进程和一个或多个补充主进程操作的进程组成时,将多个容器放置在一个 pod 中才是合适的。运行补充过程的容器被称为边车容器,因为它类似于摩托车边车,它使摩托车更加稳定,并提供了搭载额外乘客的可能性。但与摩托车不同的是,一个 pod 可以有多个边车,如图所示。
image.png
很难想象什么是互补过程,所以我会给你一些例子。在第 2 章中,您使用一个运行 Node.js 应用程序的容器部署了 pod。 Node.js 应用程序仅支持 HTTP 协议。为了让它支持 HTTPS,我们可以添加更多的 JavaScript 代码,但我们也可以在不改变现有应用程序的情况下做到这一点——通过向 pod 添加一个额外的容器——一个将 HTTPS 流量转换为 HTTP 并转发它的反向代理到 Node.js 容器。因此,Node.js 容器是主容器,而运行代理的容器是 sidecar 容器。下图显示了这个例子。
image.png
另一个示例,如图所示,是一个 pod,其中主容器运行一个 Web 服务器,该服务器从其 webroot 目录提供文件。 pod 中的另一个容器是一个代理,它定期从外部源下载内容并将其存储在 Web 服务器的 webroot 目录中。正如我前面提到的,两个容器可以通过共享一个卷来共享文件。 webroot 目录将位于此卷上。
image.png
Sidecar 容器的其他示例包括日志旋转器和收集器、数据处理器、通信适配器等。

与更改应用程序的现有代码不同,添加 sidecar 会增加 pod 的资源需求,因为必须在 pod 中运行额外的进程。但请记住,将代码添加到遗留应用程序可能非常困难。这可能是因为它的代码难以修改,难以设置构建环境,或者源代码本身不再可用。通过添加额外的进程来扩展应用程序有时是一种更便宜、更快的选择。

如何决定是否将容器拆分为多个 pod

在决定是使用 sidecar 模式并将容器放置在单个 pod 中,还是将它们放置在单独的 pod 中时,请问自己以下问题:

  • 这些容器是否必须在同一主机上运行?
  • 我想将它们作为一个单元进行管理吗?
  • 它们是否形成一个统一的整体而不是独立的组件?
  • 它们必须一起缩放吗?
  • 单个节点能否满足其组合资源需求?

如果所有这些问题的答案都是肯定的,请将它们全部放在同一个 pod 中。根据经验,始终将容器放在单独的 pod 中,除非特定原因要求它们属于同一个 pod。