找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5235

积分

1

好友

717

主题
发表于 1 小时前 | 查看: 2| 回复: 0

动态箭头指示图

一、概述

1.1 背景介绍

在微服务架构日益普及的今天,Kubernetes 已成为容器编排的事实标准。然而,很多团队在使用 K8s 时常常遇到这样的问题:

  • 服务更新时出现短暂的请求失败
  • Pod 被杀死时正在处理的请求直接中断
  • 滚动更新时偶发的 502/504 错误
  • 服务重启后需要较长时间才能恢复正常流量

这些问题的根源往往在于对 Pod 生命周期和优雅停机机制理解不够深入。本文将从原理到实践,全方位剖析 Kubernetes Pod 的生命周期管理,帮助你构建真正“优雅”的云原生应用。

1.2 技术特点

  • 声明式配置:通过 YAML 声明期望状态,K8s 自动完成调谐
  • 健康检查机制:存活探针、就绪探针、启动探针三位一体
  • 优雅终止:preStop Hook + SIGTERM 信号实现平滑关闭
  • 生命周期钩子:PostStart 和 PreStop 提供扩展点

1.3 适用场景

  • 场景一:需要零停机发布的核心业务服务
  • 场景二:有状态应用的优雅关闭(如消息队列消费者)
  • 场景三:需要精细控制启动顺序的复杂应用
  • 场景四:长连接服务(WebSocket、gRPC Stream)的平滑迁移

1.4 环境要求

组件 版本要求 说明
Kubernetes 1.25+ 本文基于 K8s 1.28 版本
kubectl 与集群版本匹配 用于演示和操作
容器运行时 containerd 1.6+ 或 CRI-O 1.25+

二、Pod 生命周期全景图

2.1 Pod 阶段(Phase)详解

Pod 的生命周期通过 status.phase 字段表示,共有以下五种状态:

                     ┌─────────────────────────────────────────────────┐
                     │                 Pod 生命周期                     │
                     └─────────────────────────────────────────────────┘
                                        │
                                        ▼
                                    ┌──────────┐
                                    │ Pending  │
                                    └────┬─────┘
                                         │ 调度成功 + 镜像拉取完成
                                         ▼
                     ┌──────────────────────────────────────────────┐
                     │                  Running                     │
                     │  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
                     │  │Container1│  │Container2│  │Container3│   │
                     │  └──────────┘  └──────────┘  └──────────┘   │
                     └───────────────────┬──────────────────────────┘
                                         │
                     ┌───────────────────┼───────────────────┐
                     │                   │                   │
                     ▼                   ▼                   ▼
                ┌──────────┐        ┌──────────┐        ┌──────────┐
                │Succeeded │        │  Failed  │        │  Unknown │
                └──────────┘        └──────────┘        └──────────┘

2.1.1 Pending

# Pod 处于 Pending 的常见原因
status:
  phase: Pending
  conditions:
  - type: PodScheduled
    status: "False"
    reason: Unschedulable
    message: "0/3 nodes are available: 3 Insufficient memory."

常见原因

  • 等待调度(资源不足、节点选择器不匹配)
  • 镜像拉取中或拉取失败
  • PVC 等待绑定

排查命令

# 查看 Pod 事件
kubectl describe pod <pod-name> -n <namespace>

# 查看调度失败原因
kubectl get events --field-selector reason=FailedScheduling

2.1.2 Running

Pod 已绑定到节点,所有容器都已创建,至少有一个容器正在运行或正在启动/重启。

# 查看 Running 状态的 Pod
kubectl get pods --field-selector=status.phase=Running

2.1.3 Succeeded

所有容器都已成功终止(退出码为 0),且不会被重启。通常用于 Job 类型的 Pod。

2.1.4 Failed

所有容器都已终止,且至少有一个容器是因为失败退出的(非 0 退出码)。

2.1.5 Unknown

无法获取 Pod 状态,通常是因为与 Pod 所在节点通信失败。

2.2 容器状态(Container State)

