内容简介:kubernetes的重要性已经不言而喻了,目前不少的企业已经选择将自己的业务运行在kubernetess上。我本人在多家企业落地kubernetes,也和很多企业负责人聊过类似话题。我们发现很多人把业务容器化简单理解为dockerfile + 原有代码。这是不对的,新的运行形态就决定了我们的代码必须做相应的适配,才可以充分发挥kubernetes的强大。本系列文章主要讲述业务容器化最佳实践,希望对大家有所帮助。今天我们主要阐述
kubernetes的重要性已经不言而喻了,目前不少的企业已经选择将自己的业务运行在kubernetess上。我本人在多家企业落地kubernetes,也和很多企业负责人聊过类似话题。我们发现很多人把业务容器化简单理解为dockerfile + 原有代码。这是不对的,新的运行形态就决定了我们的代码必须做相应的适配,才可以充分发挥kubernetes的强大。本系列文章主要讲述业务容器化最佳实践,希望对大家有所帮助。
今天我们主要阐述 应用优雅退出 。
不要把代码架构的失败归结于Kubernetes
开源的世界里,大家已经习惯了引入各种框架来解决自己的问题,但是我们都知道技术的世界是没有银弹的。
我在早期参加一些meetup的时候,很多来参会的人会提出一个问题:如何在pod缩容的时候,避免5xx?进而怀疑kubernetes可用性。
这个问题其实比较好理解。业务pod通过service或是ingress的方式,暴露出去。大多数场景都是pod作为负载均衡器的upstream存在,但是任何的负载均衡器不能实时探测你的健康状态,当你的pod退出的时候,中间有一定的时间差,真实流量被分发到了一个退出的pod当中,自然就出现了5xx。
业务运行在pod形相对于普通的虚拟机,具有动态的特性,比如扩缩容,或是被驱赶等等。这就要求我们的业务要做对应的适配。
如何优雅退出
在kubernetes中,正确的平滑退出的顺序应该如下:
- 监听并捕获SIGTERM信号
- 停止接受新的连接
- 完成已经存在的活跃请求
- 关闭keepalive
- 退出
我们知道当pod被delete的时候,SIGTERM信号被发送到每个容器中的主进程(PID 1),默认开始20s的倒计时(该值可以设置),20s后,SIGKILL信号发送,程序被kill。
首先可以查看我们的应用是否是1号主进程,如果不是,那么我们需要让1号主进程去通知业务进程,即1号进程的子进程。此类问题,1号进程很多可能是一个 run.sh
启动脚本。
例如golang项目:
sigTerm := make(chan os.Signal, 1) signal.Notify(sigTerm, syscall.SIGTERM) ... go server.ListenAndServe() <-sigTerm log.Println("got SIGTERM, prepare to shut down in 20s")
对于 停止接受新的请求连接 ,我们知道在k8s中存在LivenessProbe 和 ReadinessProbe两种healthcheck探针。
- LivenessProbe :用于判断容器是否存活,如果 LivenessProbe 探针探测到容器不健康,则 kubelet 将杀掉容器,并根据容器的启动策略做相应的处理,如果一个容器不包括 LivenessProbe 探针,kubelet 认为该容器的 LivenessProbe 探针返回的值永远是 Success。
- ReadinessProbe:用于判断容器是否启动完成(ready 状态),可以接受请求,如果 ReadinessProbe 探针检测到失败,则 Pod 的状态则被更改。
这里我们应该使用的是ReadinessProbe。ReadinessProbe 探测影响的是该pod是否是一个准备就绪的pod,只有检测通过的pod才会加到lb的upstream中。
所以我们需要保证:
- 我们的应用提供并设置了ReadinessProbe。
- 我们的ReadinessProbe是退出有关系的。
两个保证是有一定的递进关系的,详细解释一下。
如果我们设置了如下的探针:
func healthHandler(w http.ResponseWriter, r *http.Request) { typ := r.URL.Query().Get("type") log.Printf("healthy - %s", typ) w.WriteHeader(200) w.Write([]byte("ok")) }
该探针只是可以保证,pod在启动的过程中,pod处于就绪状态,流量才会打过来。在pod彻底被kill之前,该探针一直返回就绪状态,而我们要实现在停机的过程中,停止接受新的请求连接。所以我们的探针必须是和停止有关,正确的实现方式应该如下:
func healthHandler(w http.ResponseWriter, r *http.Request) { typ := r.URL.Query().Get("type") if !healthy { log.Printf("not healthy - %s", typ) w.WriteHeader(503) w.Write([]byte("not ready")) } else { log.Printf("healthy - %s", typ) w.WriteHeader(200) w.Write([]byte("ok")) } }
设置一个环境变量healthy,初始化值为true。
当接收到系统发送的SIGTERM, 业务pod开始了停机过程,先将healthy更改为false。进而探针探测失败,新的流量不再打到处于停机过程中的pod。
对于完成已经存在的活跃请求
此处要求不要急于退出,sleep一定时间,保证已经活跃的请求完成。
代码如下:
time.Sleep(sigtermTimeout)
假如存在一些长连接服务, 关闭keepalive 也是非常重要的。
代码实现如下:
if !keepAlive { log.Println("SetKeepAlivesEnabled=false") server.SetKeepAlivesEnabled(false) }
总结
该文主要讲了业务如何在代码上做一些措施,实现pod的优雅退出。接下来,我们会继续实践系列,计划从资源分配等角度展开。
以上所述就是小编给大家介绍的《业务容器化最佳实践--优雅退出》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法交易:制胜策略与原理
[美]欧内斯特·陈(Ernest P. Chan) / 高闻酉、黄蕊 / 机械工业出版社 / 49.00
本书是一本引人入胜、信息量大、覆盖各类交易策略的图书。无论个人投资者,还是机构投资者,都可以借鉴和使用其中的策略。本书中的策略大致可分为均值回归系统和动量系统两大类。书中不仅介绍了如何使用每种类别的交易策略,更解释了各种策略之所以有效的原因。本书始终以简单、线性的交易策略为重心,因为复杂的交易策略容易受到过度拟合及数据窥探的侵害。数学和软件是算法交易的两条腿。本书用到了一定程度的数学知识,使其对各......一起来看看 《算法交易:制胜策略与原理》 这本书的介绍吧!