Chapter 03

Pod:最小调度单元

Pod 是 K8s 中最小的可部署单元,理解 Pod 的设计理念、生命周期、健康探针和资源管理是掌握 K8s 的基础。

什么是 Pod

Pod 是 K8s 中最小的调度和管理单元。一个 Pod 是一组(通常是一个,也可以是多个)共享网络和存储的容器集合。同一个 Pod 内的容器:

为什么不直接调度容器?

Pod 这个抽象层允许将紧密耦合的容器(如应用 + 日志收集 agent)作为一个整体管理,同时保持容器的隔离性。Pod 中有一个隐藏的 Pause 容器(infra 容器),负责持有 Pod 的网络命名空间,其他容器加入这个命名空间。

Pod 完整 YAML 示例

apiVersion: v1
kind: Pod
metadata:
  name: web-pod
  namespace: default
  labels:
    app: web
    version: v1
spec:
  containers:
  - name: web                        # 容器名称
    image: nginx:1.25
    imagePullPolicy: IfNotPresent   # Always/Never/IfNotPresent
    ports:
    - containerPort: 80
      protocol: TCP
    resources:                       # 资源请求和限制
      requests:                      # 调度依据:保证至少得到这么多
        cpu: 100m                    # 100 millicores = 0.1 CPU
        memory: 128Mi               # 128 Mebibytes
      limits:                        # 硬上限:超出则被限流/OOMKilled
        cpu: 500m
        memory: 256Mi
    env:                             # 环境变量
    - name: APP_ENV
      value: production
    livenessProbe:                   # 存活探针:失败则重启容器
      httpGet:
        path: /healthz
        port: 80
      initialDelaySeconds: 15       # 容器启动后等待 15s 再开始探测
      periodSeconds: 10            # 每 10s 探测一次
      failureThreshold: 3          # 连续 3 次失败才重启
    readinessProbe:                  # 就绪探针:失败则从 Service 摘流
      httpGet:
        path: /ready
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
    volumeMounts:                    # 挂载 Volume
    - name: config-vol
      mountPath: /etc/nginx/conf.d
  volumes:                           # 声明 Pod 级别的 Volume
  - name: config-vol
    configMapRef:
      name: nginx-config
  restartPolicy: Always             # Always/OnFailure/Never
  terminationGracePeriodSeconds: 30 # 优雅停止等待时间(秒)

Pod 生命周期

Pod 生命周期状态机

   ┌──────────────────────────────────────────────────────┐
     创建 Pod                                             
   └──────────────────┬───────────────────────────────────┘
                      
   Pending    ←── 等待调度 / 拉取镜像 / 等待 PVC 绑定
                      
   Running    ←── 至少一个容器在运行
           ├──── 所有容器成功退出 ──► Succeeded
           └──── 容器失败且不再重启 ──► Failed
   Unknown    ←── Node 失联,状态未知
   Terminating ←── 收到删除信号,等待优雅退出
Pending
Pod 已被 API Server 接受,但尚未绑定到 Node。可能原因:等待调度、镜像拉取中、没有满足条件的节点。
Running
Pod 已绑定到节点,至少有一个容器正在运行(或正在启动/重启)。
Succeeded
Pod 中所有容器都已成功终止(退出码 0),且不会再重启。常见于 Job 类型。
Failed
Pod 中所有容器已终止,至少有一个容器以非 0 退出码失败。
CrashLoopBackOff
容器持续崩溃,K8s 在等待越来越长的时间后重启它。并非 Pod Phase,而是容器状态描述。这通常意味着应用本身有问题(如配置错误、内存不足)。
OOMKilled
容器超出内存限制(Limit),被内核 OOM Killer 强制终止。需要增加 memory limit 或优化内存使用。

多容器 Pod 设计模式

虽然大多数 Pod 只包含一个主容器,但 K8s 定义了三种多容器协作模式:

Sidecar(边车)模式

在主容器旁运行辅助容器,增强或扩展主容器的功能,两者共享相同的存储和网络。

spec:
  containers:
  - name: app                   # 主容器:写日志到共享目录
    image: my-app:v1
    volumeMounts:
    - name: log-vol
      mountPath: /var/log/app
  - name: log-shipper           # Sidecar:读日志并发送到 Elasticsearch
    image: fluent/fluent-bit:3.0
    volumeMounts:
    - name: log-vol
      mountPath: /var/log/app
      readOnly: true
  volumes:
  - name: log-vol
    emptyDir: {}               # Pod 生命周期内的临时共享存储

Init 容器

在主容器启动前运行的初始化容器,串行执行,必须全部成功才会启动主容器。

spec:
  initContainers:              # Init 容器列表,按顺序执行
  - name: wait-for-db          # 等待数据库就绪
    image: busybox:1.36
    command:
    - sh
    - -c
    - "until nc -z mysql-svc 3306; do echo waiting...; sleep 2; done"
  - name: db-migrate            # 执行数据库迁移
    image: my-app:v1
    command: ["python", "manage.py", "migrate"]
  containers:
  - name: app                   # Init 容器全部成功后才启动
    image: my-app:v1

资源请求与限制

requests
资源请求。Scheduler 根据 requests 进行调度决策,保证 Pod 在节点上至少能得到声明的资源量。requests 决定了 Pod 被调度到哪个 Node。
limits
资源限制。CPU limit 通过 cgroup 限流,超出后容器被节流(变慢但不被杀)。Memory limit 超出后容器被 OOMKilled(立即终止)。
100m CPU
100 millicores,即 0.1 个 CPU 核心。1000m = 1 核。也可以直接写 0.1。
Mi vs MB
Mi = Mebibyte(2^20 = 1,048,576 字节),MB = Megabyte(10^6 = 1,000,000 字节)。K8s 中通常用 Mi/Gi。128Mi ≈ 134MB。

健康探针(Health Probes)

K8s 提供三种探针,通过不同机制告知 K8s 容器的健康状态:

Liveness Probe(存活探针)

探测容器是否仍在运行。失败时 K8s 重启容器。用于检测死锁等无法自行恢复的状态。

Readiness Probe(就绪探针)

探测容器是否准备好接受流量。失败时将 Pod 从 Service 的 Endpoints 中摘除,不再转发流量,但不重启容器

containers:
- name: app
  image: my-app:v1

  startupProbe:                    # 启动探针:启动慢的应用防止被 Liveness 误杀
    httpGet:
      path: /healthz
      port: 8080
    failureThreshold: 30           # 最多等 30 * 10s = 300s 启动
    periodSeconds: 10

  livenessProbe:
    httpGet:
      path: /healthz
      port: 8080
    initialDelaySeconds: 0         # startupProbe 通过后立即开始
    periodSeconds: 10
    timeoutSeconds: 5             # 探测超时时间
    failureThreshold: 3

  readinessProbe:
    tcpSocket:                     # TCP 端口探测(另一种探针类型)
      port: 8080
    initialDelaySeconds: 5
    periodSeconds: 5
httpGet
向容器指定路径发送 HTTP GET 请求,2xx/3xx 视为成功。最常用的探针类型。
tcpSocket
尝试与容器指定端口建立 TCP 连接,连接成功则视为探测成功。适用于不支持 HTTP 的服务(如数据库)。
exec
在容器内执行命令,退出码为 0 则成功。适用于需要自定义检查逻辑的场景。
grpc
K8s 1.24+ 正式支持 gRPC 探针,调用 gRPC Health Checking Protocol。

Pod QoS 服务质量等级

K8s 根据容器的 requests/limits 设置,自动将 Pod 分为三个 QoS 等级,决定节点内存紧张时 Pod 被驱逐的优先级:

QoS 等级条件驱逐优先级
BestEffort 没有设置任何 requests 和 limits 最先被驱逐
Burstable 至少有一个容器设置了 requests,但 requests ≠ limits 其次被驱逐
Guaranteed 所有容器都设置了 requests == limits(CPU 和内存) 最后被驱逐
生产环境最佳实践

生产环境关键服务应设置 requests == limits,达到 Guaranteed QoS,避免在节点资源紧张时被意外驱逐。同时配合 LimitRange 为 Namespace 设置默认资源限制,防止忘记设置的 Pod 成为 BestEffort 等级。

常见错误:OOMKilled

如果你的容器频繁被 OOMKilled,不要盲目提高 memory limit。先用 kubectl top pods 观察实际内存用量,分析是内存泄漏还是正常需求。同时,Java 应用要注意设置 JVM 堆大小(-Xmx)与 K8s memory limit 的对应关系,不然 JVM 会按节点总内存分配堆,很快超出 limit 被 Kill。