每个容器有独立的状态,更加细粒度:

status:
  containerStatuses:
  - name: app
    state:
      running:
        startedAt: “2024-01-15T10:00:00Z”
    lastState:
      terminated:
        exitCode: 137
        reason: OOMKilled
        startedAt: “2024-01-15T09:00:00Z”
        finishedAt: “2024-01-15T09:55:00Z”
    ready: true
    restartCount: 1

2.2.1 Waiting

容器等待启动,可能原因包括:

reason 说明
ContainerCreating 容器正在创建中
ImagePullBackOff 镜像拉取失败后的退避
CrashLoopBackOff 容器反复崩溃后的退避
CreateContainerError 创建容器失败

2.2.2 Running

容器正在执行中,startedAt 字段表示启动时间。

2.2.3 Terminated

容器已终止,关键字段:

  • exitCode:退出码(0 表示正常)
  • reason:终止原因(Completed、Error、OOMKilled、ContainerCannotRun)
  • signal:导致终止的信号编号

2.3 Pod 条件(Conditions)

Conditions 提供更详细的状态信息:

status:
  conditions:
  - type: PodScheduled
    status: “True”
  - type: Initialized
    status: “True”
  - type: ContainersReady
    status: “True”
  - type: Ready
    status: “True”
条件类型 含义
PodScheduled Pod 已被调度到某个节点
Initialized 所有 Init 容器都已成功完成
ContainersReady Pod 中所有容器都已就绪
Ready Pod 可以接收流量(被加入 Service Endpoints)

三、探针机制深度解析

3.1 探针类型概览

                    ┌────────────────────────────────────────────┐
                    │              健康检查探针                   │
                    └────────────────────────────────────────────┘
                                    │
            ┌───────────────────────┼───────────────────────────┐
            │                       │                           │
            ▼                       ▼                           ▼
    ┌───────────────┐           ┌───────────────┐           ┌───────────────┐
    │ startupProbe  │           │ livenessProbe │           │readinessProbe │
    │   启动探针     │           │   存活探针     │           │   就绪探针     │
    └───────┬───────┘           └───────┬───────┘           └───────┬───────┘
            │                           │                           │
            │                           │                           │
            ▼                           ▼                           ▼
    ┌───────────────┐           ┌───────────────┐           ┌───────────────┐
    │ 成功后启用其他 │           │ 失败则重启容器 │           │ 失败则从Service│
    │ 探针          │           │               │           │ 中摘除         │
    └───────────────┘           └───────────────┘           └───────────────┘

3.2 启动探针(Startup Probe)

作用:保护慢启动应用,避免在启动完成前被存活探针杀死。

apiVersion: v1
kind: Pod
metadata:
  name: slow-start-app
spec:
  containers:
  - name: app
    image: slow-start-app:v1
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 0
      periodSeconds: 10
      timeoutSeconds: 3
      successThreshold: 1
      failureThreshold: 30 # 30 * 10s = 300s 最长启动时间

关键参数

参数 说明 建议值
initialDelaySeconds 首次探测前等待时间 0(启动探针不需要)
periodSeconds 探测间隔 5-10
timeoutSeconds 单次探测超时 1-5
failureThreshold 连续失败次数后认定为失败 根据最长启动时间计算
successThreshold 连续成功次数后认定为成功 1

最长启动时间计算

最长启动时间 = initialDelaySeconds + periodSeconds × failureThreshold

3.3 存活探针(Liveness Probe)

作用:检测容器是否存活,失败则重启容器。

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
    httpHeaders:
    - name: X-Custom-Header
      value: Liveness
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

常见误区

# 错误示范:检查外部依赖
livenessProbe:
  httpGet:
    path: /health/database # 检查数据库连接
    port: 8080
# 问题:数据库短暂不可用会导致所有 Pod 被重启,加剧故障

最佳实践

# 正确示范:只检查应用进程是否存活
livenessProbe:
  httpGet:
    path: /healthz # 只检查应用本身
    port: 8080
