关于 Pod
Pod 是 Kubernetes 中最小的 API 单元。从更技术的角度说,Pod 是 Kubernetes 中的原子调度单元。但我们为什么需要 Pod?要回答这个问题,我们首先需要理解容器的本质:容器本质上就是一个进程。没错,容器是云计算系统中的进程,而容器镜像是这个系统的“.exe”安装包。Kubernetes 在这个类比中则扮演着操作系统的角色。
进程与进程组
让我们登录一台 Linux 机器并执行以下命令:
$ pstree -g
该命令显示系统当前运行进程的树状结构。输出可能如下所示:
systemd(1)-+-accounts-daemon(1984)-+-{gdbus}(1984)
| `-{gmain}(1984)
|-acpid(2044)
...
|-lxcfs(1936)-+-{lxcfs}(1936)
| `-{lxcfs}(1936)
|-mdadm(2135)
|-ntpd(2358)
|-polkitd(2128)-+-{gdbus}(2128)
| `-{gmain}(2128)
|-rsyslogd(1632)-+-{in:imklog}(1632)
| |-{in:imuxsock) S 1(1632)
| `-{rs:main Q:Reg}(1632)
|-snapd(1942)-+-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
| |-{snapd}(1942)
正如你所见,在真实操作系统中,进程并非孤立运行,而是被组织成进程组。例如,负责 Linux 日志处理的程序“rsyslogd”,其主程序“main”及其使用的内核日志模块“imklog”都属于进程组 1632。这些进程协同工作以完成 rsyslogd 程序的功能。Kubernetes 本质上将这种“进程组”的概念映射到容器技术中,并使之成为这个云计算“操作系统”中的“一等公民”。Kubernetes 之所以采用这种方式,是因为 Google 工程师们意识到他们部署的应用程序通常表现出类似于“进程与进程组”的关系。具体来说,这些应用需要紧密协作,因此必须部署在同一台机器上。如果没有“组”的概念,管理这类操作关系将变得极其困难。以 rsyslogd 为例,它包含三个进程:一个 imklog 模块、一个 imuxsock 模块以及 rsyslogd 自身的主函数进程。这三个进程必须在同一台机器上运行,否则它们基于 socket 的通信和文件交换就会出问题。
容器间通信

如上图所示,这个 Pod 中包含两个用户容器 A 和 B,以及一个 Infra 容器。在 Kubernetes 中,Infra 容器设计为占用最少资源,并使用一个特殊的镜像“k8s.gcr.io/pause”。这个镜像代表了一个用汇编语言编写的容器,它永远处于“暂停”状态,未压缩的大小仅为 100–200 KB。一旦 Infra 容器“持有”了 Network Namespace(网络命名空间),用户容器就可以加入这个命名空间。因此,如果你在宿主机上检查这些容器的命名空间文件(之前提到过的文件路径),它们会指向完全相同的值。这意味着 Pod 中的容器 A 和 B 可以直接使用“localhost”进行通信。它们看到的是与 Infra 容器相同的网络设备。一个 Pod 只有一个 IP 地址,这个 IP 地址就是 Pod 网络命名空间的 IP 地址。当然,所有其他网络资源也都是按 Pod 分配的,并由 Pod 内的所有容器共享。Pod 的生命周期只与 Infra 容器绑定,与容器 A、B 无关。此外,对于同一个 Pod 内的所有用户容器,它们的进出流量都可以看作是经过 Infra 容器的。这一方面至关重要,因为如果你以后要为 Kubernetes 开发网络插件,你的主要关注点应该放在配置 Pod 的网络命名空间上,而不是每个用户容器如何使用你的网络配置——后者无关紧要。这意味着如果你的网络插件依赖于在容器内安装软件包或配置,那么它不是一个可行的解决方案。Infra 容器的镜像根文件系统几乎是空的,你没有任何自定义的空间。反过来,这也意味着你的网络插件无需关心用户容器的启动状态,只需要关注配置 Pod(即 Infra 容器的网络命名空间)即可。采用这种设计后,共享卷就变得简单多了。Kubernetes 可以将所有卷配置定义在 Pod 级别。因此,一个卷对应的宿主机目录对 Pod 来说是唯一的,Pod 内的任何容器只需声明挂载该目录即可。Pod 这种促使容器之间形成“超级亲密关系”的设计理念,旨在鼓励用户思考:运行在单个容器中、具有多个功能上不相关的组件的应用程序,是否更适合表示为同一个 Pod 中的多个容器。要理解这种思路,可以尝试将其应用于单个容器难以解决的场景。例如,假设一个应用程序不断将日志文件输出到容器内的“/var/log”目录。在这种情况下,你可以在 Pod 中挂载一个卷到应用程序容器的“/var/log”目录。然后,在同一个 Pod 内运行一个 sidecar 容器,该容器也声明将同一个卷挂载到它自己的“/var/log”目录。接下来,sidecar 容器的唯一任务就是从它的“/var/log”目录持续读取日志文件,并将它们转发到 MongoDB 或 Elasticsearch 等存储方案。这样就建立了一个基本的日志收集机制。与第一个例子类似,这个场景中 sidecar 的主要功能也依赖于使用共享卷进行文件操作。但是,不要忽略 Pod 的另一个重要特性:Pod 内的所有容器共享同一个 Network Namespace。这使得许多与 Pod 网络相关的配置和管理任务都可以委托给 sidecar,完全不需要干扰用户容器。Istio 服务网格项目就是一个很好的例子。
总结
在本文中,我们深入探讨了为什么需要 Pod 的原因。本质上,Pod 是 Kubernetes 集群中的基本单元,封装了一个或多个容器(通常是 Docker 容器)。这些容器共享网络和存储资源。从进程和进程组的角度来看,Pod 可以被视为一个轻量级的进程组。它使得多个紧密协作的进程(容器)可以作为整体进行部署、扩缩容和管理,简化了复杂应用的部署和运维。在下一篇文章中,我们将对 Pod 进行更深入的阐释。
Novita AI 是一个一站式云平台,助力您实现 AI 雄心。通过无缝集成的 API、无服务器计算和 GPU 加速,我们提供经济高效的工具,助您快速构建和扩展 AI 驱动的业务。消除基础设施烦恼,免费开始使用——Novita AI 让您的 AI 梦想成为现实。
推荐阅读:
