k8s servelList(服务列表) 卡死不同步问题分析

news2024/12/23 20:08:35

提要


容器集群版本情况:k8s 1.20

客户端k8s client版本: 0.21

事情是这样的,运行了一年的服务,突然有一天业务反馈服务使用异常,然后初步调查结果如下

以下截图是网关异常

以下截图是客户端zull(feign)转发异常(底层都是使用ribbon)

翻译信息:

某个service的Endpoints已经更新了,但是客户端的服务发现没有更新过来,还是使用了历史的serverList 配置,配置的信息包含已经下线的pod ip,这样就导致这个调用出现问题了,最直接的异常就是 No route to host (Host unreachable)

科普一下:

Endpoints与Pod的关系是什么?

Endpoints是Kubernetes集群中的一个资源对象,存储在etcd中,用来记录一个Service对应的所有Pod的访问地址。

故障分析


一:CoreDns 排查

最开始怀疑是coredns出问题了,因为客户端使用的服务发现直接跟dns挂钩

这个是注册中心流程图,最开始是apiserver -> coredns -> kube-proxy -> iptables ,然后这个serverList的更新,却不是直接跟coredns打交道的,而是跟kube-proxy。

创建新的 Service 对象时,会得到一个虚拟 IP,被称为 ClusterIP。服务名及其 ClusterIP 被自动注册到集群 DNS 中,并且会创建相关的 Endpoints 对象用于保存符合标签条件的健康 Pod 的列表,Service 对象会向列表中的 Pod 转发流量。

与此同时集群中所有节点都会配置相应的 iptables/IPVS 规则,监听目标为 ClusterIP 的流量并转发给真实的 Pod IP。

以上参考:https://ost.51cto.com/posts/17770

究竟是不是coredns出问题了呢,或者说是kube-proxy出问题了呢?!

答案是否定的!

检查容器的所有coredns的pod日志以及kube-proxy的pod日志,没有显著的异常。

另外并不是所有的客户端服务发现列表出现问题,没法同步,只是少数的应用(微服务,pod -> service)出故障了。

那,究竟是什么问题呢?!

二:客户端的服务发现排查

通过上面的分析,大概率是应用的服务发现出问题了。现在开始分析出问题应用的依赖配置

        <!-- 服务发现 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-kubernetes-discovery</artifactId>
            <version>0.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-kubernetes-ribbon</artifactId>
            <version>0.2.1.RELEASE</version>
        </dependency>

纳尼?!我的乖乖,该应用的k8s服务发现的版本居然这么旧,这个跟公司推荐的版本差太远了,公司推荐的是1.1.6以上,这个版本的服务发现目前来看是没有出问题的。

直观感受是需要客户端需要升级这个服务发现的版本,但很尴尬发现,该应用绑定的spring boot版本也比较老(很老很老这种),直接升1.1.6版本,会导致一系列的版本冲突,比较恶心!

有没有其它好的办法呢?!

没办法了,需要被迫研究源代码了!

三:服务发现源代码分析

Spring Cloud Kubernetes服务注册与发现实现原理

在sck-demo项目搭建之初,我们是跟着官方提供的demo去实现服务注册和发现的,也就是在每个服务的Application类上添加一个@EnableDiscoveryClient注解。

我们并未配置Kubernetes的地址,但我们使用DiscoveryClient却能获取到服务节点。要解开这个疑惑我们需要了解Kubernetes,以及了解Spring Cloud Kubernetes Discovery的源码。

前面我们在分析Ribbon的源码时也了解到,Ribbon并非通过DiscoveryClient去获取服务提供者的。

Ribbon通过提供一个ServerList接口让使用者自己去实现来完成Ribbon的服务发现。Ribbon定时调用ServerList更新自身缓存的服务提供者列表,默认30秒更新一次。

Spring Cloud Kubernetes Ribbon的作用就是实现Ribbon的ServerList接口,从Kubernetes获取可用的服务提供者。

实际上,Spring Cloud Kubernetes Ribbon也并未使用到Spring Cloud Kubernetes Discovery提供的DiscoveryClient接口的实现来获取服务列表,而是直接调用接口从Kubernetes中获取。