# 或使用命令检查进程
  exec:
    command:
    - cat
    - /tmp/healthy

3.4 就绪探针(Readiness Probe)

作用:检测容器是否准备好接收流量,失败则从 Service Endpoints 中摘除。

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 3
  successThreshold: 1

就绪探针可以检查外部依赖

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
# /ready 端点实现示例(Go)
func readyHandler(w http.ResponseWriter, r *http.Request) {
  // 检查数据库连接
  if err := db.Ping(); err != nil {
    w.WriteHeader(http.StatusServiceUnavailable)
    return
  }
  // 检查缓存连接
  if err := redis.Ping(); err != nil {
    w.WriteHeader(http.StatusServiceUnavailable)
    return
  }
  w.WriteHeader(http.StatusOK)
}

3.5 探针执行方式

3.5.1 HTTP 探针

httpGet:
  path: /healthz
  port: 8080
  scheme: HTTPS
  httpHeaders:
  - name: Authorization
    value: Bearer xxx

状态码判断

  • 200-399:成功
  • 其他:失败

3.5.2 TCP 探针

tcpSocket:
  port: 3306

适用场景:数据库、缓存等不支持 HTTP 的服务

3.5.3 命令探针

exec:
  command:
  - sh
  - -c
  - “pg_isready -h localhost -p 5432”

退出码判断

  • 0:成功
  • 非 0:失败

3.5.4 gRPC 探针(K8s 1.24+ 稳定)

grpc:
  port: 50051
  service: grpc.health.v1.Health # 可选,默认为空

要求:应用需实现 gRPC Health Checking Protocol

// gRPC 健康检查实现示例
import “google.golang.org/grpc/health/grpc_health_v1”

type healthServer struct{}

func (s *healthServer) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
  return &grpc_health_v1.HealthCheckResponse{
    Status: grpc_health_v1.HealthCheckResponse_SERVING,
  }, nil
}

3.6 三种探针的配合使用

apiVersion: v1
kind: Pod
metadata:
  name: app-with-probes
spec:
  containers:
  - name: app
    image: app:v1
    ports:
    - containerPort: 8080
    # 启动探针:保护慢启动
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      periodSeconds: 5
      failureThreshold: 60 # 最长 5 分钟启动
    # 存活探针:检测死锁等问题
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      periodSeconds: 10
      failureThreshold: 3
    # 就绪探针:控制流量
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      periodSeconds: 5
      failureThreshold: 3

时序关系

启动阶段:
[startupProbe] ─────检测中─────> 成功
                            │
                            ▼
运行阶段:              同时启用
           ┌───────────┴───────────┐
           │                       │
   [livenessProbe]          [readinessProbe]
           │                       │
       失败→重启              失败→摘除流量

四、优雅终止机制详解

4.1 Pod 终止流程

当 Pod 被删除时,会经历以下步骤:

                                 kubectl delete pod
                                        │
                                        ▼
                       ┌───────────────────────────────┐
                       │ 1. API Server 标记删除         │
                       │    (设置 deletionTimestamp)   │
                       └───────────────┬───────────────┘
                                        │
                   ┌───────────────────┼───────────────────┐
                   │                   │                   │
                   ▼                   ▼                   ▼
           ┌───────────────┐   ┌───────────────┐   ┌───────────────┐
           │ 2. Endpoints  │   │ 3. kubelet    │   │               │
           │   Controller  │   │   执行        │   │               │
           │   摘除 Pod    │   │   preStop     │   │               │
           └───────────────┘   └───────┬───────┘   └───────────────┘
                                        │
                                        ▼
                       ┌───────────────────────────────┐
                       │ 4. 发送 SIGTERM 信号           │
                       │    (terminationGracePeriod    │
                       │     倒计时开始)               │
                       └───────────────┬───────────────┘
                                        │
                              等待进程退出或超时
                                        │
                                        ▼
                       ┌───────────────────────────────┐
                       │ 5. 超时则发送 SIGKILL          │
                       │    (强制终止)                 │
                       └───────────────────────────────┘

