Pod/Deployment/Service/Ingress------后端上生产的四个分水岭
容器化部署容易,但很多人只照着 YAML 复制粘贴、没系统学过 K8s,出故障时连应用跑在哪个 Pod、日志去哪捞、重启容器和发布新版本有什么区别都搞不清。云原生这关,你不一定要成为 K8s 专家,但 Pod、Deployment、Service、Ingress 这四个对象,是从"会写 Java"到"能扛住生产"的分水岭------搞不懂它们,出问题时你连战场在哪都不知道。
本文用四个核心对象,把一个 Spring Boot 应用从"裸进程"一步步变成"生产可用":先讲清声明式 API 与控制循环这一底层思维,再逐个拆解 Pod(最小调度单元 + 三探针)、Deployment(滚动更新与回滚)、Service(稳定访问入口与四种类型)、Ingress(七层路由),最后串成完整落地链路并给出排障三板斧。读完能独立把 Java 应用部署上 K8s 并定位常见故障。
一、先搞懂 K8s 的底层思维:声明式 API
在讲任何对象之前,必须先把"声明式"这三个字嚼烂。这是 K8s 和传统运维最大的思维分野。
传统运维是"命令式":你告诉系统每一步怎么走。
bash
# 命令式:一步步下指令
docker run -d -p 8080:8080 my-app:v1 # 先启动一个容器
docker run -d -p 8080:8080 my-app:v1 # 再加一个容器(手动扩容)
# 挂了怎么办?你自己写脚本监控、自己重启
K8s 是"声明式":你只告诉系统"我想要什么状态",剩下交给它。
bash
# 声明式:描述期望状态
kubectl apply -f deployment.yaml
# deployment.yaml 里写了:我要 3 个副本,镜像用 my-app:v1
# K8s 会持续"对账":实际跑 2 个?自动拉起 1 个。跑成 v2 了?自动回滚到 v1。
这两者的区别,一句话就能说透:命令式是你盯着每一步,声明式是你定一个目标,系统自己兜底。
K8s 的核心是一个控制循环(Reconciliation Loop),也叫"调谐循环"。它的工作方式极其简单,但威力巨大:
bash
循环 {
观察(Observe):实际状态是什么?
对比(Diff) :和期望状态差多少?
行动(Act) :做点什么,让实际状态逼近期望状态
}
你提交给 K8s 的任何 YAML,本质都是在声明一个"期望状态"。K8s 的各个控制器(Controller)会不停地把这个期望状态变成现实。理解了这一点,你就能理解 K8s 里几乎所有让你困惑的行为------比如为什么删掉的 Pod 会自动"复活",为什么改了副本数不用手动操作,为什么服务挂了能被拉起来。
划重点:
kubectl apply是声明式的,kubectl create是命令式的。生产环境一律用apply,因为它幂等,重复执行不会报错,天然适合写进 CI/CD 流水线。
二、Pod:最小的调度单元,也是容器的"家"
先纠正一个 90% 新手都会犯的认知错误:K8s 里最小的部署单位不是容器,是 Pod。
2.1 Pod 是什么
Pod 是一个或多个容器的集合,这些容器共享网络命名空间和存储卷,永远被调度到同一台机器上。你可以把 Pod 理解成"一组关系紧密、必须同生共死的容器"。
大多数情况下,一个 Pod 只有一个主容器(比如你的 Spring Boot 应用)。什么时候会放多个容器?典型场景是 Sidecar 模式------主容器旁边挂一个辅助容器。比如你的应用容器旁边挂一个"日志采集容器",或者挂一个"AI 推理的模型下载预热容器"。
下面是一个最简 Pod 定义:
XML
# pod.yaml ------ 一个最简的 Pod(注意:生产环境不直接裸用 Pod,后面会说为什么)
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
labels: # 标签:K8s 关联对象的核心手段
app: my-app
spec:
containers:
- name: app
image: my-registry/my-app:v1.0.0 # 版本号务必写死,禁止用 latest
ports:
- containerPort: 8080
resources: # 不写资源限制是生产事故的头号元凶
requests: # 调度时的"保底"资源
memory: "256Mi"
cpu: "250m"
limits: # 运行时的"天花板"资源
memory: "512Mi"
cpu: "500m"
2.2 Pod 的生命周期
Pod 从创建到销毁,会经历一个明确的状态机,这是你排障时必须能一眼看懂的东西:

