如何在 Kubernetes 中实现 Pod 的优雅扩缩容

5次阅读

如何在 Kubernetes 中实现 Pod 的优雅扩缩容

本文详解 kubernetes 中实现 http 服务优雅扩缩容的关键机制:结合 readiness probe、prestop 生命周期钩子与应用层信号处理,确保流量零中断、请求不丢失。

本文详解 kubernetes 中实现 http 服务优雅扩缩容的关键机制:结合 readiness probe、prestop 生命周期钩子与应用层信号处理,确保流量零中断、请求不丢失。

在 Kubernetes 中实现真正的“优雅缩容”(graceful scaling),仅靠应用内监听 SIGTERM 并等待请求完成是不够的——正如你在单实例缩容时观察到的 HTTP 错误所揭示的:Kubernetes Service 的 Endpoint 更新存在延迟,导致部分新请求仍被路由至正在关闭的 Pod。

根本原因在于:Service 的负载均衡器(kube-proxy)依赖于 Endpoints 对象来决定将流量转发给哪些 Pod。而 Endpoints 仅在 Pod 状态变为 NotReady 或被彻底删除后才会更新。若未显式控制就绪状态,Pod 在收到 SIGTERM 后仍会持续接收新请求,直到其被强制终止,从而引发连接拒绝或超时。

✅ 正确做法:三步协同保障优雅性

1. 配置 Readiness Probe(就绪探针)

Readiness probe 告诉 Kubernetes “此 Pod 是否已准备好接收流量”。当 Pod 收到 SIGTERM 时,应立即让该探针失败,触发 Service 将其从 Endpoints 中移除:

# deployment.yaml 片段 livenessProbe:   httpGet:     path: /healthz     port: 8080   initialDelaySeconds: 30   periodSeconds: 10  readinessProbe:   httpGet:     path: /readyz     port: 8080   initialDelaySeconds: 5   periodSeconds: 5   failureThreshold: 1  # 一次失败即标记为 NotReady

? 关键点:/readyz 接口需由应用动态控制。收到 SIGTERM 后,应立即返回非 2xx 状态(如 503 Service Unavailable)。

2. 使用 PreStop Hook 主动降级就绪状态

PreStop 钩子在 Pod 被终止前同步执行,且在 terminationGracePeriodSeconds 计时开始后立即触发。这是触发就绪状态变更的最佳时机:

lifecycle:   preStop:     exec:       command: ["/bin/sh", "-c", "curl -f http://localhost:8080/readyz?down=true || true"]

配合 Go 应用逻辑(简化版):

var isShuttingDown = false  func readyzHandler(w http.ResponseWriter, r *http.Request) {     if r.URL.Query().Has("down") {         isShuttingDown = true         w.WriteHeader(http.StatusServiceUnavailable)         return     }     if isShuttingDown {         w.WriteHeader(http.StatusServiceUnavailable)         return     }     w.WriteHeader(http.StatusOK) }  // SIGTERM 处理器(保持原有逻辑,但不再单独依赖它做流量隔离) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) go func() {     <-sigChan     fmt.Println("Received SIGTERM, marking as not ready...")     isShuttingDown = true     // 可选:等待活跃连接自然结束(如使用 manners 或原生 http.Server.Shutdown)     server.Shutdown(context.Background()) // 替代已弃用的 manners }()

⚠️ 注意:manners 库已多年未维护,推荐改用 Go 1.8+ 原生 http.Server.Shutdown(),它提供更可靠、标准的优雅关闭支持。

3. 合理设置 terminationGracePeriodSeconds

默认值为 30 秒。需确保该时间 ≥ 应用最长请求处理时间 + 缓冲余量(建议至少 60–120 秒):

spec:   terminationGracePeriodSeconds: 90

完整 Deployment 示例(关键字段节选)

apiVersion: apps/v1 kind: Deployment metadata:   name: graceful-app spec:   replicas: 3   template:     spec:       containers:       - name: app         image: your-graceful-app:v1         ports:         - containerPort: 8080         lifecycle:           preStop:             exec:               command: ["/bin/sh", "-c", "curl -f http://localhost:8080/readyz?down=true || true"]         readinessProbe:           httpGet:             path: /readyz             port: 8080           periodSeconds: 5           failureThreshold: 1         livenessProbe:           httpGet:             path: /healthz             port: 8080           periodSeconds: 10       terminationGracePeriodSeconds: 90

? 为什么单实例缩容更容易出错?

当集群中 Pod 数量降至 1 时,任何短暂的 Endpoint 更新延迟(如 kube-controller-manager 同步延迟、etcd 写入延迟、kube-proxy 规则刷新延迟)都会被放大:此时所有流量都指向唯一 Pod,若它在 NotReady 状态生效前仍接收新请求,且又在请求处理中被终止,错误便不可避免。上述三重保障正是为了消除这一窗口。

✅ 总结:优雅扩缩容 = 控制流量入口 + 协同生命周期 + 保障退出时间

  • ❌ 不要只依赖 SIGTERM 处理 —— 它无法阻止新请求抵达;
  • ✅ 必须通过 readinessProbe 动态控制 Service 流量分发;
  • ✅ 必须用 preStop 钩子提前触发就绪状态变更;
  • ✅ 必须用 server.Shutdown() 替代过时库,确保连接真正 drain;
  • ✅ 必须调优 terminationGracePeriodSeconds,留足缓冲时间。

遵循此模式,无论扩至 100 实例,还是缩至 1 实例,你的 HTTP 服务都将实现真正的零中断优雅伸缩。

text=ZqhQzanResources