这个注册的 IP 网络都不通了,Eureka 注册中心居然没法踢掉它!

本文导读:html

  • 微服务技术架构选型介绍
  • k8s 容器化部署架构方案
  • Eureka 注册中心问题场景
  • 问题解决手段及原理剖析

阅读本文建议先了解:git

  1. 注册中心基本原理
  2. K8s(Kuberneters)基本概念

咱们的微服务目前都是在服务器上部署的,也是基于 Docker 来部署的。github

运维部门基于 K8s 自研了一套容器云管理平台,平台名称叫作 Ares,咱们也开始准备将微服务迁移到这平台上,下降虚拟机或实体机服务器运维成本,提升服务器资源利用效率。express

Ares:阿瑞斯(战神)后端

希腊神话中为战争而生的神,奥林匹斯十二神之一,被视为尚武精神的化身。看起来很牛逼的样子!bash

file

一、微服务技术架构选型介绍

微服务框架使用了流行的 Spring Cloud 框架。服务器

框架技术组件以下:网络

  • 注册中心选择的是 Eureka
  • 网关使用的 Zuul
  • 配置中心使用的 Apollo
  • 熔断限流使用了 Sentienl + Hystrix

网关 Zuul 层使用了 Ribbon 作负载均衡、Hystrix 作限流熔断。后端微服务使用了阿里巴巴开源的 Sentinel 作限流熔断。数据结构

因为当时服务器的配置不一样,好比有低配置的虚拟机,还有高配置的物理机服务器。架构

因此呢,咱们基于当时的服务器配置现状,基于 Ribbon 自行扩展了按照权重的负载均衡策略,对 Eureka 注册中心管理界面作了一点改造,可以支持动态对每台服务器变动权重。

由于本文的问题跟 Eureka 注册中心有关,对 Eureka 架构作个介绍下。

Eureka 注册中心简易架构图:file

上图简要描述了 Eureka 的基本架构,由3个角色组成:

1)Eureka Server

提供服务注册、发现、健康检查。

2)Service Provider

服务提供方,将自身服务注册到 Eureka,从而使服务消费方可以找到,咱们将容器能够做为服务提供者,会注册到 Eureka。

3)Service Consumer

服务消费方,从Eureka获取注册服务列表,从而可以消费服务咱们能够将 Zuul 网关做为服务消费者。

二、K8s 容器化部署架构方案

考虑到使用的 Spring Cloud 框架,结合运维提供的容器平台。

制定容器化部署架构以下:file

从容器建立到可访问流程:

1)建立容器

选择镜像及版本、CPU、内存配置、配置健康检查、日志收集、Pod 副本数量。提交建立容器。

2)服务注册

容器启动时,申请 SLB VIP,做为服务注册 IP,向 Eureka 上发起注册。

3)网关发起请求

域名请求,DNS 解析通过 GSLB(全局软负载均衡)负载到 Zuul 网关,Zuul 网关从 Eureka 注册中心拉取服务注册表,经过 Ribbon 负载均衡,从本地服务注册列表中,选择其中一台 Server,发起 Http 调用。

4)容器提供服务

容器内注册的是 SLB VIP(软负载均衡),这个 SLB 经过内部的 Nginx 负载均衡机制,轮询到后端的容器的多个 Pod IP 上,Pod IP 正是咱们部署的微服务业务。

为何要使用SLB VIP呢?

当时咱们对接口压测时,发现使用 K8s 内部的 Service IP 存在性能瓶颈,该问题还在研究中。后来运维内部商榷,使用 SLB 来达到负载均衡的效果。

另外说明一点:运维基于 K8s 自研的这套容器平台,网络层面作了从新架设和优化,打通了各个机房的网络。

这样作给咱们的架构部署带来了好处:前期目标仅为了迁移微服务业务,考虑到稳定性等因素,正式上线的Zuul网关和Eureka 注册中心部署在 K8s 集群外,微服务业务部署在容器内,因网络可通,容器启动后申请的 VIP,能够直接注册到 Eureka 上。

仿真环境(预上线环境)是直接将Eureka注册中心,也部署在了容器平台中,接下来会说下,所以致使的一些问题,以及解决该问题的方式。

三、Eureka 注册中心问题场景

容器测试阶段结束,因为运维调整为了 SLB VIP,将之前的应用(一个应用下包含多个 Pod 容器)都删除掉,咱们从新搭建一套仿真环境用于,上线前的性能测试环境。

可是当咱们部署完 Eureka 后,发现之前删除掉的应用VIP 也注册上来了,并且这个 VIP 网络是不通的,没法访问的。

Eureka 管理控制台示意图:file

telnet 命令测试:

telnet 10.11.195.197 80 
Trying 10.11.195.197...
telnet: connect to address 10.11.195.197: Network is unreachable
telnet: Unable to connect to remote host复制代码

结果提示 10.11.195.197 这个 VIP,网络是不可达的。

那为何这个服务能注册上来呢?

起初,跟运维老哥请教,通过容器内排查后,也暂时没有太多眉目,肯定是这个 VIP,已经下线了,网络也不通。

按照这个推测,是不太可能注册到 Eureka 上来的。

开始考虑到觉得是 Eureka 机制是否是有问题,但仔细用「屁股」猜测论思考一下,结合 Eureka 框架底层原理来看,是不该该出现这个状况。

根据 Eureka 续约机制,必定是有哪一个「哥们」在默默给这个服务 IP 发续约(向注册中心发送心跳)。

咱们在 Eureka Server 服务端,也有监听各个动做的机制,如注册服务、续约服务、下线服务,根据日志看,也的确是有这个服务 IP 一直在发送续约动做。

续约监听代码:

@EventListener
public void listen(EurekaInstanceRenewedEvent event) {
    InstanceInfo instanceInfo = event.getInstanceInfo();
    if (instanceInfo != null) {
        logger.info("renew ...." + instanceInfo.getInstanceId());
    } else {
        logger.info("renew ....instanceInfo is null");
    }
}复制代码

四、问题解决手段及原理剖析

既然引出了上述问题,固然不能听任无论,必定要一探究竟。这种问题你若不理他,迟早会搞出点别的事情来的。

Eureka 服务端已经收到了注册和一直续约的请求,说明必定是有哪一个服务一直在偷偷发送心跳。

究竟是谁干的啊?file

到底如何找到这个上报的服务呢?

运维老哥暂时比较忙,看来只能先查找网络链路,抓取网络数据包看看究竟是怎么回事了。

网络工具通常经常使用的就是 tcpdump、Wireshark。

Wireshark 小故事:

大概发生在 10 几年前,主导 Ethereal(应该据说过吧)的大佬跳槽了,而后这个商标就不能继续使用了,可是这个工具在当时来讲人气很旺,后来大佬就将项目改名为 Wireshark 了。

服务器上命令行的抓包程序 tethereal 改名为了 tshark。

容器镜像中默认是不会自带这些工具的。镜像中 Linux 操做系统使用的是 CentOS,经过自带的 yum 源安装网络工具包,比较方便。

安装 wireshark:

`yum install -y wireshark`

安装 tcpdump:

`yum install -y tcpdump`

这里咱们使用的是 Wireshark 工具,简单介绍下这个工具:

若是你要看到所有网络数据包,直接执行tshark命令便可。

1)得到 tshark 命令帮助

`tshark --help`

2)tshark 抓包模式参数一览

file

3)tshark 命令实战使用

打印源目标 Host 及 Http 协议信息:

`tshark -s 512 -i eth0 -n -f 'tcp dst port 80' -t ad -R 'http.host and http.request.uri' -T fields -e "frame.time" -e "ip.src" -e "http.host" -e "http.request.method" -e "http.request.uri" | tr -d ' '`

参数解释:

-i 捕获 eth0 网卡;

-n 禁止全部地址名字解析(默认为容许全部)

-t 设置解码结果的时间格式。

"ad"表示带日期的绝对时间,"a"表示不带日期的绝对时间,"r"表示从第一个包到如今的相对时间,“d”表示两个相邻包之间的增量时间(delta)

-R 设置读取(显示)过滤表达式(read filter expression)

-T -e 输出指定的字段

执行结果:file

来段文本:

[root@mas-manager-eureka-es1-66cb79bfb7-snmxm manager]# tshark -n -t a -R http.request -T fields -e "frame.time" -e "ip.src" -e "http.host" -e "http.request.method" -e "http.request.uri" | grep 10.11
Running as user "root" and group "root". This could be dangerous.
Capturing on eth0
Sep 27, 2019 00:22:05.174770971 10.124.12.169   10.124.14.4     PUT     /eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569490783397
Sep 27, 2019 00:22:13.814821143 10.124.11.125   10.124.14.4     PUT     /eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569407741389
Sep 27, 2019 00:22:15.180243816 10.124.11.123   10.124.14.4     PUT     /eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569490783397复制代码

经过抓包,根据问题 IP 过滤获得的结果,咱们看到了/eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569490783397说明是有 IP 在上报,上报来源 IP 就是第二列的 IP。

将这个信息提供给运维同窗,根据来源 IP 去继续查找线索。

可是,发现第二列 IP 并非实际的服务器节点 IP,查不到。由于这些 IP 都是主机上的虚拟 IP,每次上报来源 IP 不一样,因此还要反向查找实际归属的主机节点。

为何要有虚拟 IP?

实际是 SLB 节点上虚拟 IP,由于会负载到它所属主机节点,这台主机上默认只能支撑最大 65535 个 TCP 链接,因此为了单机能支撑更高的 TCP 链接数,会虚拟出来不少个 IP。假设有 10 个虚拟 IP,每一个虚拟 IP 支撑 65535 个 TCP 链接,这台主机总共能够支撑 10 * 65535 = 60万以上的链接数了。

K8s 有多套集群,每一个集群中有不少台主机节点,茫茫主机池中怎么去查找这些虚拟 IP 呢,

其实咱们的目的是为了找到,谁往注册中心发送请求了,仍是能够继续经过抓取网络数据包来定位这个问题。

这些网络数据包必定会通过 K8s 集群里的某一台节点,一台一台去找,很麻烦,编写简单脚本抓包查找

#!/bin/bash
tcpdump -i any host 10.124.14.4 -n -s 0 -X -l | grep 10.11.195>/tmp/1.txt &
sleep 20s
kill -2 %1
cat /tmp/1.txt复制代码

网络抓包结果:file

截取了关键的抓包信息:

11:41:56.598204 IP 10.110.157.81.54078 > 10.124.14.4.http: Flags [P.], seq 273:622, ack 218, win 245, options [nop,nop,TS val 3348483954 ecr 1420800289], length 349: HTTP: PUT /eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569394834392 HTTP/1.1复制代码

这里的 10.110.157.81.54078 就是主机节点 IP,目标地址 10.124.14.4 就是容器内的 Eureka 注册中心地址。

发送的请求是 /eureka/apps/LETV-MAS-CALLER-TVPROXY-USER/10.11.195.197:80?status=UP&lastDirtyTimestamp=1569394834392这个请求地址上就带了 10.11.195.197 这个网络不可达的 IP 地址。

到这里,其实咱们已经基本定位到了,必定是从 K8s 集群容器内发出来了。根据这个有价值的节点信息,链接到 K8s 集群内,查找该节点上部署的容器。

查找 K8s 集群内 Pod 命令行:`kubectl get pod --all-namespaces -o wide |grep 10.110.157.81`

部署在改节点上的容器:file

运维根据 Eureka 上名称大概猜想一下,终于找到这个「罪魁祸首」的容器了。进入容器内,查看配置 SVIP (Eureka上的注册IP)就是 10.11.195.197 这个IP。

将这个问题容器完全关闭后,没有再继续发送续约请求,Eureka 注册中心上过了一段时间摘掉了 IP。

你们可能有疑问,这么繁琐,为啥不直接到 K8s 集群内去找,由于 K8s 集群内目前已有业务在运行着,集群内有几百个容器在跑着。当时运维一块儿测试时,容器名称都是自定义的,因此不是很好查找。

我们通过这个过程的排查,确认了这个 Eureka 注册中心上的地址虽然不通,可是一直是有容器在上报,而上报的「ServerId」指向的 10.11.195.197:80 地址。

咱们也能够结合底层源码了解下。

Eureka 续约时序图:file

接口实现方式跟注册服务相似,更新自身状态后,会同步到其余集群节点。

PeerAwareInstanceRegistryImpl 类的 renew 方法会调用到 AbstractInstaceRegistry 抽象实例注册类的 renew 方法。

AbstractInstaceRegistry#renew 方法源码:file

会根据服务 id 从注册表中获取 Lease 对象,若是不为空,则完成续约,更新 lastUpdateTimestamp 字段。

file

Eureka 注册表的数据结构:

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();复制代码

是一个 ConcurrentHashMap 结构,Key 就是应用名称,Value 也是一个 Map 结构。Map 结构中的 Key 是注册ID(IP + 端口),Value 是 Lease 服务续约对象,里面包含了动做类型,最后上报(心跳)更新时间戳等等信息。

容器服务做为 Eureka Client,每隔必定时间间隔(默认60秒)向注册中心发起一次续约。

Eureka Server 会定时检测服务实例心跳是否正常,若是间隔必定时间(90秒),尚未来续约,就会将这个服务从注册中心摘除掉。

最后总结:

总结上述分析过程,一图胜千言:file

重要的不是结果,而是这个过程,但愿你也能享受这个过程。

参考资料:

Wireshark 使用文档:https://www.wireshark.org/docs/man-pages/tshark.html

Netflix Eureka 源代码:https://github.com/netflix/eureka

欢迎关注个人公众号,扫二维码关注得到更多精彩文章,与你一同成长~Java爱好者社区

相关文章
相关标签/搜索