4.2 terminationGracePeriodSeconds

apiVersion: v1
kind: Pod
metadata:
  name: graceful-app
spec:
  terminationGracePeriodSeconds: 60 # 默认 30 秒
  containers:
  - name: app
    image: app:v1

时间分配建议

terminationGracePeriodSeconds = preStop执行时间 + 业务处理收尾时间 + 缓冲时间

例如:
- preStop sleep: 5 秒
- 业务收尾: 最长 45 秒
- 缓冲: 10 秒
- 总计: 60 秒

4.3 preStop Hook 详解

4.3.1 为什么需要 preStop

问题场景:Pod 删除时,Endpoints 摘除和容器终止是并行执行的。

                        删除 Pod
                          │
            ┌──────────────┴──────────────┐
            │                             │
            ▼                             ▼
    ┌───────────────┐             ┌───────────────┐
    │ Endpoints     │             │ kubelet       │
    │ 摘除 Pod      │             │ 终止容器      │
    │ (通知各节点)  │             │ (发送SIGTERM) │
    └───────┬───────┘             └───────┬───────┘
            │                             │
            │ 约 100ms-几秒               │ 立即
            │ (取决于集群规模)            │
            ▼                             ▼
    Ingress/LB 更新                应用开始关闭

竞态条件

  1. kubelet 可能在 Endpoints 更新到所有节点之前就杀死 Pod
  2. Ingress 或负载均衡器仍在向即将终止的 Pod 发送流量
  3. 导致请求失败

4.3.2 preStop 配置

apiVersion: v1
kind: Pod
metadata:
  name: app-with-prestop
spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: app
    image: app:v1
    lifecycle:
      preStop:
        exec:
          command:
          - /bin/sh
          - -c
          - |
            # 等待 Endpoints 更新传播到所有节点
            sleep 5
            # 通知应用开始优雅关闭
            curl -X POST http://localhost:8080/admin/shutdown

或使用 HTTP 方式

lifecycle:
  preStop:
    httpGet:
      path: /prestop
      port: 8080

4.3.3 preStop 最佳实践

# 完整的优雅终止配置示例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: app
        image: web-app:v1
        ports:
        - containerPort: 8080
        lifecycle:
          preStop:
            exec:
              command: [“sh”, “-c”, “sleep 5”]
        # 就绪探针:确保流量控制
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          periodSeconds: 2
          failureThreshold: 1 # 快速响应

4.4 应用层的优雅关闭

4.4.1 正确处理 SIGTERM

Go 示例

package main

import (
    “context”
    “log”
    “net/http”
    “os”
    “os/signal”
    “syscall”
    “time”
)

func main() {
    server := &http.Server{
        Addr: “:8080”,
    }

    // 启动服务
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf(“Server error: %v”, err)
        }
    }()

    // 监听终止信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
    <-quit

    log.Println(“Shutting down server…”)

    // 创建超时上下文
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // 优雅关闭
    if err := server.Shutdown(ctx); err != nil {
        log.Printf(“Server forced to shutdown: %v”, err)
    }

    log.Println(“Server exited”)
}

Java Spring Boot 示例

# application.yml
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
@Component
public class GracefulShutdown implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 停止接收新请求
        // 等待现有请求完成
        // 关闭数据库连接等资源
        log.info(“Application is shutting down gracefully…”);
    }
}

Node.js 示例

const http = require(‘http’);

const server = http.createServer((req, res) => {
    res.end(‘Hello World’);
});

server.listen(8080);

// 优雅关闭
process.on(‘SIGTERM’, () => {
    console.log(‘SIGTERM received, shutting down gracefully’);

    server.close(() => {
        console.log(‘Server closed’);
        // 关闭数据库连接等
        process.exit(0);
    });

    // 强制退出超时
    setTimeout(() => {
        console.error(‘Could not close connections in time, forcefully shutting down’);
        process.exit(1);
    }, 30000);
});

4.4.2 长连接服务的优雅关闭

