Kubernetes 基础:Pod、Deployment 和 Service
1. 概述
Kubernetes 有数十种资源类型,但日常工作中 80% 的工作都围绕三个概念:Pod、Deployment、Service。本文帮助你理解每个资源的本质、它们如何相互关联,以及如何编写能在集群上真正运行的 YAML manifest。
读完后,你就能自己将一个 Spring Boot 应用部署到 Kubernetes、将其扩展到多个副本,并通过 Service 对外暴露 —— 所有这些只需要 kubectl 和几个 YAML 文件。
注意: 本文假设你已有一个集群(Minikube、Kind 或云平台)并且
kubectl已指向正确的 context。
2. Pod —— 最小的部署单位
2.1. 什么是 Pod
Pod 是一组共享网络命名空间(相同 IP、相同端口空间)和存储卷的一个或多个容器。实际上,大多数 Pod 只包含一个应用容器。
apiVersion: v1
kind: Pod
metadata:
name: order-service-pod
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
解释:
metadata.labels标签app: order-service—— Kubernetes 通过这个标签来“识别”Pod,Service 和 Deployment 使用这个标签来选择相关对象。resources.requests是调度器将 Pod 放置到节点所需的最少资源;limits是上限,超过会被限制(CPU)或被杀死(内存 —— OOMKilled)。containerPort: 8080只是信息声明,不会自动向外暴露端口。
应用并检查:
kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME READY STATUS RESTARTS AGE
order-service-pod 1/1 Running 0 5s
2.2. 为什么不应该直接创建 Pod
像上面这样“裸”创建的 Pod 有一个大问题:如果容纳 Pod 的节点死亡或 Pod 崩溃,没有人会自动重建它。Pod 没有自我修复或扩缩容机制。
注意: 在生产环境中,几乎不会直接创建 Pod。总是使用 Deployment(或 StatefulSet、Job)来管理 Pod。
3. Deployment —— 管理 Pod 的生命周期
3.1. 声明 Deployment
Deployment 通过 ReplicaSet 创建和管理一组 Pod,确保始终有正确数量的副本在运行,并支持无停机滚动更新。
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
解释:
replicas: 3要求 Deployment 始终维持 3 个 Pod 运行 —— 如果一个 Pod 死亡,Deployment 会自动创建新 Pod 替代。selector.matchLabels必须与template.metadata.labels匹配。这是 Deployment 与它管理的 Pod 之间的强制链接。readinessProbe决定 Pod 是否接收流量(由 Service 使用);livenessProbe决定 Pod 是否需要重启。混淆这两个 probe 是学习 Kubernetes 时的常见错误。
应用并观察:
kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME READY UP-TO-DATE AVAILABLE AGE
order-service 3/3 3 3 12s
NAME READY STATUS RESTARTS AGE
order-service-7d4f9c8b6d-2kx9p 1/1 Running 0 12s
order-service-7d4f9c8b6d-7h2mn 1/1 Running 0 12s
order-service-7d4f9c8b6d-xq1lw 1/1 Running 0 12s
3.2. 扩缩容和滚动更新
扩缩副本数无需修改 YAML:
kubectl scale deployment order-service --replicas=5
更新镜像时进行滚动更新 —— Deployment 根据滚动策略自动用新 Pod 替换旧 Pod,无停机时间:
kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out
如果新版本有 bug,立即回滚到上一版本:
kubectl rollout undo deployment/order-service
3.3. 配置滚动更新策略
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
maxUnavailable: 1 允许更新期间最多有 1 个 Pod“不可用”;maxSurge: 1 允许创建最多超过声明副本数 1 个的新 Pod。对于 replicas: 3,集群在转换过程中会运行 2-4 个 Pod,但不会低于 2 个。
4. Service —— 稳定地暴露 Pod
4.1. 为什么需要 Service
Pod 有自己的 IP,但这个 IP 会在每次重建 Pod 时改变(崩溃、滚动更新、扩缩容)。Service 提供一个稳定的地址(ClusterIP + DNS 名称)位于 Pod 组前面,根据标签选择器自动进行负载均衡 —— 无论底层 Pod 如何变化都不受影响。
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
解释:
selector.app: order-service与 Pod 上的标签匹配(由 Deployment 通过template.metadata.labels设置)—— Service 通过称为 Endpoints 的中间对象自动发现和跟踪这些 Pod。port: 80是 Service 在集群内监听的端口;targetPort: 8080是容器实际暴露的端口。这两个数字不必相同。type: ClusterIP(默认值)只能从集群内访问 —— 适合微服务之间的内部通信。
检查 Service 和 Endpoints:
kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
order-service ClusterIP 10.96.142.201 <none> 80/TCP 4s
NAME ENDPOINTS AGE
order-service 10.244.1.5:8080,10.244.1.6:8080,+1 more 4s
4.2. Service 类型
| 类型 | 访问范围 | 使用场景 |
|---|---|---|
ClusterIP |
仅限集群内 | 微服务间的内部通信 |
NodePort |
在所有节点上开放固定端口(30000-32767) | 快速测试、开发/本地环境 |
LoadBalancer |
通过云提供商分配公网 IP | 在云上向互联网暴露服务 |
在 Minikube 上快速测试的暴露示例:
apiVersion: v1
kind: Service
metadata:
name: order-service-external
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
nodePort: 30080
type: NodePort
minikube service order-service-external --url
注意: 在生产环境中使用
NodePort是反模式 —— 端口范围受限,没有 TLS 终止或像 Ingress 那样的健康检查路由。对于云上的生产环境,优先使用LoadBalancer或 Ingress Controller。
5. 在真实应用中结合三者
完整的流程:客户端调用 Service → Service 对 Deployment 管理的 Pod 进行负载均衡。
Client
│
▼
Service (order-service, ClusterIP 10.96.142.201:80)
│ (label selector: app=order-service)
▼
┌─────────────┬─────────────┬─────────────┐
│ Pod #1 │ Pod #2 │ Pod #3 │
│ :8080 │ :8080 │ :8080 │
└─────────────┴─────────────┴─────────────┘
▲ 由...管理
Deployment (order-service, replicas: 3)
为了方便一次部署,你可以将 Deployment 和 Service 合并到一个文件中,用 --- 分隔:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
kubectl apply -f order-service-all.yaml
6. 部署前为 readiness 端点编写测试
在 Kubernetes probe 依赖 /actuator/health/readiness 之前,你应该确保这个端点在 Spring Boot 代码中返回正确的状态。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void readinessProbe_shouldReturnUp() {
ResponseEntity<String> response =
restTemplate.getForEntity("/actuator/health/readiness", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("\"status\":\"UP\"");
}
}
这个测试调用 TestRestTemplate 访问真实的 readiness 端点,确认 HTTP 200 和响应体包含 UP。这正是 Deployment 中的 readinessProbe 定期调用的端点 —— 如果测试在这里失败,Pod 在集群上也永远不会变成“Ready”。
7. 常见错误
- 选择器与标签不匹配:由于
selector和labels写错或不一致,Service 或 Deployment 找不到任何 Pod(Endpoints 为空)。始终使用kubectl get endpoints <svc-name>来确认。 - 缺少 readinessProbe:Service 仍会将流量路由到应用未启动完毕的 Pod,导致部署后立即出现 502/503 错误。
- 缺少 resource requests/limits:Pod 可能被调度器错误地放置在节点上,或在流量增加时意外被 OOMKilled。
- 在生产环境中使用裸 Pod:没有自我修复能力,Pod 丢失就永久丢失,必须手动重建。
- 镜像标签
latest:滚动更新无法检测变化,Kubernetes 认为镜像未改变并且不会重新拉取。始终使用具体的版本标签。
8. 结论
Pod 是最小的容器运行单位,Deployment 管理一组 Pod 的生命周期和扩缩容,Service 则提供稳定的地址来访问它们,尽管底层 Pod 不断变化。掌握这三个组件是你进一步学习 Ingress、ConfigMap/Secret 或 Helm chart 的基础。下一步可以尝试:部署一个真实的 Spring Boot 应用,观察滚动更新过程,并尝试手动杀死一个 Pod 来看 Deployment 如何自动恢复。