k8s 优雅平滑地关闭Pod
目录
k8s 优雅平滑地关闭Pod
引发报错原因
迁移的时候分上线和下线,这里大体说一下,接下来主要以平滑优雅下线服务为主
上线POD:应用没拉起来,流量就进入了。
结论:POD上线只需要配置健康检查,即可避免流量损失。
下线POD:流量没停掉,应用就关掉了。
结论:在没有使用preStop的情况下,除非你让程序收到SIGTERM后延迟一会再关闭监听,否则可能丢失流量。
k8s删除Pod的过程
在删除pod 的过程中,有两条平行的时间线。一是改变网络规则的时间线,另一个是 pod 的删除
网络规则生效
- kube-apiserver 接收到 pod 删除请求,将 pod 在 Etcd 中的状态更新为 Terminating;
- Endpoint Controller 从 Endpoint 对象中删除 pod 的 IP;
- kuber-proxy 根据 Endpoint 对象的变化更新 iptables 的规则,不再将流量路由到被删除的 Pod。
删除 pod
- kube-apiserver 接收到 Pod 删除请求,将 Pod 的在 Etcd 中的状态更新为 Terminating
- preStop 钩子被执行
- Kubelet 向容器发送 SIGTERM
- 继续等待,直到容器停止,或者超时 spec.terminationGracePeriodSeconds,这个值默认为 30s
- 如果超过了 spec.terminationGracePeriodSeconds 容器仍然没有停止,k8s 将会发送 SIGKILL 信号给容器
- 进程全部终止后,整个 Pod 完全被清理掉
注意:这个优雅退出的等待计时spec.terminationGracePeriodSeconds是与 preStop 同步开始的,而且它也不会等待 preStop 结束
可能遇到的问题
- 应用程序在收到 SIGTERM 信号后直接终止了运行,导致部分还没有被处理完的请求直接中断,代理层返回 502
- Service Endpoints 移除不够及时,在 Pod 已经被终止后,仍然有个别请求被路由到了该 Pod,得不到响应导致 504
总的来说,这两个情况都是,service还在,但是pod因为过早的发送了SIGTERM,导致一些请求没处理完就被中断了,或者pod直接被删除了无法响应新进来的请求
如何避免上述问题
为容器内的进程设置正常关闭,可以解决502的问题
以 SpringBoot 为例,启用优雅关闭可以 Spring Boot 配置文件中添加下面设置
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
添加 preStopHook
当kube-apiserver 接收到 pod 删除请求后,必须要预留一段时间,来等待网络规则的更新,避免新的流量路由到一个不可用的pod上 。
因此,应该让 Kubelet 在收到删除 pod 事件时“sleep 一下”,并在给Pod发送SIGTERM之前留出足够的时间来更新网络规则,可以解决504的问题
containers:
- name: my-app
# 添加下面这部分
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "sleep 10"
修改终止 GracePeriodSeconds
Kubernetes 为容器删除留下了 30 秒的最大时间尺度。如果 Spring 的优雅关闭超时时间和 Kubernetes 的 preStopHooks 之和超过 30 秒,可能会导致 Kubernetes 在 Spring Boot 处理完请求之前强行删除容器。因此,如果过程超过 30 秒,则应将 timerminationGracePeriodSeconds 调整为大于30秒
dockerfile实例
FROM 192.168.1.2/common/openjdk:1.8
ENV JAVA_OPT="-Xmx640m -Xms640m -XX:MaxMetaspaceSize=256M -XX:MetaspaceSize=256M -Dfile.encoding=utf-8" \
SERVICE_PORT=1100 VERSION=
EXPOSE ${SERVICE_PORT}
WORKDIR /opt
COPY app.jar .
COPY run.sh .
RUN chmod +x run.sh && mkdir -p config
CMD ["bash", "run.sh"]
cat run.sh
#!/bin/bash
WORKDIR=$(cd `dirname $0`;pwd)
#便于k8s挂载configmap
[[ -f /tmp/config/application-dev.yml ]] && cp /tmp/config/application-dev.yml ${WORKDIR}/config/
cd ${WORKDIR}
java -Xbootclasspath/a:./config/ $JAVA_OPT -jar app.jar --server.port=${SERVICE_PORT} > /dev/stdout 2>&1 &
tail -f /dev/stdout