WebSocket 服务

func handleShutdown(hub *Hub) {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM)
    <-quit

    log.Println(“Shutting down WebSocket server…”)

    // 1. 停止接受新连接
    hub.stopAccepting()

    // 2. 通知所有客户端服务即将关闭
    hub.broadcast([]byte(`{“type”:”shutdown”,”message”:”Server is shutting down”}`))

    // 3. 等待客户端断开或超时
    timeout := time.After(25 * time.Second)
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            if hub.connectionCount() == 0 {
                log.Println(“All connections closed”)
                return
            }
        case <-timeout:
            log.Printf(“Timeout, %d connections still open”, hub.connectionCount())
            return
        }
    }
}

消息队列消费者

func (c *Consumer) handleShutdown() {
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM)
    <-quit

    log.Println(“Shutting down consumer…”)

    // 1. 停止拉取新消息
    c.stopFetching()

    // 2. 等待当前处理中的消息完成
    c.wg.Wait()

    // 3. 提交 offset
    c.commitOffsets()

    log.Println(“Consumer shutdown complete”)
}

五、Init 容器详解

5.1 Init 容器概述

Init 容器在应用容器启动之前运行,用于:

  • 等待依赖服务就绪
  • 初始化配置文件
  • 执行数据库迁移
  • 设置权限和目录
┌─────────────────────────────────────────────────────────────┐
│                        Pod 启动顺序                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌───────────┐   ┌───────────┐   ┌───────────────────┐    │
│   │  Init 1   │──▶│  Init 2   │──▶│   App Containers  │    │
│   │  (顺序)   │   │  (顺序)   │   │    (并行启动)     │    │
│   └───────────┘   └───────────┘   └───────────────────┘    │
│                                                             │
│   ◀──────── 必须成功 ────────▶                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2 等待依赖服务

apiVersion: v1
kind: Pod
metadata:
  name: app-with-init
spec:
  initContainers:
  # 等待数据库就绪
  - name: wait-for-db
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      until nc -z mysql-service 3306; do
        echo “Waiting for MySQL…”
        sleep 2
      done
      echo “MySQL is ready!”
  # 等待 Redis 就绪
  - name: wait-for-redis
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      until nc -z redis-service 6379; do
        echo “Waiting for Redis…”
        sleep 2
      done
      echo “Redis is ready!”
  containers:
  - name: app
    image: app:v1

5.3 初始化配置

apiVersion: v1
kind: Pod
metadata:
  name: app-with-config-init
spec:
  initContainers:
  - name: init-config
    image: busybox:1.36
    command:
    - sh
    - -c
    - |
      # 从 ConfigMap 生成配置文件
      cat /config-template/app.conf.template | \
        sed “s/\${DB_HOST}/${DB_HOST}/g” | \
        sed “s/\${DB_PORT}/${DB_PORT}/g” > /app-config/app.conf
  env:
  - name: DB_HOST
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: db_host
  - name: DB_PORT
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: db_port
  volumeMounts:
  - name: config-template
    mountPath: /config-template
  - name: app-config
    mountPath: /app-config
  containers:
  - name: app
    image: app:v1
    volumeMounts:
  - name: app-config
    mountPath: /etc/app
  volumes:
  - name: config-template
    configMap:
      name: app-config-template
  - name: app-config
    emptyDir: {}

5.4 数据库迁移

apiVersion: v1
kind: Pod
metadata:
  name: app-with-migration
spec:
  initContainers:
  - name: db-migration
    image: app:v1
    command:
    - /app/migrate
    - —database-url=$(DATABASE_URL)
    - up
  env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: db-credentials
        key: url
  containers:
  - name: app
    image: app:v1

5.5 Native Sidecar(K8s 1.28+)

K8s 1.28 引入了 Native Sidecar(SidecarContainers Feature Gate),解决了传统 Sidecar 的生命周期问题:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-native-sidecar
spec:
  initContainers:
  # Native Sidecar: 设置 restartPolicy: Always
  - name: log-collector
    image: log-collector:v1
    restartPolicy: Always # 关键配置
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  containers:
  - name: app
    image: app:v1
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  volumes:
  - name: logs
    emptyDir: {}

