docker容器优雅停机导致的问题说明

现象

测试同学反馈页面某功能有时候不能使用。

分析

根据页面返回结果和URL,从开发同学那里了解到该现象是新环境里a服务调b服务返回异常导致的。从多次请求的结果看来,大概50%的几率返回异常。按常理判断如果b服务有多个节点的,可能有其中个别节点异常会导致此现象发生,但是b服务只有一个节点,请求的同时,观察b服务的日志,在a调用结果异常的情况下,b没有任何日志输出,正常情况日志输出返回结果,说明请求没有到达b服务,如果对调用链路不熟的情况下完全可以抓包明确请求是否到达。那么调用失败的请求是去哪里了呢?调用链路如下:
docker容器优雅停机导致的问题说明
图1
图1可以看出a调用b的地址是从consul-server上拿到的,在consul-server控制台看到的情况入下图:
docker容器优雅停机导致的问题说明
图2
注:图中gw-mini是服务b
图2 中看到单节点的b服务,在consul注册中心上注册了两个实例,实际是这样吗?继续看下图:
docker容器优雅停机导致的问题说明
图3
图3中b服务是单节点,ip是10.100.73.72,而consul-server中的另外一个地址10.100.73.75是其他服务的ip地址。在consul上b服务下面的两个实例健康检查都是正常状态,难怪a调用b老是出现异常的返回结果,a拿到的地址list里面有一个地址是不对的。为什么consul上b服务会有两个实例?看看异常实例10.100.73.75的详情,如下图:
docker容器优雅停机导致的问题说明
图4
健康检查URL是b服务的没错,返回结果也正常,http status_code 200,跳了统一登录认证,此处其实是有拦截器导致返回结果是200。在此服务中还有其他实例,但是处于异常状态,下图所示:
docker容器优雅停机导致的问题说明
图5
在k8s集群中,服务所在pod的ip(即:注册到consul上的ip地址)是根据网络插件配置自动分配的,每次更新服务都会重建pod,pod ip也会随之改变。此时基本可以确定,在更新的时候,注册在consul上的老版本实例没有及时移除掉,其使用的ip在k8s回收之后重新分配给了新服务的pod,且新服务里面有拦截器,所有请求先跳转登录,因此导致了这样的问题出现,如果服务多了问题会更加严重。
拉了开发同学一起看看,是不是服务在关闭的时候不会反注册,从consul上下线自己。开发同学在本地IDE启动服务,点停止按钮(发送SIGTERM信号)的时候日志打印出deregister service相关内容,且在几秒钟后测试consul上服务也自动下线了。但是在出问题的新环境里面,更新服务时没有看到任何反注册日志,服务也不会从consul上自动下线,可以判定是优雅停机出现了问题。有查阅docker官方文档,在docker中,容器停止的时候会给进程号为1的进程发送SIGTERM,且在默认10s之后进程还没退出的话发送SIGKILL信号强制关闭。查看此项目dockerfile,其中entrypoint是以shellform格式运行了一个bash脚本,在bash脚本中启动了java进程,以此种方式运行的进程,实际上上是sh –c的子命令,且不会传递信号给entrypoint进程,所以在关闭的时候java进程并没有收到优雅停机信号,自然也没机会通知consul下线服务。

处理

两方面,一个是让java进程能够接收到SIGTERM信号,另一个是健康检查的问题。
第一个问题,整改dockerfile,entrypoint采用execform的格式写,且完全没必要放在bash脚本里面,直接启动java进程。
第二个问题,对健康检查URL去掉拦截。
还有一方面就是设置pod lifecycle,添加prestop钩子。

结果

整改之后,服务关闭的时候正常输出deregister service信息,consul-server上也会自动下线服务。此异常页面再没出现过类似问题。

猜你喜欢

转载自blog.51cto.com/weifan/2483460