正是因为如此,笔者尝试去掉@EnableDiscoveryClient注解后,以及去掉Spring Cloud Kubernetes Discovery的依赖后,项目依然能正常运作。

需要注意,Spring Cloud Kubernetes Ribbon依赖Spring Cloud Kubernetes Core,如果去掉Spring Cloud Kubernetes Discovery,可能就要自动手动添加Spring Cloud Kubernetes Core的依赖,否则服务启动失败。

Spring Cloud Kubernetes Discovery实现DiscoveryClient接口只是能够让我们通过DiscoveryClient获取服务提供者。

SpringCloud Kubernetes Discovery实现的服务注册接口也并未真正的去注册服务。

可以这么说,在Spring Cloud Kubernetes项目中,Spring Cloud Kubernetes Discovery是一个多余的存在。

如果去掉Spring Cloud Kubernetes Discovery后,我们想要获取某个服务的当前可用服务提供者怎么获取呢?

我们可以通过使用Ribbon的ServerList去获取,由Spring Cloud Kubernetes Ribbon实现。

本地使用kubectl proxy

本地使用kubectl proxy命令就会运行一个Kubernetes API代理服务。

例如:

$ kubectl proxy --port=8004Starting to serve on 127.0.0.1:8004

使用kubectl proxy —port=8004开启Kubernetes API代理服务,监听请求的端口为8004。代理服务启动成功后,我们就可以使用127.0.0.1:8004访问Kubernetes的API了。

例如,获取sck-demo-provider这个服务的所有endpoints,在浏览器输入:

http://127.0.0.1:8004/api/v1/namespaces/default/endpoints/sck-demo-provider

其中default为名称空间,sck-demo-provider为服务名。响应结果如下图所示。

以上参考:https://blog.51cto.com/u_15064638/2871844

四:客户端的服务发现源代码分析

好了,通用的源码已经分析完了,现在我们正式分析应用的具体依赖源码。

k8s client 0.21 依赖的 ribbon-loadbanlancer 版本是2.2.5,然后具体触发这个更新server list列表的是 PollingServerListUpdater类,

注意,ribbon这里只管调度,不管具体实现,具体实现继续看!

如截图所示,具体实现是k8s client服务发现的依赖包。

再接着看!

接着实现http调用的是 io.fabric8.kubernetes.client 的 client.newCall(request).execute() 方法。

再再接着看!

最底层实现调用的是okhttp3工具!目前k8s 服务发现对应的okhttp3依赖版本是3.8.1 版本!

本质原因找到了,是这个版本的execute出问题了,卡住了,导致ribbon的更新任务对于的线程假死了。

最后我们来看看,k8s 服务发现 1.1.6版本对应的okhttp3版本是多少。

        <!-- 尝试解决更新列表卡主问题 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.9</version>
        </dependency>

然后其对应的http调用执行有啥不一样

多了 transmitter.timeoutEnter() 这个调用钩子,能够有效的修复卡死bug!

查看Okhttp源码时,在Transmitter类中发现了一个AsyncTimeout对象。了解代码后得知,该类是用于做一些超时检测的操作。