Native Sidecar 的优势

  • 在所有普通 Init 容器完成后启动
  • 与应用容器并行运行
  • 在应用容器终止后才终止
  • 支持存活探针和就绪探针

六、Pod 干扰预算(PDB)

6.1 PDB 概述

Pod Disruption Budget 限制自愿性干扰(如节点维护、滚动更新)同时中断的 Pod 数量。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 2 # 最少可用 Pod 数
  # 或使用 maxUnavailable
  # maxUnavailable: 1    # 最多不可用 Pod 数
  selector:
    matchLabels:
      app: web-app

6.2 配置策略

6.2.1 高可用服务

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: critical-app-pdb
spec:
  minAvailable: “50%” # 至少保持 50% 可用
  selector:
    matchLabels:
      app: critical-app

6.2.2 有状态服务

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: redis-pdb
spec:
  maxUnavailable: 1 # 每次最多 1 个不可用
  selector:
    matchLabels:
      app: redis

6.3 与滚动更新配合

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 6
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2 # 最多额外创建 2 个 Pod
      maxUnavailable: 1 # 最多 1 个不可用
  # …
—
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-app-pdb
spec:
  minAvailable: 4 # 至少保持 4 个可用
  selector:
    matchLabels:
      app: web-app

七、实战案例

7.1 案例一:零停机发布配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zero-downtime-app
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0 # 确保不减少可用 Pod
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: app
        image: web:v1
        ports:
        - containerPort: 8080
        lifecycle:
          preStop:
            exec:
              command: [“sh”, “-c”, “sleep 10”]
        startupProbe:
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 5
          failureThreshold: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          periodSeconds: 5
          failureThreshold: 1
          successThreshold: 1
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 10
          failureThreshold: 3
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
—
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: web

7.2 案例二:gRPC 服务优雅关闭

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: grpc-service
  template:
    metadata:
      labels:
        app: grpc-service
    spec:
      terminationGracePeriodSeconds: 90
      containers:
      - name: grpc
        image: grpc-service:v1
        ports:
        - containerPort: 50051
          name: grpc
        env:
        - name: GRPC_SHUTDOWN_TIMEOUT
          value: “60”
        lifecycle:
          preStop:
            exec:
            # 等待负载均衡器感知 + 完成进行中的 RPC
            command:
            - sh
            - -c
            - |
                        # 通知应用进入 draining 状态
                        curl -X POST http://localhost:8081/admin/drain
                        # 等待现有请求完成
                        sleep 30
        readinessProbe:
          grpc:
            port: 50051
          periodSeconds: 5
        livenessProbe:
          grpc:
            port: 50051
          periodSeconds: 10

应用端配置(Go):

func main() {
    server := grpc.NewServer()

    // 注册健康检查
    grpc_health_v1.RegisterHealthServer(server, &healthServer{})

    // 启动 admin 服务
    adminMux := http.NewServeMux()
    var draining atomic.Bool

    adminMux.HandleFunc(“/admin/drain”, func(w http.ResponseWriter, r *http.Request) {
        draining.Store(true)
        // 更新健康检查状态为 NOT_SERVING
        healthServer.SetServingStatus(“”, grpc_health_v1.HealthCheckResponse_NOT_SERVING)
        w.WriteHeader(http.StatusOK)
    })

    go http.ListenAndServe(“:8081”, adminMux)

    // 监听信号
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM)

    go func() {
        <-quit
        log.Println(“Received SIGTERM, graceful shutdown…”)

        // GracefulStop 会等待所有 RPC 完成
        server.GracefulStop()
    }()

    server.Serve(lis)
}

