业务容器化上云之后,时常会有版本的动态变更,如何无损升级越发重要。结合底层技术原理,本文将对Pod优雅停机展开分析,供业务团队参考。
绕过平台控制器,以及kubelet组件复杂的sync、manager逻辑,Pod被删除或者被节点压力驱逐后,Node Kubelet组件都会走到一个内部函数:killContainer()。killContainer内部再调用runtimeService接口StopContainer,走到CRI运行时层面,完成进程的停止(sigterm)、杀死(sigKill)。
先来看下killContainer函数:
(1)从注释上不难看出,函数主体大致可分为2步:如果有preStop逻辑,先Run一下;然后走StopContainer逻辑
// killContainer kills a container through the following steps:
// * Run the pre-stop lifecycle hooks (if applicable).
// * Stop the container.
(2)结合代码,可以看到kubelet内置了一个2s 的优雅终止时间,如果业务Pod没有配置TerminationGracePeriodSeconds,就会使用这个默认值(minimumGracePeriodInSeconds)。
(3)如果用户Pod配置了preStop 生命周期hook且gracePeriod>0, 执行preStopHook逻辑;
这里gracePeriod,将会作为preStopHook执行的超时时间。所以,日常实践中,Pod TerminationGracePeriodSeconds配置需要大于preStop 执行的时间——需要注意。
(4)从killContainer代码中,也不难发现,preStopHook执行的时间(如果有的话),会算在整体的gracePeriod中。GracePeriod扣除之后,走到StopContainer逻辑,作为其stop 容器的超时参数timeout。
以containerd 运行时为例,runtimeService的StopContainer,会继续调用CRI接口,经GRPC调用到运行时层面:
kubelet 调用逻辑:
containerd stopContainer 方法具体实现:
代码解读:
> timeout>0 , 发sigterm 信号,终止容器;
> timeout<=0, 或者上述sigterm 逻辑结束,发sigkill信号,杀死容器;
这里分享一个优雅停机的golang实例,供交流参考:
var onlyOneSignalHandler = make(chan struct{})
var shutdownHandler chan os.Signal
var shutdownSignals = []os.Signal{syscall.SIGTERM, syscall.SIGKILL}
// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned
// which is closed on one of these signals. If a second signal is caught, the program
// is terminated with exit code 1.
func SetupSignalHandler() <-chan struct{} {
close(onlyOneSignalHandler) // panics when called twice
shutdownHandler = make(chan os.Signal, 2)
stop := make(chan struct{})
signal.Notify(shutdownHandler, shutdownSignals...)
go func() {
<-shutdownHandler
close(stop)
sig := <-shutdownHandler
if sig == syscall.SIGKILL {
os.Exit(1) // if sigkill signal. Exit directly.
}
}()
return stop
}
(1)处理信号sigterm、sigkill;
(2)只在收到sigkill信号,才立即exit 1 退出运行;
func main() {
logs.Info("server started")
stop := util.SetupSignalHandler()
tick := time.NewTicker(time.Second * 3)
//business logic
exit := false
for {
select {
case <-stop:
exit = true
case <-tick.C:
logs.Info("loop once")
}
if exit {
break
}
}
logs.Info("grace shutting down 120 seconds...")
time.Sleep(time.Second * 120)
logs.Info("server end")
}
(1) select 逻辑收到stop 信号,才会退出for 循环,模拟业务运行逻辑;
(2)sleep 120s,模拟grace shutdown 优雅停机时的收尾逻辑;
由此可见,业务pod 实现优雅停机的必要条件,包含:
(1)pod 内部业务支持sigterm、sigkill 信号的分别处理,前者优雅shutdown、后者直接退出;
(2)配置合理的pod终止宽限期:TerminationGracePeriodSeconds;
(3)必要时Pod可以配置preStop hook;
https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。