第 5 章 · 资源模型:为什么「Pod」是最小调度单元

一句话结论:K8s 的资源模型是它所有能力的基础,而其中最反直觉、也最关键的一个设计是——最小调度单元不是「容器」,而是「Pod」(一个或多个容器组成的组)。因为现实中,有些容器天生必须「共享网络、共享存储、同生共死」,把它们强行拆成独立单元,反而违背了它们真实的运行需求。


1. 先问:为什么不直接调度「容器」?

容器(container)是运行时最小单位,直觉上「调度容器」最自然。但你很快会撞上一个真实场景:

一个 Web 服务,旁边要配一个「日志采集」容器(把日志实时转发出去)。这两个容器必须共享同一份日志文件,而且必须部署在同一台机器、同生共死(Web 挂了,日志采集也没意义了)。

如果你把「容器」当作调度单位,就得手动保证「这两个容器永远放同一台机器、共享存储、一起启停」——这个「组合关系」无处安放,全得靠运维人肉保证,一扩容就乱。

Pod 就是为这个「必须紧密协作的容器组」而生的抽象:它是「共享网络命名空间 + 共享存储卷 + 共享生命周期」的一组容器的集合。


2. Pod 的三个「共享」,说清了它的本质

共享项含义解决的真实需求
网络命名空间Pod 内所有容器共享一个 IP、一个端口空间容器间用 localhost 通信,不用管 IP 和端口转发
存储卷(Volume)挂在 Pod 级的卷,所有容器可见日志采集容器能读到 Web 容器的日志文件
生命周期一起创建、一起终止Web 挂了,日志容器也随之清理

为什么这样设计:因为它反映了容器协作的真实形态。把「必须在一起的东西」建模成一个不可分割的单元,调度、网络、存储、生命周期这些复杂问题,就都简化成「对 Pod 这一个单元的处理」。

类比:调度「容器」像调度「个人」,调度「Pod」像调度「家庭」——有些活(日志采集和 Web)是一家人,必须住同一间房、共用水电、一起搬家。硬把一家人拆成独立个体去调度,反而制造麻烦。


3. 思考的分叉:抽象怎么「分层」才合理

有了 Pod,K8s 还在它上面叠了几层抽象。每一层解决一个「Pod 单独解决不了」的问题:

Deployment ──▶ 管理 ReplicaSet(副本数、滚动更新)
    └── ReplicaSet ──▶ 维持 N 个相同的 Pod
            └── Pod ──▶ 跑一组容器

为什么要分层,而不是「一个对象管所有」? 因为不同关注点要分开:

  • Pod:关心「这一组容器怎么跑」。
  • ReplicaSet:关心「同一模板的 Pod 保持 N 个」。
  • Deployment:关心「怎么从一个版本平滑滚到另一个版本」。

如果让一个「Deployment」既管副本、又管容器细节、又管滚动策略,它就会变成一个巨大的、难以维护的怪物。分层 = 每个对象只关心一层,职责单一(这和第 3 章「一个控制器管一件事」是同一思想)。

而 Service 这个抽象,解决的是另一个维度的问题——Pod 是**「易变、易死」的(扩缩容、重启会让 Pod 的 IP 变来变去),而服务消费者需要一个「稳定」的访问入口**。于是:

Service(稳定的虚拟 IP + DNS 名)
    └── selector 动态选择一组 Pod(label 匹配)

4. label / selector:一个被低估的「解耦」设计

Service 怎么知道「我代理哪几个 Pod」?K8s 的答案不是「硬编码 Pod 的 IP 列表」,而是 label(标签)+ selector(选择器):

# Service 通过 label 选择器,动态匹配「有 app: web 标签」的 Pod
spec:
  selector:
    app: web        # 不是「代理 IP 1.2.3.4」,而是「代理所有带 app=web 的 Pod」

