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 如何自动恢复。

kubernetesdevopscontainerorchestration