7.3 案例三:消息队列消费者

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kafka-consumer
spec:
  replicas: 3
  selector:
    matchLabels:
      app: kafka-consumer
  template:
    metadata:
      labels:
        app: kafka-consumer
    spec:
      # 较长的优雅终止时间,等待消息处理完成
      terminationGracePeriodSeconds: 120
      containers:
      - name: consumer
        image: kafka-consumer:v1
        env:
        - name: KAFKA_CONSUMER_GROUP
          value: “my-consumer-group”
        - name: SHUTDOWN_TIMEOUT_MS
          value: “90000”
        lifecycle:
          preStop:
            exec:
              command:
              - sh
              - -c
              - |
                        # 通知消费者停止拉取新消息
                        curl -X POST http://localhost:8080/admin/stop-consuming
                        # 等待处理完成
                        while curl -s http://localhost:8080/admin/in-flight | grep -q ‘“count”:[^0]’; do
                          echo “Waiting for in-flight messages…”
                          sleep 2
                        done
                        echo “All messages processed”
        # 消费者不需要 readinessProbe(不接收入站流量)
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 30
          failureThreshold: 3

八、故障排查指南

8.1 常见问题排查

8.1.1 Pod 无法启动

# 查看 Pod 状态
kubectl get pod <pod-name> -o wide

# 查看详细事件
kubectl describe pod <pod-name>

# 查看容器日志
kubectl logs <pod-name> -c <container-name>

# 查看上一个容器的日志(如果重启过)
kubectl logs <pod-name> -c <container-name> —previous

常见原因和解决方案

症状 原因 解决方案
ImagePullBackOff 镜像拉取失败 检查镜像名称、tag、认证配置
CrashLoopBackOff 容器反复崩溃 查看日志,检查启动命令
CreateContainerConfigError 配置错误 检查 ConfigMap、Secret 挂载
OOMKilled 内存不足 增加内存 limit

8.1.2 Pod 就绪但无法接收流量

# 检查 Endpoints
kubectl get endpoints <service-name>

# 检查 Pod 是否在 Endpoints 中
kubectl describe endpoints <service-name>

# 检查 Service 选择器是否匹配
kubectl get svc <service-name> -o yaml
kubectl get pods —show-labels

8.1.3 优雅终止问题

# 观察 Pod 终止过程
kubectl delete pod <pod-name> —grace-period=60 &
kubectl get pod <pod-name> -w

# 检查 SIGTERM 处理
kubectl exec <pod-name> — kill -0 1  # 检查 PID 1 进程

# 查看 preStop 是否执行
kubectl logs <pod-name> -c <container-name> -f

8.2 调试工具

8.2.1 临时调试容器(Ephemeral Container)

# 为运行中的 Pod 添加调试容器
kubectl debug -it <pod-name> —image=busybox —target=<container-name>

# 使用更完整的调试镜像
kubectl debug -it <pod-name> —image=nicolaka/netshoot —target=<container-name>

8.2.2 Pod 网络调试

# 在 Pod 中执行网络诊断
kubectl exec -it <pod-name> — sh

# 检查 DNS
nslookup kubernetes.default
nslookup <service-name>.<namespace>.svc.cluster.local

# 检查连通性
nc -zv <service-name> <port>
curl -v http://<service-name>:<port>/health

8.3 监控指标

# Prometheus 监控规则
groups:
- name: pod_lifecycle
  rules:
  # Pod 重启频繁
  - alert: PodRestartingTooOften
    expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: “Pod {{ $labels.pod }} 重启过于频繁”

  # Pod 启动时间过长
  - alert: PodStartupSlow
    expr: |
          (time() - kube_pod_start_time{}) > 300
          and on(pod, namespace) kube_pod_status_phase{phase=”Pending”} == 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: “Pod {{ $labels.pod }} 启动时间超过 5 分钟”

  # Pod 不健康
  - alert: PodNotReady
    expr: |
          kube_pod_status_ready{condition=”true”} == 0
          and on(pod, namespace) kube_pod_status_phase{phase=”Running”} == 1
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: “Pod {{ $labels.pod }} 处于 NotReady 状态”

九、总结

