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 时,它们无法看到彼此,也无法发送 SIGTERMSIGINT 之类的进程信号。

正是这种特定命名空间的共享,使运行在 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。


好好学习,天天向上