Kubernetes Pod优雅终止与零宕机滚动更新实战配置

Pod终止不是简单的kill进程那么简单

Kubernetes容器编排环境中,应用更新、节点驱逐、手动缩容都会触发Pod终止。默认配置下,kubelet发送SIGTERM后等待30秒,超时直接SIGKILL。如果应用未正确处理SIGTERM信号——比如HTTP请求还在处理中、数据库事务未提交、消息未确认——请求丢失、数据不一致就成了常态。DevOps实践中,优雅终止和零宕机滚动更新是保证SRE稳定性工程指标的核心配置。

Pod终止流程与preStop钩子配置

Kubernetes Pod终止的完整流程:

1. API Server接收删除请求,Pod状态变为Terminating
2. kubelet执行preStop钩子(如果配置)
3. Endpoints Controller从Service后端列表移除该Pod
4. kubelet发送SIGTERM信号给容器内PID 1进程
5. 等待terminationGracePeriodSeconds(默认30秒)
6. 超时后发送SIGKILL强制终止

关键点:步骤3和步骤4是并行的。从Service后端摘除Pod有传播延迟(默认Endpoint更新周期约5-10秒),这意味着SIGTERM信号发出后,仍可能有新的流量通过iptables/ipvs规则到达已终止的Pod。

解决方案是preStop钩子加一段等待时间,确保Service层摘除Pod后应用才真正开始关闭:

spec:
  terminationGracePeriodSeconds: 60
  containers:
  - name: app
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 15"]
    ports:
    - containerPort: 8080

preStop sleep 15秒,给kube-proxy足够时间更新iptables规则。加上应用自身的关闭耗时,terminationGracePeriodSeconds设60秒留足余量。

Java应用优雅关闭实践

Spring Boot应用需要正确响应SIGTERM信号,完成进行中的请求后再退出:

# application.yml
server:
  shutdown: graceful

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

配合Embedded Tomcat的优雅关闭:

@Bean
public GracefulShutdown gracefulShutdown() {
    return new GracefulShutdown();
}

@Bean
public ServletWebServerFactory servletWebServerFactory() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.addCustomizers(gracefulShutdown);
    return factory;
}

public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
    private volatile Connector connector;

    @Override
    public void customize(Connector connector) {
        this.connector = connector;
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        this.connector.pause();
        Executor executor = this.connector.getProtocolHandler().getExecutor();
        if (executor instanceof ThreadPoolExecutor) {
            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
            threadPoolExecutor.shutdown();
            try {
                if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
                    log.warn("Tomcat thread pool did not shut down gracefully within 30s");
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Go应用优雅关闭实践

Go应用的优雅关闭需要手动处理os.Signal:

func main() {
    srv := &http.Server{Addr: ":8080"}

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down server...")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }
    log.Println("Server exited")
}

srv.Shutdown会停止接受新连接,等待活跃连接处理完毕再退出,超时则强制关闭。

Deployment滚动更新参数调优

零宕机滚动更新的核心参数在Deployment的strategy字段:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1

– maxUnavailable: 0 表示更新过程中不允许有Pod不可用,保证始终有足够副本服务流量
– maxSurge: 1 表示先创建1个新Pod,就绪后再终止1个旧Pod,逐步替换

配合readinessProbe确保新Pod真正就绪后才接收流量:

readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
  failureThreshold: 3

livenessProbe与readinessProbe分离:livenessProbe检测进程死活,readinessProbe检测服务是否准备好处理请求。避免livenessProbe失败导致Pod被杀重启引发更多不可用。

PodDisruptionBudget保障可用性

PDB限制主动驱逐(升级、维护)导致的不可用Pod数量:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 80%
  selector:
    matchLabels:
      app: my-app

minAvailable设80%意味着滚动更新时最多20%的Pod可以同时不可用。集群管理员执行kubectl drain驱逐节点时也会遵守这个约束。

CI/CD流水线中的滚动更新验证

在GitLab CI或Jenkins Pipeline中加入健康检查等待步骤:

deploy-job:
  stage: deploy
  script:
    - kubectl set image deployment/app app=my-app:$CI_COMMIT_SHA
    - |
      echo "Waiting for rollout to finish..."
      kubectl rollout status deployment/app --timeout=300s
      if [ $? -ne 0 ]; then
        echo "Rollout failed, rolling back..."
        kubectl rollout undo deployment/app
        exit 1
      fi
    - |
      echo "Running smoke tests..."
      for i in $(seq 1 10); do
        HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://app.example.com/health)
        if [ "$HTTP_CODE" != "200" ]; then
          echo "Health check failed (attempt $i): HTTP $HTTP_CODE"
          kubectl rollout undo deployment/app
          exit 1
        fi
        sleep 2
      done
      echo "Smoke tests passed"

rollout status等待所有Pod就绪,超时自动回滚。smoke test阶段对健康检查端点做10次验证,任何一次非200立即回滚。

常见故障场景与排查路径

场景1:滚动更新期间5xx错误率上升

检查readinessProbe配置是否正确——如果probe路径返回200但应用实际未就绪(如数据库连接池未初始化),流量会打到未就绪的Pod。确保readinessProbe验证关键依赖(DB、缓存)的连通性。

场景2:Pod卡在Terminating状态

kubectl describe pod查看Events,确认preStop钩子是否卡住。常见原因:preStop中执行了不可中断的脚本或sleep时间超过terminationGracePeriodSeconds。检查容器内PID 1是否是应用进程——如果用shell脚本启动应用,PID 1是shell而非应用,SIGTERM信号不会传递到应用进程。解决方案:使用exec启动应用或配置正确的信号传递。

场景3:新版本Pod始终NotReady

readinessProbe持续失败,kubectl logs查看应用启动日志排查。如果新版本有Bug导致无法启动,maxSurge策略会不断创建新Pod但旧Pod不被终止,资源耗尽。设置progressDeadlineSeconds: 600,超过10分钟未完成更新自动标记为失败。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/kubernetespod-you-ya-zhong-zhi-yu-ling-dang-ji-gun-dong/

(0)
小编小编
上一篇 9小时前
下一篇 9小时前

相关推荐