Kubernetes 为什么不直接调度容器?非要套一层 Pod

几乎每个新手都会问这个问题:"我都打包好容器了,K8s 为什么还让我写 Pod YAML?直接部署容器不香吗?" 如果你觉得 Pod 是多此一举,那你还没见过没有 Pod 的世界。

🧠 先回到 2013 年:没有 Pod 的时代

那是 Docker 刚刚火起来的时候。开发者和运维人员都在做同一件事:把手头的应用拆成一个个容器。

但很快,一个棘手的问题出现了。

你有一个 Web 服务(nginx),它需要:

  • 一个本地缓存的 Redis(共享内存通信)
  • 一个日志采集的 Fluentd(读 nginx 的日志文件)

在物理机时代,这三个进程直接部署在同一台机器上:

php 复制代码
同一台机器:
  ├── nginx (localhost:80)
  ├── redis (localhost:6379)    ← 和 nginx 共享 localhost
  └── fluentd (读取 /var/log/nginx/*.log)

简单、直接、高效。nginx 通过 localhost:6379 访问 Redis,Fluentd 直接读本地的日志文件。

然后用 Docker 打包。容器的最佳实践说:一个容器只跑一个进程(single concern principle)。于是你有了三个容器:

makefile 复制代码
容器A: nginx
容器B: redis
容器C: fluentd

问题来了:

❌ nginx 没法通过 localhost 访问 Redis ------ 每个容器有自己独立的网络命名空间,有自己的 localhost。nginx 的 localhost 和 Redis 的 localhost 是两个完全隔离的网络栈。

❌ Fluentd 读不到 nginx 的日志文件 ------ 每个容器有自己独立的文件系统。Fluentd 容器里根本没有 nginx 的日志。

👉 容器化之前,在一台机器上共享 localhost 和文件系统是理所当然的事。容器化之后,这些"共享"都需要特事特办。

你当然可以用 Docker 的 --net=container:xxx--volumes-from--pid=container:xxx 来手动搞定。但很快你会意识到:

  • 你要手动保证这三个容器启动在同一台机器上
  • 启动顺序很重要(先启动 Redis,再启动 nginx)
  • 其中一个容器挂了,其他两个要不要重启?
  • 手动拼装的依赖关系,没法用 YAML 描述、没法用 CI/CD 管理
  • 没有统一的调度器保证它们部署在同一台节点

人们在 Docker Compose 和一堆 shell 脚本里疲于奔命。但问题是 Kubernetes 是一个集群级的编排系统,不是单机工具。它要调度容器到任意节点------如果不在一个抽象里把"这几个容器必须在一起"表达出来,调度器就没法干活。

这就是 Pod 存在的根本原因。

🎯 Pod 解决的核心问题:共享和共址

Pod 是一个抽象层,它说:这几个容器共享 Linux 命名空间,并且它们必须在同一台节点上

scss 复制代码
+-------- Pod (一个 IP,一个 hostname) --------+
|                                                |
|  [nginx] ── [redis] ── [fluentd]              |
|     │          │           │                   |
|     └─共享: Network / IPC / PID / Volume ─┘   |
+------------------------------------------------+

具体来说,Pod 内的容器共享:

1️⃣ Network namespace ------ 共享 localhost

同一个 Pod 内的容器用 localhost 互访。nginx 配置里写 redis://localhost:6379,访问的就是 Pod 内 Redis 容器的 6379 端口。不需要 Service、不需要 DNS、不需要知道对方 Pod 的 IP。

👉 这是 Sidecar 模式的物理基础。Envoy 代理能和业务容器共享同一个网络栈,就因为它们在同一个 Pod 里。

2️⃣ IPC namespace ------ 共享进程间通信

POSIX 消息队列、System V 信号量,这些传统 IPC 机制在 Pod 内的容器之间是可用的。虽然现代微服务很少用了,但遗留系统仍然依赖。

3️⃣ PID namespace(可选)------ 共享进程表

yaml 复制代码
spec:
  shareProcessNamespace: true

开启后,Pod 内的容器可以看到彼此的进程。ps aux 看到的不只是自己,而是整个 Pod 内所有容器的进程。

4️⃣ Volume ------ 共享文件系统

Pod 级别声明 Volume,多个容器挂在同一个目录下。日志采集器读业务容器的日志文件,就是这个模式。

c 复制代码
业务容器 → 写日志到 /var/log/app.log (共享 Volume)
Fluentd 容器 → 读 /var/log/app.log (同一个共享 Volume)

⚔️ 单容器多进程?为什么有人这么想,以及为什么是错的

"既然 Pod 是为了共享 localhost 和文件系统,那我把 nginx、Redis、Fluentd 全打包进一个容器不行吗?"

技术上可以。你知道怎么写 Dockerfile,把三个进程全装到一个镜像里,用一个 supervisor 管起来。

dockerfile 复制代码
FROM ubuntu:22.04
RUN apt-get install -y nginx redis-server
COPY fluentd /usr/local/bin/
COPY supervisord.conf /etc/
CMD ["supervisord"]

但这是通向地狱的道路:

进程管理 --- 谁负责重启挂掉的 Redis?supervisord 要配。日志怎么收集?要配。进程监控?要配。你等于在容器里重新发明了 Pod 的进程管理。

镜像构建 --- 每次 nginx 升级,你要重新构建整个镜像------哪怕 Redis 和 Fluentd 完全没变。CI 时间翻倍、镜像体积膨胀。

资源隔离 --- nginx 吃 CPU 很凶,连带把 Redis 饿死了------kubelet 只知道这个容器总共用了多少资源,不知道内部哪个进程是罪魁祸首。Pod 里每个容器有独立的 cgroup,能看到谁吃了多少。

版本管理 --- nginx 版本归 A 团队维护,Redis 版本归 B 团队维护,Fluentd 版本归 C 团队维护。一个镜像 = 三个团队耦合在一起 = 谁也动不了。

安全边界 --- Redis 容器用 redis 用户跑,nginx 用 www-data 用户跑。合在一个容器里?要么用 root(危险),要么徒手配 su/sudo + 用户切换(复杂且容易配错)。

👉 "一个容器多个进程"恰恰放弃了容器最核心的优势:进程级隔离和独立生命周期管理。Pod 就是用来解决这个问题的------在保持容器独立性的同时,提供进程间紧密协作的能力。

🧩 Init Containers:Pod 给你免费的启动编排

Pod 不只是"共享命名空间的容器集合",它还提供了容器之外的编排逻辑。

yaml 复制代码
spec:
  initContainers:
  - name: wait-for-db
    image: busybox
    command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
  - name: migrate-db
    image: my-app
    command: ['./migrate']
  containers:
  - name: app
    image: my-app

Init Containers 按顺序执行,上一个成功才到下一个。全部通过后,业务容器才启动。这是 Pod 级别的能力------如果你直接调度容器,需要在外部单独管理这些依赖编排。

👉 Init Containers 还能用不同的镜像(wait-for-db 用 busybox、migrate-db 用自己的应用镜像)。一个容器多进程做不了这个------你只有一套文件系统。

💥 调度原子性:生死与共,永不分离

这是 Pod 最容易被忽视的价值。

Kubernetes 的调度器(Scheduler)以 Pod 为最小调度单元。一个 Pod 内的所有容器一定在同一个节点上。不存在"nginx 调度到了 Node-A,Redis 调度到了 Node-B"的情况。

如果直接调度容器:

  • Scheduler 要理解"容器之间的亲和关系"
  • 一个容器调度失败,已经调度的"关联容器"要不要撤回?
  • 节点挂了,要分别追踪三个容器各自被调度到了哪里

👉 Pod 是把"这一组容器必须共存亡"这件事封装成了一个调度决策。

Pod 是 Kubernetes 调度、伸缩、更新、回滚的最小原子单元。不是容器,是 Pod。Deployment 的滚动更新是 Pod 级别的------一个 Pod 里的所有容器一起替换。HPA 扩的是 Pod 数量,不是容器数量。Service 的负载均衡目标是 Pod,不是容器。

🎯 Google Borg 的血泪教训

Kubernetes 的 Pod 设计直接来源于 Google 的 Borg 系统。在 Borg 里,Pod 对应的是 Alloc(Allocation)。

Google 内部的论文(Borg 论文,2015 年发表在 EuroSys)明确写了:

An "alloc" is a reserved set of resources on a machine in which one or more tasks can be run; the tasks share access to certain resources (e.g., files, ports, IP address).

翻译:Alloc 是一台机器上预留的一组资源,里面可以跑一个或多个 task,这些 task 共享资源(文件、端口、IP 地址)。

Google 在 Borg 时代就发现:直接调度进程(task),缺乏一个"共享环境"的抽象层,大量应用的部署变得异常复杂。 Kubernetes 继承了这一经验,把 Pod 作为第一公民。

就连 Google 内部跑了几十年的系统,最终也收敛到了"Pod"这个概念上。这足以说明:这不是设计过度,是经验的沉淀。

🧩 给你一张对比表:容器直接调度 vs Pod

markdown 复制代码
                      容器直接调度              Pod
                      
调度单元              单个容器                 一组紧密协作的容器
网络标识              各自独立 IP              一个 IP 所有容器共享
localhost 通信         不可能                    Pod 内容器之间 zero-cost
文件共享               需要外部存储              Pod 级 Volume
启动顺序              外部编排                  Init Containers(内置)
资源限制              单个容器                 每个容器独立 cgroup
生命周期管理           各自独立                 统一管理(Pod 级)
调度保证               需要外部亲和性规则        天然共址
更新粒度              容器级                   Pod 级(滚动更新一整组)
服务发现目标           容器                      Pod(Service 绑 Pod,不是容器)

💡 最后一击

Pod 不是 Kubernetes 为了让你多写几行 YAML 而强行加的一层包装。它解决的是一个根本性的问题:在容器时代,怎么让"几个进程"像在传统机器上那样自然地共享资源(网络、存储、IPC)------同时又保持容器本身的独立性和可维护性。

没有 Pod 的 Kubernetes = 你会被逼着用 Docker Compose + shell 脚本 + hand-rolled 调度器拼出一个山寨版 Pod。

Pod 不是负担,是赠品。它是 Google 用了十几年的经验告诉你:不要直接调度容器,要给每个应用一个"共享环境"。这个环境,就是 Pod。

相关推荐
亮亮不想说话958883 小时前
iOS底层探索 -- GCD分析
面试
程序员小假4 小时前
从问题到答案:RAG系统完整处理流程与核心机制深度拆解
后端·面试·agent
沉默王二9 小时前
阿里一面,我霸气反问:你说你们在做Agent项目,说说langchain、muti-agent、a2a这些你们都是怎么做的?面试官一直在擦汗。。
面试·agent·ai编程
云技纵横9 小时前
@Transactional 里套 REQUIRES_NEW,为什么会把连接池耗尽?
后端·面试
weedsfly10 小时前
栈和堆:JavaScript 内存的“旅馆”和“仓库”
前端·javascript·面试
SamDeepThinking10 小时前
函数式编程:用BiFunction消除多类型分支的代码重复
java·后端·面试
Ruihong1 天前
Vue withDefaults 转 React:VuReact 怎么处理?
vue.js·react.js·面试
kyriewen1 天前
别再这样写 async/await 了:我在 Code Review 中见过最多的 8 个错误
前端·javascript·面试
烬羽1 天前
字符串算法入门:从反转字符串到回文判断,面试不再慌
算法·面试