/**
 * This timeout uses a background thread to take action exactly when the timeout occurs. Use this to
 * implement timeouts where they aren't supported natively, such as to sockets that are blocked on
 * writing.
 *
 * <p>Subclasses should override {@link #timedOut} to take action when a timeout occurs. This method
 * will be invoked by the shared watchdog thread so it should not do any long-running operations.
 * Otherwise we risk starving other timeouts from being triggered.
 *
 * <p>Use {@link #sink} and {@link #source} to apply this timeout to a stream. The returned value
 * will apply the timeout to each operation on the wrapped stream.
 *
 * <p>Callers should call {@link #enter} before doing work that is subject to timeouts, and {@link
 * #exit} afterwards. The return value of {@link #exit} indicates whether a timeout was triggered.
 * Note that the call to {@link #timedOut} is asynchronous, and may be called after {@link #exit}.
 */publicclassAsyncTimeoutextendsTimeout{

这里提供了几个有用的信息:

  • 这是一个利用统一子线程检测超时的工具,主要针对的是一些原生不支持超时检测的类。

  • 它提供了一个timedOut()方法,作为检测到超时的回调

  • 内部提供的sink()source()方法可以适配流的读写超时检测,这可以对应到网络请求的流读写,后面会讲到。

  • 提供enter()exit()作为开始计时和结束计时的调用。也就是说开始执行计时的起点将会在enter()发生

详情见: https://juejin.cn/post/6962464239864774664

验证


如截图所示,超时按约定被捕获了。

总结


这个服务列表更新的异常有3种处理方式

1. 在ribbon调度这块加一层线程卡死监控,如果线程长时间处理Thread.State.WAITING.name()状态的,定时强制唤醒!(感谢东平的早期方向引导)


2. 在spring-cloud-kubernetes-ribbon执行更新的地方,额外加一个监控钩子,处理方式跟上面一致。

3. 其它依赖不变的情况下,直接升级okhttp3版本,升到3.14.9以上

        <!-- 尝试解决更新列表卡主问题 -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.14.9</version>
        </dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/384383.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

依赖倒置DIP在系统架构中的应用

最近在对项目中的某一模块进行重构和功能的拓展。一直没想到好方法。 简单理解为&#xff1a; R项目 调用了 E项目的打印接口&#xff0c;但是E项目需要对R传来对数据传输对象DTO进行二次处理&#xff0c;甚至夹杂很多R项目的业务逻辑&#xff08;去调用R项目的接口&#xff0…

代码规范书写说明

目录 一&#xff0c;命名风格 二、常量定义 三、代码格式 一&#xff0c;命名风格 &#xff08;1&#xff09;、不能够以下划线或者美元符号开始&#xff0c;也不能以下划线或者美元符号结束 反例&#xff1a;_name / __name / $name / name_ &#xff08;2&#xff09;、所…

春招进行时:“211文科硕士吐槽工资5500” HR:行情和能力决定价值

学历重要&#xff0c;还是能力重要&#xff1f; 春招进行时&#xff0c;不少学生求职遇冷&#xff0c;会把原因归结为学历水平不够高、毕业院校不够档次、专业不够热门、非一线城市就业机会少等等。 直到上海一位211大学的文科男硕士&#xff0c;吐槽招聘会提供的岗位薪资待遇…

10个实用技巧:如何让你的外贸独立站排名直线上升

在当今竞争激烈的互联网市场中&#xff0c;谷歌SEO已经成为了外贸独立站排名提升的必修课程。为了使得自己的网站能够在谷歌上排名更高&#xff0c;网站优化的工作显得尤为重要。 在这篇文章中&#xff0c;我们将分享10个实用技巧&#xff0c;帮助你的外贸独立站排名直线上升。…

【软件测试】接口测试总结

本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1…

java虚拟机栈解读

虚拟机栈出现的背景 由于跨平台性的设计&#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同&#xff0c;所以不能设计为基于寄存器的。 优点是跨平台&#xff0c;指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&#xff0c;实现同样的功能需要更…

【微信小程序-原生开发】实用教程11 - 用户登录鉴权(含云函数的创建、删除、使用,通过云函数获取用户的openid)

此篇可在实用教程10&#xff08;见下方链接&#xff09;的基础上继续开发&#xff0c;也可以在任何微信小程序中直接使用。 https://blog.csdn.net/weixin_41192489/article/details/128835069 用户登录鉴权逻辑 核心技术&#xff1a;通过云函数获取用户的openid 要想使用云函数…

网络层IP协议与数据链路层以太网协议

文章目录一、IP协议IP地址地址管理路由选择DNS二、以太网协议以太网帧MTU一、IP协议 IP协议是我们网络层的代表协议&#xff0c;今天我们就来一起学习一下吧&#xff0c;我们这里介绍的主要是IPv4协议。 版本&#xff1a;指定IP协议的版本&#xff0c;版本的取值只有4&#x…

如何使用ExchangeFinder在给定域中寻找Microsoft Exchange实例

关于ExchangeFinder ExchangeFinder是一款功能强大且使用简单的开源工具&#xff0c;该工具能够在给定域中尝试搜索指定的Microsoft Exchange实例&#xff0c;该工具的搜索机制基于Microsoft Exchange的常见DNS名称实现&#xff0c;并且能够识别指定的Microsoft Exchange版本&…

java基础之异常总结(自问自答版本)

1.errors和exception的区别是什么&#xff1f; 二者都是JAVA异常处理的重要子类&#xff0c;各自都包含大量子类 区别: exception:程序本身可以处理的异常&#xff0c;可以通过catch来进行捕获&#xff0c;遇到这种错误&#xff0c;应对其进行处理&#xff0c;使应用程序可以继…

大数据技术之Canal入门篇

大数据技术之Canal入门篇 文章目录大数据技术之Canal入门篇写在前面第 1 章 Canal 入门1.1 什么是 Canal1.2 MySQL 的Binlog1.2.1 什么是 Binlog1.2.2 Binlog 的分类1.3 Canal 的工作原理1.3.1 MySQL 主从复制过程1.3.2 Canal 的工作原理1.4 使用场景第 2 章 MySQL 的准备2.1 创…

作为产品经理,你都是怎样思考问题的?

作为产品经理&#xff0c;我们既不是产品的业务员更不是原型画师&#xff0c;而是伟大的创造者。用户对一个产品的评价可能只有好与坏&#xff0c;而作为产品工作者的我们必须有自己思考产品的视角&#xff0c;透过表现洞察本质。笔者根据自己有限的用户研究与产品设计的工作经…

leetcode_回溯算法

回溯算法刷题总结回溯法理论基础回溯算法的模板组合问题77.组合优化版本216.组合总和III17.电话号码的字母组合组合总和组合总和II分割131.分割回文串93.复原IP地址子集78.子集90.子集II491.递增子序列&#xff08;和子集问题很像&#xff09;排列全排列全排列II其他问题332.重…

RK3568平台开发系列讲解(Linux系统篇)伪文件系统目录详细介绍

🚀返回专栏总目录 文章目录 一、procfs文件系统二、sysfs文件系统沉淀、分享、成长,让自己和他人都能有所收获!😄 📢除了专门用于存储设备记录文件的文 件系统外,Linux 内核还提供了procfs、sysfs 等伪文件系统。 伪文件系统存在于内存中,通常不占用硬盘空间,它以文…

QML 键盘事件

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 和鼠标一样,键盘同样也提供了用户交互的能力,所以在介绍完《QML 鼠标事件》之后,是时候深入键盘事件了。 在 QML 中,有一个附加属性 - Keys,是专供可视元素进行按键处理的。当用户按下或释放一个按键时…

DDD:统一语言

目录一、统一语言的作用阐述二、统一语言与领域分析2.1、统一的领域术语2.2、统一的领域行为描述三、统一语言落地执行一、统一语言的作用阐述 【统一语言】&#xff0c;怎么强调都不为过&#xff01;&#xff01; 日常沟通中&#xff0c;时常会出现这么一幕&#xff1a;A同学…

第四章 reactive对象的简单实现以及reactive的依赖收集和触发依赖

reactive对象的简单实现 主要通过reactive.spec.ts这个测试案例来实现功能 import { reactive } from "../reactive"describe(reactive,()>{it(happy path,()>{const original {foo:1}const observed reactive(original)expect(observed).not.toBe(origina…

Unity常见面试题详解(持续更新...)

一丶声明、定义、实例化、初始化 1、首先我们来讨论在C/C中的声明和定义.. 1&#xff09;我们先从函数声明和定义说起... 一般我们在C里都会先定义一个函数&#xff0c;然后再Main函数前将函数声明&#xff0c;比如&#xff1a; //函数声明 int Add(int);int Main {} //函数…

tmux终端复用软件

一、安装[rootpool-100-1-1-159 test]# yum install tmux [rootpool-100-1-1-159 test]# yum search tmux Repository extras is listed more than once in the configuration Last metadata expiration check: 0:33:52 ago on Fri 03 Mar 2023 09:10:34 AM CST.Name Exactly M…

【C++】适配器模式 -- stack/queue/dqueue

一、适配器模式 设计模式 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结&#xff1b;Java 语言非常关注设计模式&#xff0c;而 C 并没有太关注&#xff0c;但是一些常见的设计模式我们还是要学习。 迭代器模式 其实我们在前面学习 strin…