Kubernetes 核心三元组:深入理解 Deployment、Pod 与 Service
在容器编排的生态中,Kubernetes并非简单的容器调度器,而是一个基于声明式 API 的自动化协作系统。
Deployment、Pod 与 Service 构成了 K8s 最基础的“黄金三元组”:Deployment负责状态达成,Pod 负责最小运行单元,另一个负责访问入口抽象。本文的目标不是罗列字段,而是帮你建立一条完整的思维链路,让你能从“应用如何运行”一路推演到“流量如何到达”。
一个负责声明应用的理想状态,一个负责承载实际运行的工作负载,另一个则负责在动态变化之上提供一个稳定的访问抽象。
1. 本质上的分工:声明、运行与暴露
1.1 Deployment 是期望状态的守护者
Deployment 是 Kubernetes 中典型的控制器(Controller),它并不直接操控容器,而是持续地做一件事:比较期望状态和当前状态,然后驱动集群向期望状态靠拢。你在 Deployment 里写下的副本数、镜像版本、环境变量、卷挂载等内容,就是你对这个应用所定义的“期望”。Deployment 通过创建和管理 ReplicaSet 来达成这一期望,而 ReplicaSet 再负责维持具体数量的 Pod 实例。
这种间接控制带来了滚动更新、回滚和扩缩容的天然能力。当你修改 Deployment 的镜像标签时,它不会立刻删光所有旧 Pod,而是会新建一个 ReplicaSet,逐步增加新 Pod 并减少旧 Pod,在过程中保证可用副本数始终不低于某个阈值。一旦出现问题,你还可以随时回滚到前一个 Revision。
Deployment 的核心职责是“维护期望副本数并编排变更过程”,它把“一次性运行实例”的不稳定收敛为“持续可用服务”的确定状态。
1.2 Pod 是运行的最小原子
Pod 是 Kubernetes 调度的最小单位,也是容器的“运行边界”。一个 Pod 可以包含一个或多个容器,这些容器共享同一个网络命名空间(即同一个 IP 地址和端口空间)和同一个 IPC 命名空间,还可以通过挂载卷共享文件。这意味着同一个 Pod 内的容器可以像同一台主机上的进程一样通过 localhost 互相通信,这一点是 Sidecar 模式(如日志收集、配置热加载、服务网格代理)得以成立的技术基础。
Pod 最需要被记住的特性是 “短暂性”和“可置换性”。Pod 的 IP 会变化,Pod 会因为节点故障、资源不足或滚动更新而消失,新 Pod 会替代它们。Kubernetes 在设计上并不鼓励直接操作单个 Pod,也不推荐依赖 Pod IP 进行服务间通信,因为 Pod 本身不是一个稳定实体。
1.3 Service 是稳定的访问入口
如果 Pod 是临时工,那么 Service 就是公司总机。无论背后的员工怎么换,你只需记住一个号码。Service 被创建时会获得一个固定的虚拟 IP(ClusterIP),集群内的其他组件可以通过这个 IP 或对应的 DNS 名称稳定访问到一组动态变化的 Pod。Service 通过标签选择器(Label Selector)持续匹配符合条件的 Pod,并将它们的 IP 地址和端口注册到称为 Endpoints(或更高效的 EndpointSlice)的对象中。当 Pod 增加、删除或 IP 变更时,Endpoints 自动更新,而 Service 的虚拟 IP 永远不变。
这一层的抽象,是 Kubernetes 实现服务发现和负载均衡的基石。没有 Service,Pod 之间就只能通过脆弱的、随时可能失效的 IP 相互寻找;有了 Service,整个系统便具备了自愈和弹性伸缩后的自动重连能力。
2.标签与选择器:动态关系的万能胶水
Kubernetes 并不通过写死 IP 地址来绑定资源之间的关系,而是使用标签(Label)和选择器(Selector)建立松耦合的关联。标签是附加在对象上的键值对,而选择器则是查询标签的条件。
Deployment 在 Pod 模板中定义的标签,会被所有由它创建的 Pod 继承。Service 的 spec.selector 字段则指明了“只有带有这些标签的 Pod 才能接收流量”。 当这两者使用同一套标签时,Service 就能准确发现并负载均衡到正确的后端。这是一种动态订阅机制:Pod 不需要主动向 Service 注册,只要它的标签满足 Service 的选择器,它就会被自动纳入后端池;反之,当标签被修改或 Pod 消失时,它会被立即移除。
正因为标签是动态关联的核心,实际工作中标签体系的设计至关重要。推荐使用有意义的键,如 app.kubernetes.io/name、app.kubernetes.io/component、app.kubernetes.io/version 等标准化推荐标签,而不仅仅是 app: my-app。这不仅让 Service 选择器更清晰,也为监控、日志收集、灰度发布等高级功能提供了基础。
3.端口体系的四层模型
Kubernetes 中与端口相关的字段有四个,它们分别作用在不同的抽象层。只要以 “谁定义、谁使用、谁暴露” 的框架去理解,就不会混淆。
字段 | 定义位置 | 作用 | 特性 |
|---|---|---|---|
| Pod 容器定义 | 声明容器内应用监听的端口 | 仅文档化,不影响连通性,但支持命名和探针引用 |
| Service 定义 | 指定流量转发到 Pod 中的哪个端口 | 可以是数字,也可以是 |
| Service 定义 | Service 自身暴露在集群内的端口 | 其他 Pod 通过 |
| Service 定义 | 在每个节点上对外暴露的固定端口 | 范围通常为 30000-32767,允许从集群外部直接访问 |
端口的命名是容易被忽视但极为有用的特性。在容器定义中给 containerPort 设置 name: http,然后在 Service 的 targetPort 中直接写 http,而不是硬编码数字。这样做的好处是:当容器端口因镜像升级而改变时,你只需修改容器定义中的端口号,Service 配置完全不用动,大大提升了可维护性。
流量路径闭环:
外部请求 → NodeIP:nodePort(节点端口) → ServiceIP:port(Service 虚拟端口) → PodIP:targetPort(后端目标端口) → Container:containerPort(容器监听端口)
其中 nodePort 解决“怎么从集群外进来”,port 解决“Service 以什么身份对内服务”,targetPort 解决“最后交给 Pod 的哪个端口处理”。这三层各司其职,构成了服务暴露的完整闭环。
4.Service 的暴露形态:从集群内到全球可达
根据使用场景的不同,Service 可以配置为四种类型,每种类型都代表了一种对访问范围的声明。
ClusterIP(默认):仅为集群内部提供虚拟 IP,只能在集群内的 Pod 或节点上访问。这是微服务间通信的首选方式,安全、无外部依赖。
NodePort:在 ClusterIP 之上,在每个节点的 IP 上开一个静态高端端口。方便测试、学习或简单暴露,但生产环境不建议大规模使用,因为端口管理混乱且缺乏统一的七层治理。
LoadBalancer:在 NodePort 的基础上,向云厂商请求一个外部负载均衡器,并将流量引入集群。适合云上对外服务,但通常每个 Service 会独占一个负载均衡器,成本较高。
ExternalName:不分配 ClusterIP,而是通过返回一个 CNAME 记录,将服务请求转发到集群外部的某个 DNS 名称。常用于整合外部数据库、缓存等第三方服务。
当业务规模扩大、需要对外提供多个 HTTP 服务时,更优雅的方式是使用 Ingress。Ingress 不是 Service 类型,而是一个独立的 API 对象,它工作在七层,可以提供基于域名和路径的路由、TLS 终止、流量灰度等能力。理想的生产架构是:集群内部服务全部使用 ClusterIP,边缘使用一个 LoadBalancer 对外暴露 Ingress Controller,再由 Ingress 规则将流量分发到内部各个 ClusterIP Service 上。这样既保持了内部的简洁与安全,又集中管理了外部入口策略。
5. 健康探针:将“运行中”与“可服务”区分开
一个容器在运行并不代表它能够处理请求。应用可能正在启动加载数据,也可能因死锁而完全挂起。Kubernetes 使用三种探针来区分这些状态,并由 kubelet 执行检查。
startupProbe(启动探针):判断容器是否已完成启动。当这个探针成功之前,其他探针不会生效,适用于启动时间较长的应用,可以避免启动缓慢的容器被存活探针意外杀死。
readinessProbe(就绪探针):判断容器是否已准备好接收业务流量。一旦失败,Service 会将该 Pod 从 Endpoints 中移除,直到探针恢复成功。它解决的是“慢初始化”和“后端依赖未就绪”的问题。
livenessProbe(存活探针):判断容器内部状态是否健康。如果失败,kubelet 会重启该容器。它主要用于修复死锁、内存溢出导致的僵死等故障,但不能轻易用于依赖外部资源的检查,否则可能引起级联重启。
正确使用探针是服务稳定性的基础设施。一个常见的错误是混淆就绪和存活:将过于严格的检查放在存活探针里,会导致应用在遇到临时故障时被频繁重启,反而加剧系统抖动。经验法则是:存活探针只检查内部不可恢复的错误,就绪探针可以包含对外部依赖的检查,而启动探针为慢启动提供保护窗口。
6. 完整的协作示例
下面这个示例不是为了展示所有字段,而是为了突出 Deployment、Pod、Service 三者如何通过标签和端口名耦合在一起。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-demo
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: web-demo
template:
metadata:
labels:
app.kubernetes.io/name: web-demo
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 2
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: web-demo-svc
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: web-demo
ports:
- name: http
port: 8080 # Service 对内暴露的端口
targetPort: http # 指向容器端口名 'http'在这个例子中:
Deployment 通过
spec.selector.matchLabels和template.metadata.labels的统一,保证它管理的 Pod 都带有app.kubernetes.io/name: web-demo标签。Service 的选择器匹配相同标签,动态找到所有就绪的 Pod。
Service 的
port设为 8080,意味着集群内其他应用可以通过web-demo-svc:8080访问到这个服务。而targetPort: http利用端口命名,透明地转发到容器的 80 端口。readinessProbe 确保只有真正通过 HTTP 健康检查的 Pod 才会接收流量,避免了请求落入未就绪的实例。
7. 架构图
架构图要点说明:
控制链路:
Deployment创建ReplicaSet,ReplicaSet负责维持期望数量的Pod。这是应用“声明式管理”的根路径。动态关联:每个 Pod 携带标签(如
app.kubernetes.io/name=web-demo),Service通过selector匹配这些标签,并动态生成Endpoints列表。Pod 的变化会自动同步到 Endpoints,无需手动维护 IP。流量路径:外部用户请求先到达
LoadBalancer或Ingress Controller,然后被转发到Service的port,再由Service负载均衡到某一个后端 Pod 的targetPort(最终落到容器的containerPort)。健康保障:每个 Pod 都配置了
readinessProbe和livenessProbe,只有通过就绪探针的 Pod 才会被加入Endpoints接收流量;存活探针则负责将僵死的容器重启。端口映射:图中虚线注释标注了完整的四层端口链:
nodePort(节点入口)→port(Service 虚拟端口)→targetPort(后端目标)→containerPort(容器监听)。
8. 从关系视角看故障排查
面对一个“Service 不通”的问题,不需要盲目翻看所有资源,而是要沿着对象协作的链条逐级定位。这条链条是:
Pod 是否就绪?→ Service 是否选中了 Pod?→ 端口映射是否正确?→ 外部入口是否配置正确?
如果 Pod 长时间非 Ready,先描述 Pod 查看事件和探针输出。
如果 Pod 已经 Ready,但 Service 没有 Endpoints,则需要检查 Service 的
selector和 Pod 的标签是否精确匹配(包括大小写)。如果 Endpoints 已存在但访问超时,则检查
targetPort是否等于容器实际监听端口,或检查网络策略是否阻断流量。对外暴露的场景下,还需要确认 Service 的类型、NodePort 范围、Ingress 规则或负载均衡器设置。
这个思考顺序本身,就是对 Deployment → Pod → Service 协作模型的最佳实践应用。