这个设计精妙在哪:

  • 解耦:Service 不关心「具体是哪个 Pod」,只关心「满足条件的那些」。Pod 随便重启、换 IP,只要 label 不变,Service 自动跟上。
  • 松散的关联:label 是「贴在对象上的元数据」,可以随时增删改,关联关系变得极其灵活(一个 Pod 可以同时被多个 Service 选中,只要它贴了多个 label)。

类比:硬编码 IP 像「给外卖员留一个具体门牌号」——你搬家了,外卖就送不到。label/selector 像「给外卖员留『找门口挂着红灯笼的那家』」——你搬到哪,只要还挂红灯笼,他就找得到。用「特征」而不是「地址」来建立关联,是应对「对象易变」的关键设计。

这个「用标签做松耦合关联」的思想,在后面 CRD/Operator(第 8 章)里还会反复出现,是 K8s 扩展性的重要基石。


5. 思维流重放:为什么最小单元不是「容器」

第 1 节给了结论(因为有些容器必须同机协作),这一节重走「当年的人是怎么在『容器』和『机器』之间,找到 Pod 这个中间点的」。

第 1 步 · 直觉:调度的是容器,那最小单元自然就是「容器」。细粒度、灵活,似乎最优。

第 2 步 · 推演:但真实的容器不是孤立的。一个 Web 服务旁边要挂日志采集、网络代理(sidecar),这些必须同机、共享网络和存储、同生共死。如果单元是「容器」,这个「必须在一起」的关系无处安放——要么靠运维人肉保证(一扩容就乱),要么在调度器里写一堆「这几个容器绑一起」的特例逻辑。

第 3 步 · 反转方向一:那把单元设成「虚拟机/机器」?——粒度又太粗,回到「一机跑一堆、资源浪费、启动慢」的老路,丢了容器的轻量优势。

第 4 步 · 反转方向二:能不能在「容器」和「机器」之间,加一层「容器组」的概念?——把『必须紧密协作的一组容器』建模成一个不可分割的单元。这样调度器只处理「这一个单元」,网络、存储、生命周期也都简化为「对单元的处理」。

第 5 步 · 权衡收敛:Pod 引入了一层新抽象,代价是「多了一个概念要学、要理解」。但它卡在「容器太细、机器太重」之间的甜点上,而且它强制「同机共享」,反而把一个本可能很复杂的分布式协作问题,简化成了「单机进程组」问题。选 Pod。

一句话复盘:Pod 的本质不是「发明了一个新名词」,而是**「抽象粒度的选择」**——它找到了「容器太细、机器太重」之间那个恰到好处的点。好的抽象,往往不是『能力更强』,而是『粒度刚好、把不可能关在门外』。


思考题

Q:Pod 解决了「共享容器组」的问题,但为什么 K8s 不干脆让 Pod 也能「跨机器」——比如日志容器放 A 机器、Web 容器放 B 机器,靠网络通信?这样调度不是更灵活吗?

参考答案:

因为「跨机器共享」会摧毁 Pod 存在的根本理由,并引入新的复杂度:

  1. 共享的语义就没了:Pod 的核心价值是「共享网络命名空间 + 共享存储 + 同生命周期」。一旦允许跨机器,这三个「共享」全部失效——跨机器的容器不可能共享一个 IP、共享一块本地卷、保证同生共死。允许跨机器 = 把 Pod 退化成「普通的两个独立容器」。
  2. 引入了分布式通信的复杂度:跨机器意味着要处理网络分区、延迟、部分失败。而「同机器紧密协作」恰恰是为了规避这些分布式难题——同一台机器上,localhost 通信又快又可靠,共享本地卷也是直接的。
  3. 真实需求不需要:需要「跨机器协作」的场景,本来就应该用「两个 Pod + Service」来表达,而不是强行塞进一个 Pod。

所以答案指向一个更深的洞察:抽象的价值,在于它「排除了一些可能」来换取「简单和可靠」。Pod 通过强制「同机共享」,把一个本可能很复杂的分布式协作问题,简化成了「单机进程组」问题。好的抽象,往往不是「能力更强」,而是「边界更清晰、把不可能关在门外」。