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/