9.1 关键要点回顾

  • Pod Phase 和 Container State:理解两个层级的状态,精确定位问题
  • 三种探针的配合
    • startupProbe:保护慢启动应用
    • livenessProbe:检测死锁,只检查应用本身
    • readinessProbe:控制流量,可以检查依赖
  • 优雅终止四要素
    1. terminationGracePeriodSeconds 足够长
    2. preStop Hook 等待 Endpoints 更新
    3. 应用正确处理 SIGTERM
    4. PDB 保护高可用
  • Init 容器:用于初始化和依赖等待,按顺序执行

9.2 最佳实践清单

  • 为所有服务配置合适的探针
  • preStop 至少 sleep 5-10 秒
  • terminationGracePeriodSeconds 要覆盖所有收尾操作
  • 应用要正确处理 SIGTERM 信号
  • 为关键服务配置 PDB
  • 使用 Native Sidecar(K8s 1.28+)管理辅助容器

9.3 进阶学习方向

  1. 深入 kubelet 源码
    • 了解探针执行机制
    • 理解容器运行时接口(CRI)
  2. Service Mesh 集成
    • Istio Sidecar 注入
    • Envoy 优雅关闭配置
  3. 有状态应用管理
    • StatefulSet 生命周期
    • Operator 模式

9.4 参考资料

  • Kubernetes 官方文档 - Pod Lifecycle
  • Kubernetes 官方文档 - Configure Liveness, Readiness and Startup Probes
  • Kubernetes 官方文档 - Termination of Pods
  • CNCF Blog - Kubernetes Best Practices: Terminating with Grace

附录

A. 命令速查表

# Pod 生命周期相关
kubectl get pods -o wide                       # 查看 Pod 状态
kubectl describe pod <pod>                    # 查看详细信息和事件
kubectl logs <pod> -c <container>              # 查看日志
kubectl logs <pod> —previous                  # 查看上一个容器日志
kubectl exec -it <pod> — sh                   # 进入容器
kubectl debug -it <pod> —image=busybox        # 添加调试容器

# 探针调试
kubectl get pod <pod> -o jsonpath=‘{.status.containerStatuses[0].state}’
kubectl get events —field-selector involvedObject.name=<pod>

# 优雅终止调试
kubectl delete pod <pod> —grace-period=60    # 指定优雅终止时间
kubectl delete pod <pod> —force —grace-period=0  # 强制删除(谨慎使用)

# PDB 相关
kubectl get pdb                               # 查看 PDB
kubectl describe pdb <pdb-name>               # 查看 PDB 详情

B. 探针配置速查表

场景 startupProbe livenessProbe readinessProbe
快速启动应用 可选 推荐 推荐
慢启动应用 必须 推荐 推荐
无状态 Web 服务 可选 推荐 必须
消息队列消费者 可选 推荐 可选
批处理任务(Job) 可选 可选 不需要

C. 术语表

术语 英文 解释
Pod 阶段 Pod Phase Pod 在生命周期中的宏观状态
容器状态 Container State 容器的细粒度状态(Waiting/Running/Terminated)
存活探针 Liveness Probe 检测容器是否存活的探针
就绪探针 Readiness Probe 检测容器是否就绪的探针
启动探针 Startup Probe 检测容器是否启动完成的探针
优雅终止 Graceful Termination 平滑关闭,完成清理工作后退出
终止宽限期 Termination Grace Period SIGTERM 到 SIGKILL 的等待时间
Pod 干扰预算 Pod Disruption Budget 限制自愿性干扰同时中断的 Pod 数量
初始化容器 Init Container 在应用容器之前运行的初始化容器
原生 Sidecar Native Sidecar K8s 1.28+ 的 Sidecar 容器原生支持



上一篇:2025年行业分析:TOP TOY的泡泡玛特模仿之路与百亿估值挑战
下一篇:Linux服务器7大配置漏洞实战分析:SSH弱配、权限失控与日志缺失
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-10 08:26 , Processed in 1.171893 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表