本文導讀: 微服務技術架構選型介紹 k8s 容器化部署架構方案 Eureka 註冊中心問題場景 問題解決手段及原理剖析 閱讀本文建議先瞭解: 1. 註冊中心基本原理 2. K8s(Kuberneters)基本概念 我們的微服務目前都是在伺服器上部署的,也是基於 Docker 來部署的。 運維部門基於 ...
本文導讀:
- 微服務技術架構選型介紹
- k8s 容器化部署架構方案
- Eureka 註冊中心問題場景
- 問題解決手段及原理剖析
閱讀本文建議先瞭解:
- 註冊中心基本原理
- K8s(Kuberneters)基本概念
我們的微服務目前都是在伺服器上部署的,也是基於 Docker 來部署的。
運維部門基於 K8s 自研了一套容器雲管理平臺,平臺名稱叫做 Ares,我們也開始準備將微服務遷移到這平臺上,降低虛擬機或實體機伺服器運維成本,提高伺服器資源利用效率。
Ares:阿瑞斯(戰神)
希臘神話中為戰爭而生的神,奧林匹斯十二神之一,被視為尚武精神的化身。看起來很牛逼的樣子!
1、微服務技術架構選型介紹
微服務框架使用了流行的 Spring Cloud 框架。
框架技術組件如下:
- 註冊中心選擇的是 Eureka
- 網關使用的 Zuul
- 配置中心使用的 Apollo
- 熔斷限流使用了 Sentienl + Hystrix
網關 Zuul 層使用了 Ribbon 做負載均衡、Hystrix 做限流熔斷。
後端微服務使用了阿裡巴巴開源的 Sentinel 做限流熔斷。
由於當時伺服器的配置不同,比如有低配置的虛擬機,還有高配置的物理機伺服器。
所以呢,我們基於當時的伺服器配置現狀,基於 Ribbon 自行擴展了按照權重的負載均衡策略,對 Eureka 註冊中心管理界面做了一點改造,能夠支持動態對每台伺服器變更權重。
因為本文的問題跟 Eureka 註冊中心有關,對 Eureka 架構做個介紹下。
Eureka 註冊中心簡易架構圖:
上圖簡要描述了 Eureka 的基本架構,由3個角色組成:
1)Eureka Server
提供服務註冊、發現、健康檢查。
2)Service Provider
服務提供方,
將自身服務註冊到 Eureka,從而使服務消費方能夠找到,
我們將容器可以作為服務提供者,會註冊到 Eureka。
3)Service Consumer
服務消費方,
從Eureka獲取註冊服務列表,從而能夠消費服務
我們可以將 Zuul 網關作為服務消費者。
2、K8s 容器化部署架構方案
考慮到使用的 Spring Cloud 框架,結合運維提供的容器平臺。
制定容器化部署架構如下:
從容器創建到可訪問流程:
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 管理控制台示意圖:
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 服務端已經收到了註冊和一直續約的請求,說明一定是有哪個服務一直在偷偷發送心跳。
到底是誰乾的啊?
到底如何找到這個上報的服務呢?
運維老哥暫時比較忙,看來只能先查找網路鏈路,抓取網路數據包看看到底是怎麼回事了。
網路工具一般常用的就是 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 抓包模式參數一覽
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 輸出指定的欄位
執行結果:
來段文本:
[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
網路抓包結果:
截取了關鍵的抓包信息:
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
部署在改節點上的容器:
運維根據 Eureka 上名稱大概猜測一下,終於找到這個「罪魁禍首」的容器了。
進入容器內,查看配置 SVIP (Eureka上的註冊IP)就是 10.11.195.197 這個IP。
將這個問題容器徹底關閉後,沒有再繼續發送續約請求,Eureka 註冊中心上過了一段時間摘掉了 IP。
大家可能有疑問,這麼繁瑣,為啥不直接到 K8s 集群內去找,因為 K8s 集群內目前已有業務在運行著,集群內有幾百個容器在跑著。當時運維一起測試時,容器名稱都是自定義的,所以不是很好查找。
咱們經過這個過程的排查,確認了這個 Eureka 註冊中心上的地址雖然不通,但是一直是有容器在上報,而上報的「ServerId」指向的 10.11.195.197:80 地址。
我們也可以結合底層源碼瞭解下。
Eureka 續約時序圖:
介面實現方式跟註冊服務類似,更新自身狀態後,會同步到其他集群節點。
PeerAwareInstanceRegistryImpl 類的 renew 方法會調用到 AbstractInstaceRegistry 抽象實例註冊類的 renew 方法。
AbstractInstaceRegistry#renew 方法源碼:
會根據服務 id 從註冊表中獲取 Lease 對象,如果不為空,則完成續約,更新 lastUpdateTimestamp 欄位。
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秒),還沒有來續約,就會將這個服務從註冊中心摘除掉。
最後總結:
總結上述分析過程,一圖勝千言:
重要的不是結果,而是這個過程,希望你也能享受這個過程。
參考資料:
Wireshark 使用文檔:
https://www.wireshark.org/docs/man-pages/tshark.html
Netflix Eureka 源代碼:
https://github.com/netflix/eureka
歡迎關註我的公眾號,更多精彩文章,與你一同成長,掃碼二維碼關註~