這個註冊的 IP 網路都不通了,Eureka 註冊中心竟然無法踢掉它!

  • 2019 年 10 月 3 日
  • 筆記

本文導讀:

  • 微服務技術架構選型介紹
  • k8s 容器化部署架構方案
  • Eureka 註冊中心問題場景
  • 問題解決手段及原理剖析

閱讀本文建議先了解:

  1. 註冊中心基本原理
  2. K8s(Kuberneters)基本概念

我們的微服務目前都是在伺服器上部署的,也是基於 Docker 來部署的。

運維部門基於 K8s 自研了一套容器雲管理平台,平台名稱叫做 Ares,我們也開始準備將微服務遷移到這平台上,降低虛擬機或實體機伺服器運維成本,提高伺服器資源利用效率。

Ares:阿瑞斯(戰神)

希臘神話中為戰爭而生的神,奧林匹斯十二神之一,被視為尚武精神的化身。看起來很牛逼的樣子!

file

1、微服務技術架構選型介紹

微服務框架使用了流行的 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 網關作為服務消費者。

2、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註冊中心,也部署在了容器平台中,接下來會說下,因此導致的一些問題,以及解決該問題的方式。

3、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");      }  }
4、問題解決手段及原理剖析

既然引出了上述問題,當然不能放任不管,一定要一探究竟。
這種問題你若不理他,早晚會搞出點別的事情來的。

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爱好者社区

Tags: