几乎每个新手都会问这个问题:"我都打包好容器了,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。