上一篇我们把容器拆到了内核层——namespace、cgroup、镜像分层。但单机 docker run 起几个容器,离”上生产”还差着十万八千里:几百个容器怎么调度到几十台机器上?挂了怎么自愈?升级怎么不停服?流量怎么找到不断被重建、IP 一直变的容器? 这些”编排”问题,就是 Kubernetes 要解决的。
这一篇不堆 YAML 字段手册,而是把 K8s 最核心的一组对象——Pod、Deployment、Service、Ingress、ConfigMap/Secret——以及它们背后那套声明式 + reconcile 控制循环的心智讲透。理解了这套心智,你看任何 K8s 资源都是同一个套路。
本文是容器化与 K8s 部署系列的第 2 篇。 全系列 5 篇:
- 容器化(开篇)· Docker 原理与镜像分层
- Kubernetes 核心对象 · Pod/Deployment/Service/Ingress(本篇)
- K8s 调度、资源与弹性伸缩
- CI/CD 流水线 · Jenkins 构建镜像与自动部署到 K8s
- 广告服务上云实战 · 容器化部署与调优
一句话定位:Kubernetes 是一个”声明式的期望状态系统”——你用 YAML 声明”我想要什么”(3 个副本、这个镜像、暴露这个端口),一堆控制器不停地把集群的实际状态朝你的期望状态收敛;Pod/Deployment/Service 就是这套系统里最核心的几种”期望”。
TL;DR
- 声明式 vs 命令式:你不告诉 K8s”怎么做”(起容器、绑端口),只声明”想要的最终状态”;控制器负责持续 reconcile(对比期望 spec vs 实际 status,有差就修)。这是理解 K8s 的第一心智。
- 控制面四大件:kube-apiserver(唯一入口,所有组件只跟它对话)、etcd(集群唯一状态存储,基于 Raft,见共识)、scheduler(把 Pod 绑到合适节点)、controller-manager(一堆控制器跑 reconcile)。
- 节点两大件:kubelet(管本节点 Pod 生命周期、调 CRI 起容器、回写状态)、kube-proxy(维护 Service 的转发规则)。
- 铁律:所有组件都只跟 apiserver 通信,绝不互相直连;集群状态的唯一真相在 etcd。
- Pod 是最小调度单位:一个 Pod 里可有多个共享网络(同 IP/端口空间)和存储的容器(主容器 + sidecar)。Pod 是”牲畜不是宠物”——随时可被杀掉重建,IP 会变。
- ReplicaSet 保副本数、Deployment 管版本:你几乎从不直接建 Pod。Deployment 声明”我要 N 个这个版本的 Pod”,它管 ReplicaSet,ReplicaSet 管 Pod,并支持滚动更新与回滚。
- Service 提供稳定入口:Pod IP 会漂,Service 给一个稳定虚拟 IP(ClusterIP)+ DNS 名,靠 label selector 动态关联后端 Pod(Endpoints 自动更新),kube-proxy 做负载均衡。
- Service 三种类型:
ClusterIP(集群内)、NodePort(节点端口对外)、LoadBalancer(云 LB 对外);Ingress 在其上做 L7 域名/路径路由 + TLS 终止。 - 配置与密钥分离:
ConfigMap(非敏感配置)、Secret(敏感信息,base64 编码非加密,需配 RBAC/加密存储)——把配置从镜像里剥离,一个镜像多环境复用。 - Namespace 做逻辑隔离:按团队/环境切分资源、配额和权限边界。
- AdTech 血泪:Service 的 label selector 写错(和 Pod label 不匹配),Endpoints 为空 → 流量打进去直接黑洞、大量 no available backend,而 Pod 本身全是健康的——排查方向全错。
Table of contents
Open Table of contents
1. 声明式与 reconcile:理解 K8s 的第一心智
学 K8s 最容易犯的错,是拿”命令式脚本”的脑子去套。命令式是:启动容器 → 绑端口 → 挂载盘 → 如果挂了我再写脚本重启。K8s 是声明式的:你交给它一份 YAML,说”我要 3 个跑 bid-service:v2 镜像的副本,对外暴露 8080”,然后就不管了——剩下的”怎么达成、挂了怎么补、机器没了怎么重调度”,全是 K8s 的事。
这套魔法的核心叫 reconcile(调谐/协调)控制循环:
每种资源都有 spec(期望状态,你写的) 和 status(实际状态,系统回写的)。对应的控制器(controller) 就是一个死循环:不停地读期望、读实际、对比差异、执行动作把实际朝期望推,直到两者一致(收敛)。
Pod 挂了?副本数从 3 掉到 2,ReplicaSet 控制器发现”期望 3、实际 2”,立刻补一个。节点宕机?上面的 Pod 没了,控制器在别的节点重建。你声明的是一个”目标”,系统自己想办法维持这个目标——这就是 K8s 自愈能力的来源。记住这一条,后面所有对象都是这个模式的实例。
2. 集群架构:控制面与工作节点
K8s 集群分成两类角色:控制面(Control Plane,大脑) 和 工作节点(Worker Node,干活的)。
控制面四大件(apiserver/etcd/scheduler/controller-manager)负责编排决策,节点上的 kubelet/kube-proxy 负责把决策落成运行的容器和转发规则。所有组件只跟 apiserver 对话。
2.1 控制面组件
- kube-apiserver:集群的唯一入口和中枢。所有操作(
kubectl、控制器、kubelet)都通过它的 REST API 读写资源。它负责认证、鉴权、准入控制、校验,然后把状态持久化到 etcd。它是唯一直接读写 etcd 的组件。 - etcd:集群的唯一状态存储——所有对象(Pod/Deployment/Service…)的 spec 和 status 都存在这里。它是一个强一致的分布式 KV,底层用 Raft 共识。etcd 挂了 = 集群失忆,所以生产上一定是奇数节点(3/5)高可用部署 + 定期备份。
- kube-scheduler:只干一件事——把还没绑定节点的 Pod,挑一个合适的节点绑上去。它综合资源余量、亲和性、污点等打分(详见第 3 篇)。
- kube-controller-manager:一个进程里跑着一大堆控制器(Deployment、ReplicaSet、Node、Endpoints、Job…),每个都是一个 reconcile 循环,各自维护自己那类资源的期望状态。
2.2 工作节点组件
- kubelet:每个节点上的 agent。它 watch apiserver,发现”有 Pod 被调度到我这”,就调用容器运行时(CRI → containerd → runc)把容器拉起来,并持续做健康检查、把实际状态回写给 apiserver。
- kube-proxy:维护本节点上 Service 的流量转发规则(iptables/IPVS/eBPF),让访问 ClusterIP 的流量被负载均衡到后端 Pod。
- 容器运行时:真正跑容器的 containerd/CRI-O(见上一篇 的运行时链路)。
2.3 一条铁律:万物经 apiserver
初学者常画错的架构图是”scheduler 直接指挥 kubelet""controller 直接改 etcd”。都不对。真相是:
所有组件都只跟 apiserver 通信,通过 watch/list 感知变化、通过写资源表达意图,彼此之间从不直连。 apiserver 是唯一的读写枢纽,etcd 只被 apiserver 独占访问。
这个设计的好处是解耦和可扩展:任何新组件(自定义控制器、Operator)只要会调 apiserver、会 watch 资源,就能无缝接入这套体系——这也是 K8s 生态如此繁荣的架构基础。
3. 一次 kubectl apply 发生了什么
把声明式和 reconcile 串起来,走一遍 kubectl apply -f deployment.yaml(期望 3 副本)的完整链路:
声明式提交期望 → 各控制器 watch 到差异 → 创建 Pod、调度、拉起容器、回写状态。整个过程没人被”命令”,都是各自 watch + reconcile 出来的。
- 提交期望:
kubectl apply把 Deployment 的 spec(3 副本、镜像、端口)发给 apiserver,apiserver 校验后写进 etcd。到这一步,集群里其实还没有任何容器——你只是登记了一个”愿望”。 - Deployment 控制器补齐 Pod:Deployment 控制器 watch 到这个新对象,对比”期望 3 / 实际 0”,于是创建一个 ReplicaSet;ReplicaSet 控制器再创建 3 个 Pod 对象(此时 Pod 还没被调度到任何节点,处于 Pending)。
- scheduler 绑定节点:scheduler watch 到这些”未绑定节点的 Pod”,给每个 Pod 打分选出合适的节点,把绑定关系写回 apiserver。
- kubelet 拉起容器:对应节点的 kubelet watch 到”有 Pod 分给我了”,调用 CRI 拉镜像、创建容器,Pod 真正跑起来。
- 回写状态:kubelet 把 Pod 的实际状态(Running)回写给 apiserver → etcd。此刻”期望 3 / 实际 3”,收敛完成。
- 持续维持:之后控制器仍在死循环里盯着——任何一个 Pod 挂了,实际掉到 2,立刻再补一个。这就是声明式系统的自愈。
注意整个过程里没有任何组件”命令”另一个组件。Deployment 控制器没有”告诉” scheduler 去调度,scheduler 也没”通知” kubelet;大家都只是各自 watch apiserver 上的资源变化,做自己那一步 reconcile。这种”基于共享状态的松耦合协作”是 K8s 架构的精髓。
4. 工作负载对象:Pod、ReplicaSet、Deployment
4.1 Pod:最小调度单位
Pod 是 K8s 调度和管理的最小单位,而不是容器。一个 Pod 里可以有一个或多个容器,它们:
- 共享网络 namespace:同一个 Pod 里的容器共享 IP 和端口空间,彼此可以用
localhost互通。 - 可共享存储卷:挂同一个 volume 交换数据。
- 同生共死、同节点调度:一个 Pod 的所有容器总在同一节点、一起被调度、一起被销毁。
典型的”多容器 Pod”是主容器 + sidecar:主容器跑业务,sidecar 跑日志采集、服务网格代理(Envoy)、指标导出等。
最关键的认知:Pod 是”牲畜不是宠物”(cattle, not pets)。它随时可能因为节点故障、滚动更新、扩缩容被杀掉重建,重建后 IP 会变、名字会变。所以你永远不该记住某个 Pod 的 IP 去访问它——这正是需要 Service 的原因(§5)。也因此,Pod 应尽量无状态(状态沉到外部存储),才敢被随意重建。
4.2 ReplicaSet:维持副本数
直接创建的裸 Pod(bare pod)没人管——挂了就没了,不会重建。ReplicaSet 的职责就是维持指定数量的 Pod 副本:声明 replicas: 3,它就通过 reconcile 保证”任何时刻都有 3 个匹配的 Pod 在跑”,多了删、少了补。
4.3 Deployment:管版本与滚动更新
但你几乎不会直接创建 ReplicaSet——你用 Deployment。Deployment 在 ReplicaSet 之上加了版本管理和发布策略:
apiVersion: apps/v1
kind: Deployment
metadata:
name: bid-service
spec:
replicas: 3
selector:
matchLabels: { app: bid-service } # 管理哪些 Pod(靠 label 匹配)
template: # Pod 模板
metadata:
labels: { app: bid-service } # 必须与上面 selector 匹配!
spec:
containers:
- name: bid
image: registry/bid-service:v2
ports: [{ containerPort: 8080 }]
它的核心能力:
- 滚动更新(RollingUpdate):改镜像
v1 → v2后,Deployment 会新建一个 v2 的 ReplicaSet,逐步把 v2 Pod 拉起、把 v1 Pod 缩掉(受maxSurge/maxUnavailable控制),全程不中断服务。 - 回滚:每次发布都记录历史 ReplicaSet,一条
kubectl rollout undo就能回到上个版本。 - 暂停/恢复:可以暂停发布做金丝雀观察。
滚动更新、金丝雀、优雅停机的深水区留到末篇实战 结合 发布与变更安全 展开。层级关系记住一句:Deployment 管 ReplicaSet,ReplicaSet 管 Pod。
5. Service 与 Ingress:流量怎么找到 Pod
Pod 的 IP 会漂、会随重建变化,那外部或其他服务怎么稳定地访问它?答案是 Service——K8s 里最容易出问题、也最值得讲透的对象。
Service 用 label selector 动态关联一组 Pod、提供稳定的虚拟 IP + DNS;Ingress 在其上做 L7 路由。Pod 增删时 Endpoints 自动更新,前端流量无感。
5.1 Service:稳定入口 + 动态后端
Service 做两件事:
- 提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名(如
bid-service.default.svc.cluster.local),这个 IP/名字在 Service 生命周期内永不变。 - 用 label selector 动态关联一组后端 Pod:Service 声明
selector: {app: bid-service},K8s 就自动把所有 label 匹配的、健康的 Pod IP 收集成一个 Endpoints(EndpointSlice) 列表。Pod 增删/重建时,Endpoints 自动更新——前端始终访问 Service 的稳定 IP,kube-proxy 负责把流量负载均衡到当前健康的后端 Pod。
apiVersion: v1
kind: Service
metadata:
name: bid-service
spec:
selector:
app: bid-service # ← 必须匹配 Pod 的 label,否则 Endpoints 为空!
ports:
- port: 80 # Service 端口
targetPort: 8080 # 转发到 Pod 的端口
selector 与 Pod label 必须匹配——这是 §8 生产事故的核心。selector 写错一个字,Endpoints 就是空的,流量进 Service 后无处可去,全部黑洞,而 Pod 本身完全健康。
5.2 三种 Service 类型
| 类型 | 作用 | 典型用途 |
|---|---|---|
| ClusterIP(默认) | 只在集群内部可达的虚拟 IP | 微服务之间互相调用 |
| NodePort | 在每个节点上开一个端口(30000–32767),外部经 节点IP:端口 访问 | 简单对外暴露、测试 |
| LoadBalancer | 云厂商创建一个外部 LB + 公网 IP,指向后端 | 生产对外入口 |
5.3 Ingress:L7 路由与 TLS 终止
如果每个对外服务都开一个 LoadBalancer,成本和管理都爆炸。Ingress 让一个入口 LB 后面,按 L7 规则(域名 / 路径) 把流量分流到不同的 Service:
api.ad.com/bid→bid-serviceapi.ad.com/report→report-service
它还统一做 TLS 终止(证书集中管理)。Ingress 本身只是一份”路由规则”,真正干活的是 Ingress Controller(Nginx/Traefik/云原生网关)。它也是灰度/金丝雀按权重分流的天然挂载点(呼应 发布与变更安全,更细的流量切分可交给 Service Mesh)。
6. 配置与隔离:ConfigMap、Secret、Namespace
好的容器实践是镜像不可变、配置外置——同一个镜像跑在 dev/staging/prod,只是注入的配置不同。K8s 用两个对象承载配置:
- ConfigMap:存非敏感配置(数据库地址、超时时间、feature flag)。可以以环境变量或挂载文件的方式注入容器。改配置不用重新构建镜像。
- Secret:存敏感信息(密码、token、证书、密钥)。用法和 ConfigMap 类似,但——注意 Secret 默认只是 base64 编码,不是加密!要真正安全,必须配合 etcd 静态加密(encryption at rest)+ RBAC 严格授权 + 外部密钥管理(如 Vault/云 KMS)。别把 Secret 当保险箱,它只是”别明晃晃写在 YAML 里”的程度。
Namespace 则是逻辑隔离的单位:把不同团队、不同环境(dev/prod)、不同项目的资源分到不同 namespace,各自有独立的名字空间、资源配额(ResourceQuota) 和 RBAC 权限边界。DNS 名里的 .default. 那一段就是 namespace。它是软隔离(不是安全边界),但对多团队共用一个集群的组织协作至关重要。
7. 声明式带来的运维范式转变
把前面的对象串起来,K8s 其实重塑了运维心智:
- 不再”操作服务器”,而是”声明期望”:你不再 SSH 上机器改配置、重启进程,而是改 YAML、
apply,让控制器去收敛。基础设施变成了可版本化、可 review、可回滚的代码(GitOps)。 - 故障自愈是默认行为:Pod 挂了自动重建、节点没了自动重调度——不用你写监控脚本 + 重启逻辑。
- 一切皆对象、皆可扩展:Pod/Service/Deployment 是内置对象,但你可以用 CRD + 自定义控制器(Operator) 定义自己的资源类型,复用同一套 reconcile 机制来编排数据库、消息队列、甚至业务工作流。
这套范式的代价是心智门槛和复杂度——你得理解声明式、label selector、reconcile,才不会在出问题时抓瞎。下面这个事故就是典型。
参考
- Kubernetes. Kubernetes 官方文档 · Concepts:Pod/Deployment/Service/Ingress、控制面架构的一手权威说明。
- Kubernetes. Kubernetes Components(集群组件):apiserver/etcd/scheduler/controller-manager/kubelet/kube-proxy 的职责说明。
- Kubernetes. Service / Ingress 官方文档:Service 三种类型、Endpoints、Ingress L7 路由的权威参考。
- Kubernetes. Deployments:滚动更新、回滚、ReplicaSet 层级关系。
- Brendan Burns, Joe Beda, Kelsey Hightower. Kubernetes: Up and Running(O’Reilly):K8s 核心对象与声明式模型的系统性经典读物。