
一、概述
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
状态码判断:
3.5.2 TCP 探针
tcpSocket:
port: 3306
适用场景:数据库、缓存等不支持 HTTP 的服务
3.5.3 命令探针
exec:
command:
- sh
- -c
- “pg_isready -h localhost -p 5432”
退出码判断:
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 更新 应用开始关闭
竞态条件:
- kubelet 可能在 Endpoints 更新到所有节点之前就杀死 Pod
- Ingress 或负载均衡器仍在向即将终止的 Pod 发送流量
- 导致请求失败
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:控制流量,可以检查依赖
- 优雅终止四要素:
terminationGracePeriodSeconds 足够长
preStop Hook 等待 Endpoints 更新
- 应用正确处理 SIGTERM
- PDB 保护高可用
- Init 容器:用于初始化和依赖等待,按顺序执行
9.2 最佳实践清单
- 为所有服务配置合适的探针
- preStop 至少 sleep 5-10 秒
- terminationGracePeriodSeconds 要覆盖所有收尾操作
- 应用要正确处理 SIGTERM 信号
- 为关键服务配置 PDB
- 使用 Native Sidecar(K8s 1.28+)管理辅助容器
9.3 进阶学习方向
- 深入 kubelet 源码
- Service Mesh 集成
- Istio Sidecar 注入
- Envoy 优雅关闭配置
- 有状态应用管理
- 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 详情
| 场景 |
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 容器原生支持 |