内容简介:在 k8s 平台测试自研 Service Mesh 方案时,发现更新服务时,会有少量请求耗时剧增。跟踪排查后确认是由于 Pod 被删除后,原先的 Pod 的 IP 不存在,客户端建立连接超时引起。正常升级某个服务的 Deployment。升级策略,先起一个新实例,再停一个旧实例:
在 k8s 平台测试自研 Service Mesh 方案时,发现更新服务时,会有少量请求耗时剧增。跟踪排查后确认是由于 Pod 被删除后,原先的 Pod 的 IP 不存在,客户端建立连接超时引起。
现象
正常升级某个服务的 Deployment。
升级策略,先起一个新实例,再停一个旧实例:
type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0
实例停止前如果没有请求会立即退出,如果有请求则等待最多 60 秒,仍然没有结束时会被强制杀掉。
terminationGracePeriodSeconds: 60
升级过程中,发现服务响应时间的 98 值增长很多,95 值没有太大变化,看起来有少量请求被升级操作影响到了。
原因
排查后,确认部分请求变慢的原因是因为和后端实例建立连接超时,由于使用的是 Go 的 DefaultTransport,所以连接超时时间为 30s,部分请求在超时 30s 后才被重试,从而导致响应时间的 98 值变慢。
为什么建立连接会超时?
原来在升级实例的过程中,实例被杀掉,对应的容器的虚拟 IP 就不存在了,而客户端建立连接时发送的 SYNC 包收不到回应,会一直重发,直到超时。
之所以客户端仍然会给该 IP 发送请求,是因为我们自研的 Service Mesh 方案的服务发现没有采用 k8s 默认的 DNS 轮询方式,而是自己开发的服务发现组件,为了能够更好地配合负载均衡的能力。网关是采用轮询的方式,每隔 10s 从 Discovery 组件同步一次数据,所以被杀掉的实例没有及时被同步到各网关。
kubernetes Pod 停止流程
为了更好解决问题,我们需要理解 k8s 中单个 Pod 停止的流程。
- 用户发送请求删除 Pod,默认终止等待时间为 30s
- 在 Pod 超过该等待时间后 API server 就会更新 Pod 的状态为 dead
- 在客户端命令行上显示 Pod 状态为 terminating
-
与步骤三同时,当 Kubelet 观察到一个 Pod 在步骤2被标记为 terminating
,开始终止工作
- 如果在pod中定义了 preStop hook ,在停止 pod 前会被调用。如果在等待期过后, preStop hook 依然在运行,第二步会再增加2秒的等待期
- 向 Pod 中的进程发送 SIGTERM 信号
- 跟第三步同时,该 Pod 将从该 service 的地址列表中删除,不再是 replication controllers 中处于运行状态的实例之一。关闭的慢的 Pod 将不会再处理流量,因为负载均衡器(像是 service proxy)会将它们移除
- 过了等待期后,将向 Pod 中依然运行的进程发送 SIGKILL 信号而杀掉进程
- Kublete 会在 API server 中通过将优雅周期设置为0(立即删除)来完成 Pod 的删除。Pod 将会从 API 中消失,并且在客户端也不可见
解决方案
优化超时时间
使用自定义的 Transport,内网的话超时时间可以减少为 1s,让请求尽快被重试,虽然不能解决问题,但是可以有效缓解问题。
确保实例被服务发现摘除后再停止
思考了问题发生的原因,首先想到的就是能不能让实例先从服务发现中摘除,确认服务发现数据被同步到了各网关后,再杀实例。搜索了 k8s 的相关文档,发现通过 preStop 的 hook 机制,可以实现该功能。
示例配置如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: terminationGracePeriodSeconds: 90 containers: - name: nginx image: my-nginx:xxx lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 30"] ports: - containerPort: 80
重点就在于 lifecycle
的配置。对应实例停止时,会先将该实例的服务发现地址从 service
中移除,之后会调用我们给定的命令 sleep 30
,等待 30s 后,再给实例发送 SIGTERM 信号,如果实例超过 terminationGracePeriodSeconds
配置的时间后,会再给实例发送 SIGKILL 信号,强行杀掉实例。
我们服务发现数据同步间隔是 10s,留出 30s 的时间,所有网关的服务发现数据正常情况下已经全部同步完成,不会再有新的流量被路由到该实例上,也就不会出现新建连接超时的问题。
需要注意的是,由于我们延迟了 30s 停止实例,所以保险起见 terminationGracePeriodSeconds 也可以相应的增加 30s。
猜想其他的解决方法
k8s 自身提供延迟停止实例的能力
如果 k8s 自身就能通过 Deployment 参数配置实现上文中我们通过 preStop 实现的功能会更好一些,毕竟是一个比较取巧的方案,不一定完善。
IP 被摘除后建立连接可以直接返回错误
涉及到 k8s 集群的网络解决方案,不一定所有的架构都能支持,需要进一步调研。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring Cloud 参考文档(Hystrix超时和Ribbon客户端)
- redigo设置超时时间
- 深入理解 JDBC 的超时
- [golang]一定要设置超时
- 超时与重试机制(一)
- MongoDB 查询超时异常 SocketTimeoutException
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入浅出SQL(中文版)
贝里 编 / O‘Reilly Taiwan公司 / 东南大学 / 2009-6 / 98.00元
你将从《深入浅出SQL(中文版)》学到什么?在如今的世界,数据就是力量,但是成功的真正秘诀却是管理你的数据的力量。《深入浅出SQL(中文版)》带你进入SQL语言的心脏地带,从使用INSERT和SELECT这些基本的查询语法到使用子查询(subquery)、连接(join)和事务(transaction)这样的核心技术来操作数据库。到读完《深入浅出SQL(中文版)》之时,你将不仅能够理解高效数据库设......一起来看看 《深入浅出SQL(中文版)》 这本书的介绍吧!