四个核心状态,背下来:
| 状态 | 含义 | 你的关注点 |
|---|---|---|
| Pending | 已创建,但还没调度或容器没起来 | 看是资源不足,还是镜像拉不下来 |
| Running | 至少一个容器在运行 | 不等于"健康",得配合探针判断 |
| Succeeded | 所有容器正常退出 | 通常是 Job/定时任务 |
| Failed | 容器异常退出 | 查容器日志和退出码 |
这里有个大坑 :Running ≠ 服务可用。容器进程活着,不代表它已经能对外服务(比如 Spring Boot 可能还在启动、数据库还没连上)。这就是为什么 K8s 设计了"探针(Probe)"。
2.3 三种探针:健康的真正判官
XML
# deployment.yaml 的 Pod 模板片段
spec:
containers:
- name: app
image: my-registry/my-app:v1.0.0
startupProbe: # 启动探针:给慢启动的 JVM 留足时间
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30 # 最多等 30 * 10s = 300秒
periodSeconds: 10
livenessProbe: # 存活探针:进程"死没死"
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
readinessProbe: # 就绪探针:能不能"接客"
httpGet:
path: /actuator/health
port: 8080
periodSeconds: 5
三者的分工一定要分清,搞混了会引发生产事故:
-
startupProbe(启动探针):只判断"启动完成没有"。JVM 启动慢,尤其是配了 AI SDK 加载模型的 Spring Boot,可能几十秒才起来,这时 livenessProbe 会误判"死了"而反复重启容器。startupProbe 就是用来给慢启动兜底的。
-
livenessProbe(存活探针) :判断"进程是否已死"。失败就重启容器------注意是重启容器,不是重启 Pod。
-
readinessProbe(就绪探针) :判断"能不能接流量"。失败就从 Service 的负载均衡里摘掉,但不重启容器。
一句话记忆:startup 管"起没起来",liveness 管"死没死",readiness 管"能不能用"。
三、Deployment:为什么不能裸用 Pod
前面我留了个伏笔------"生产环境不要直接裸用 Pod"。原因很简单:
-
Pod 一旦挂了,不会自动恢复;
-
你想扩容到 3 个副本,得手动创建 3 次;
-
你想滚动升级版本,得手动一个个替换。
Deployment 就是来解决这三件事的。它内部管理一个 ReplicaSet(副本集),ReplicaSet 再管理一组 Pod,形成一条"控制链":
XML
Deployment(声明期望副本数、版本、更新策略)
└── ReplicaSet(保证"当前版本"的 Pod 数量达标)
└── Pod(真正干活的最小单元)
一个生产级 Deployment 长这样:
XML
# deployment.yaml ------ Spring Boot 应用的生产级部署(依赖:JDK 17 + Spring Boot 3.x)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-order-service
labels:
app: ai-order-service
spec:
replicas: 3 # 期望副本数,改这个数字就能扩缩容
strategy: # 滚动更新策略
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # 更新期间最多允许 0 个 Pod 不可用 → 零停机
maxSurge: 1 # 更新期间最多多出 1 个 Pod
selector: # 用来"找到"属于它的 Pod
matchLabels:
app: ai-order-service
template: # Pod 模板,定义了 Pod 长什么样
metadata:
labels:
app: ai-order-service
spec:
containers:
- name: app
image: my-registry/ai-order-service:v2.3.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
resources:
requests: { memory: "512Mi", cpu: "500m" }
limits: { memory: "1Gi", cpu: "1000m" }
readinessProbe:
httpGet: { path: /actuator/health, port: 8080 }
periodSeconds: 5
Deployment 的三个杀手级能力,配合 kubectl 命令就是:
bash
# 1. 扩缩容:一条命令,K8s 自己搞定
kubectl scale deployment ai-order-service --replicas=5
# 2. 滚动更新:换镜像,K8s 逐个替换旧 Pod(零停机)
kubectl set image deployment/ai-order-service app=my-registry/ai-order-service:v2.4.0
kubectl rollout status deployment/ai-order-service # 看更新进度
# 3. 一键回滚:版本出问题,退回到上一个版本
kubectl rollout undo deployment/ai-order-service
maxUnavailable: 0 配合 maxSurge: 1 这个组合,能保证更新过程中始终有 3 个 Pod 在对外服务------这就是"滚动更新零停机"的原理,值得你记住。
四、Service:Pod 会死,但服务地址不能变
现在你有了 3 个 Pod,但问题来了:Pod 的 IP 是随机分配且会变的。Pod 一旦重启,IP 就换了,那你的前端、你的其他微服务,怎么稳定地找到它?
Service 解决的就是"稳定访问入口"的问题。
Service 会给一组 Pod 分配一个稳定的虚拟 IP(ClusterIP)和 DNS 名称 ,然后通过 selector 找到它要代理的 Pod,把流量转发过去。Pod 死了重启,IP 变了,但只要 labels 没变,Service 就永远能找到它。
Service 有四种类型,这是面试和实战的双料高频考点:
| 类型 | 访问范围 | 典型场景 | 一句话 |
|---|---|---|---|
| ClusterIP | 集群内部 | 微服务之间互调 | 默认类型,内部专用 |
| NodePort | 集群外部(节点 IP:端口) | 临时暴露、调试 | 在 ClusterIP 基础上,在每个节点开个端口 |
| LoadBalancer | 集群外部(云厂商 LB) | 生产对外服务 | 在 NodePort 基础上,自动创建云负载均衡器 |
| ExternalName | 集群外部(DNS 别名) | 访问集群外的服务 | 就是个 CNAME,不代理 Pod |
四种类型是层层递进的关系,看这个图就明白了:
bash
ClusterIP ──→ 仅集群内部可达
│ 叠加"在每个节点开一个端口"
▼
NodePort ──→ 外部可通过 <节点IP>:<端口> 访问
│ 叠加"云厂商自动创建负载均衡器"
▼
LoadBalancer ──→ 外部通过云 LB 的固定 IP 访问(生产首选)
ExternalName ──→ 独立的,仅做 DNS 别名映射
下面是一个 ClusterIP 型 Service,配合上面的 Deployment 使用:
XML
# service.yaml ------ 为 ai-order-service 提供一个稳定的集群内部地址
apiVersion: v1
kind: Service
metadata:
name: ai-order-service
spec:
type: ClusterIP # 默认类型,集群内部访问
selector: # 通过 label 匹配到对应的 Pod
app: ai-order-service
ports:
- name: http
port: 8080 # Service 对外暴露的端口
targetPort: 8080 # Pod 上实际监听的端口
protocol: TCP
部署完成后,集群内其他服务可以直接用 http://ai-order-service:8080 访问它,不用关心背后是 3 个还是 30 个 Pod,也不用关心它们的 IP 是什么。这就是 Service 的价值:把"访问入口"和"具体实例"彻底解耦。
实战提醒:
selector是 Service 找到 Pod 的唯一桥梁,selector里的 label 必须和 Pod 模板里的 label 完全一致,错一个字母流量就断,还不会报错。
五、Ingress:七层路由,一个入口管所有服务
Service 解决了"稳定访问",但还差最后一公里。
假设你有三个服务:订单服务、用户服务、还有前几周我们做的 AI 智能客服服务 。如果每个服务都用 NodePort 暴露,那你要记三个端口号,前端还要处理一堆 IP。更重要的是,NodePort 和 LoadBalancer 都是四层(TCP)的,干不了"按域名/路径路由"这种七层的事。
Ingress 就是来解决七层路由的 。它让你用一个统一入口,根据域名和路径,把请求分发到不同的 Service:
XML
用户请求 https://api.yourdomain.com
│
▼
Ingress Controller(如 nginx-ingress)
│ 按规则匹配
├── /order/** ──→ ai-order-service (Service)
├── /user/** ──→ user-service (Service)
└── /ai/** ──→ ai-chat-service (Service)
一个 Ingress 规则长这样:
XML
# ingress.yaml ------ 按路径路由到不同服务
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # 重写规则,常见坑
spec:
ingressClassName: nginx # 必须指定 Ingress Controller
rules:
- host: api.yourdomain.com
http:
paths:
- path: /order
pathType: Prefix
backend:
service:
name: ai-order-service
port: { number: 8080 }
- path: /ai
pathType: Prefix
backend:
service:
name: ai-chat-service
port: { number: 8080 }
一个必须记住的坑 :Ingress 只是一纸"路由规则",它本身不干活。真正转发流量的是 Ingress Controller(比如 nginx-ingress、Traefik),这个是需要你额外部署的组件。很多新手写完 Ingress 发现不生效,就是忘了装 Controller。
六、串起来:一个 Spring Boot 应用的完整落地
把上面四件套串起来,一个 Java 应用从裸进程到生产可用的完整路径就是:
部署顺序也很清晰,照着敲就行:
到这里,你的 Java 应用就有了:自动恢复(Deployment)+ 稳定访问(Service)+ 统一入口(Ingress)+ 健康自检(Probe)。这就是云原生时代一个后端工程师的"及格线"配置。
实战建议:
1. 先懂"声明式",再背命令。 K8s 的命令有几百条,背不完也没必要。但只要你把"声明式 + 控制循环"这个底层思维吃透,所有的对象、控制器、行为都能自己推导出来。看任何 K8s 文档,先问一句"这是在声明什么期望状态"。
2. 生产环境的三条铁律:写死版本号、配好资源限制、配好就绪探针。 我见过太多线上事故,根源就是这三条没做好:镜像用了 latest 导致不可复现;没写 limits 导致一个内存泄漏的 Pod 拖垮整台节点;没配 readinessProbe 导致流量打进还没就绪的 Pod 全部报错。这三条不做到,别谈"上生产"。
3. 排障三板斧:describe 看事件,logs 看日志,get events 看历史。 Pod 起不来,别瞎猜。按顺序来:
bash
kubectl describe pod <pod名> # 看 Events,会告诉你"为什么":镜像拉不下?资源不足?探针失败?
kubectl logs <pod名> -f # 看容器日志,找应用层面的报错
kubectl get events --sort-by=.metadata.creationTimestamp | tail -20 # 看集群最近发生了什么
90% 的问题,这三条命令走一遍就能定位。
结尾
K8s 这玩意,概念多、术语绕,但它骨子里就一句话:你声明一个目标,它负责把目标变成现实。 你作为后端工程师要做的,不是记住所有命令,而是把 Pod、Deployment、Service、Ingress 这四个对象的职责边界刻进脑子------Pod 是干活的,Deployment 是管人的,Service 是开门的,Ingress 是引路的。
下一篇,我们把上一周搭的 Spring Boot 微服务真正搬进 K8s,讲ConfigMap + Secret 配置管理,以及基于 QPS 的弹性伸缩(HPA)------让你的服务不仅能跑,还能"自己扩容"。咱们不见不散。