K8S - 了解 Pods

您已经了解到 pod 是一组位于同一位置的容器,也是 Kubernetes 中的基石。您无需单独部署容器,而是将一组容器作为一个单元(Pod)进行部署和管理。尽管 pod 可能包含多个容器,但一个 pod 只包含一个容器的情况也并不少见。当一个 pod 有多个容器时,它们都运行在同一个工作节点(Node)上 —— 一个 pod 实例永远不会跨越多个节点。如下图所示

image-20221031212416278

为什么我们需要 Pods?

解释这个问题之前,让我们先讨论一下,为什么

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

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

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

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

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

了解一个 Pod 怎么组合多个容器

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

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

容器使用自己的一组 Linux 名称空间,但它也可以与其他容器共享一些命名空间(namespace)。命名空间(namespace)的这种共享正是 Kubernetes 和容器运行时将容器组合成 pod 的方式。

在一个 Pod 中的所有容器,通过共享 Network Namespace 的方式共享 网络接口、IP地址、端口空间。

image-20221102201108225

由于共享端口空间(Network Namespace),在同一个 pod 的容器中运行的进程不能绑定相同的端口号,而其他 pod 中的进程,由于 Pod 与 Pod 之间有自己的网络接口和端口空间,不同 pod 之间则不会有端口冲突。

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

注意

当同一个 pod 的容器使用不同的 PID Namespace 时,它们无法看到彼此,也无法发送 SIGTERM 或 SIGINT 之类的进程信号。

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

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

如何在 Pods 中组织容器

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

让我举个具体的例子来说明它

将多层级程序栈分割进多个 Pods

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

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

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

拆分成多个 Pods 以实现各自的扩展

不能使用一个 Pod 管理的另一个原因与水平扩展有关。一个 Pod 不仅仅是部署的基本单元,也是水平扩展的基本单元。当您的 pod 同时包含前端和后端容器并且 Kubernetes 复制它时,您最终会得到前端和后端容器的多个实例,但这并不总是您想要的。有状态的后端,例如数据库,通常无法扩展。至少不像无状态前端那么容易扩展。如果容器必须与其他组件分开扩展,这清楚地表明它必须部署在单独的 pod 中。

image-20221103114045612

将应用程序堆栈拆分为多个 pod 是正确的方法。但是,什么时候在同一个 pod 中运行多个容器呢?

Sidecar(边车) 容器介绍

仅当应用程序由一个主进程和其他补充主进程操作的进程组成时,将多个容器放置在一个 pod 中才是合适的。运行补充过程的容器被称为*Sidecar Container(边车容器)*,因为它类似于摩托车边车,它使摩托车更加稳定,并提供了搭载额外乘客的可能性。但与摩托车不同的是,一个 Pod 可以有多个 Sidecar。

image-20221103114332173

很难想象什么是互补进程,所以我会给你一些例子。例如,您使用一个运行 Node.js 应用程序的容器部署了 pod。Node.js 应用程序仅支持 HTTP 协议。为了让它支持 HTTPS,我们可以添加更多的 JavaScript 代码来支持 HTTPS,但我们也可以在不改变现有应用程序的情况下做到这一点——通过向 pod 添加一个额外的容器,一个将 HTTPS 流量转换为 HTTP 并转发它的反向代理到 Node.js 容器。因此,Node.js 容器是主容器,而运行代理的容器是 sidecar 容器。

image-20221103180445015

另一个示例是一个 pod,其中主容器运行一个 Web 服务器,该服务器从其 webroot 目录提供静态资源。pod 中的另一个容器是一个代理,它定期从外部源下载内容并将其存储在 Web 服务器的 webroot 目录中。正如我前面提到的,两个容器可以通过共享一个卷来共享文件。webroot 目录将位于此卷上。

image-20221103180548854

Sidecar 容器的其他示例包括:日志切割器、日志收集器、数据处理器、通信适配器等。

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

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

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

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

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


好好学习,天天向上