内容简介: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的优雅退出。接下来,我们会继续实践系列,计划从资源分配等角度展开。
以上所述就是小编给大家介绍的《业务容器化最佳实践--优雅退出》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
代码整洁之道:程序员的职业素养
罗伯特·C.马丁 (Robert C.Martin) / 余晟、章显洲 / 人民邮电出版社 / 2016-9-1 / 49.00元
1. 汇聚编程大师40余年编程生涯的心得体会 2. 阐释软件工艺中的原理、技术、工具和实践 3. 助力专业软件开发人员具备令人敬佩的职业素养 成功的程序员在以往的工作和生活中都曾经历过大大小小的不确定性,承受过永无休止的压力。他们之所以能够成功,是因为拥有一个共同点,都深切关注创建软件所需的各项实践。他们将软件开发视为一种需要精雕细琢加以修炼的技艺,他们以专业人士的标准要求自己,......一起来看看 《代码整洁之道:程序员的职业素养》 这本书的介绍吧!