盤)
凌晨兩點(diǎn)四十手機(jī)連著震了十幾次我爬起來看了一眼告警群線上服務(wù)成功率掉到65%響應(yīng)時間已經(jīng)逼近十秒持續(xù)了快二十分鐘。我第一反應(yīng)不是打開日志追堆棧而是先問了句自己——現(xiàn)在進(jìn)來的流量到底是從哪個IP來的這個習(xí)慣是我踩過好幾次坑之后才養(yǎng)成的。很多人遇到線上故障下意識就沖進(jìn)應(yīng)用日志里找exception找來找去半天最后發(fā)現(xiàn)根本不是代碼問題。IP地址聽起來是個再基礎(chǔ)不過的網(wǎng)絡(luò)概念但在關(guān)鍵時刻它往往是最先開口說話的“目擊證人”。這篇復(fù)盤我想完整記錄一下Zabbix/Nginx日志、ss/netstat連接狀態(tài)、whois歸屬查詢這些手段是如何一環(huán)扣一環(huán)在一個看似莫名其妙的線上故障里把真正的根因給揪出來的。適合剛好遇到“服務(wù)突然不可用、成功率莫名下跌、重啟也解決不了”這類問題的人看也適合剛接觸運(yùn)維、后端開發(fā)想建立故障排查方法論的同學(xué)參考。我不打算上來就告訴你答案而是把當(dāng)時的排查路徑原封不動復(fù)現(xiàn)一遍包括中間走錯的方向和最后反轉(zhuǎn)的瞬間。1. 故障現(xiàn)象與第一輪排查1.1 告警長什么樣從監(jiān)控指標(biāo)說起這次故障的業(yè)務(wù)場景不算復(fù)雜一個對外提供API的服務(wù)前邊是Nginx做負(fù)載均衡后邊掛著四臺應(yīng)用服務(wù)器。監(jiān)控看板上的信號非常明確API成功率從99.9%開始往下掉最低掉到62%左右平均響應(yīng)時間從80ms直接飆到9.8s錯誤碼集中在502和504。但有一個細(xì)節(jié)很有意思四臺應(yīng)用服務(wù)器里的三臺負(fù)載指標(biāo)完全正常CPU、內(nèi)存、流量都沒有明顯波動只有一臺機(jī)器的網(wǎng)絡(luò)連接數(shù)異常地高。我當(dāng)時的第一判斷跟大多數(shù)人一樣肯定又是哪次發(fā)布有問題。于是先去查了最近的發(fā)布記錄結(jié)果顯示沒有任何新代碼上線也沒有配置變更。這就有點(diǎn)意思了沒有發(fā)布沒有變更服務(wù)卻大面積超時和連接失敗說明問題大概率不在應(yīng)用代碼本身而是在請求鏈路某個環(huán)節(jié)上。這個時候如果繼續(xù)盯著日志翻堆棧大概率是浪費(fèi)時間更好的做法是把視角收回來從連接層和網(wǎng)絡(luò)層開始問問題請求從哪來經(jīng)過了哪些IP到了哪臺機(jī)器1.2 我第一反應(yīng)之前踩過的坑說實(shí)話以前遇到線上故障我的第一反應(yīng)也是開日志、看堆棧、搜關(guān)鍵字結(jié)果有一次查了三個小時最后發(fā)現(xiàn)是上游服務(wù)把請求打到了一個已經(jīng)被摘除的節(jié)點(diǎn)上日志里什么異常都沒有只有一堆連接超時。那次之后我給自己定了一條規(guī)矩線上故障首選排查路徑永遠(yuǎn)是“流量入口 - 網(wǎng)絡(luò)鏈路 - 連接狀態(tài) - 應(yīng)用日志”順序不能亂。為什么先看鏈路和連接因?yàn)閼?yīng)用日志只能告訴你“我這臺機(jī)器內(nèi)部發(fā)生了什么”但它沒法告訴你“為什么請求會走到這臺機(jī)器”。而IP地址和端口狀態(tài)恰恰是串聯(lián)整條鏈路的線索。比如一個請求從客戶端到Nginx再到后端每一跳都會留下源IP和目標(biāo)IP的痕跡。只要把這條鏈路捋直了大部分“莫名其妙”的故障都能從鏈路中間找到異常點(diǎn)。反過來如果一上來就鉆到日志里很容易被錯誤日志的數(shù)量誤導(dǎo)以為系統(tǒng)哪里都壞了其實(shí)只是入口流量被導(dǎo)向了一個異常的目標(biāo)而已。2. 從域名到IP把訪問鏈路攤開來看2.1 從DNS解析開始IP地址的第一層身份排查的第一步我先確認(rèn)了入口域名的解析結(jié)果是否正確。我們的服務(wù)對外域名是 api.example.com正常情況下應(yīng)該解析到負(fù)載均衡的VIP虛擬IP再由VIP轉(zhuǎn)發(fā)給后端節(jié)點(diǎn)。我直接在跳板機(jī)上執(zhí)行了下面這組命令dig short api.example.com nslookup api.example.com解析結(jié)果返回了一個IP一看就是內(nèi)網(wǎng)地址10.20.30.4。這個IP是我們Nginx集群的VIP看起來沒什么異常。但緊接著我又做了一次測試從一臺公網(wǎng)測試機(jī)去解析同一個域名發(fā)現(xiàn)返回的結(jié)果竟然也是10.20.30.4這就有點(diǎn)不對了。我們的架構(gòu)里公網(wǎng)用戶訪問應(yīng)該先經(jīng)過云上的公網(wǎng)負(fù)載均衡再轉(zhuǎn)發(fā)到內(nèi)網(wǎng)的Nginx VIP正常情況下公網(wǎng)解析不會直接暴露內(nèi)網(wǎng)IP。也就是說要么是DNS配置被改過要么是某些調(diào)用方繞過了公網(wǎng)入口直接拿著內(nèi)網(wǎng)IP在訪問?,F(xiàn)在回看這個細(xì)節(jié)是整個排查路上第一個真正有價值的線索——IP地址在那一刻已經(jīng)不止是一個數(shù)字它在告訴我訪問鏈路的入口可能被“偷換”了。但因?yàn)楫?dāng)時線上告警還在持續(xù)我沒有在DNS上逗留太久只做了個臨時記錄就繼續(xù)往下追連接狀態(tài)了。2.2 本機(jī)連接狀態(tài)ss命令告訴我誰在連我第二步我登上了那臺連接數(shù)異常高的應(yīng)用服務(wù)器用ss命令看了一眼實(shí)時的網(wǎng)絡(luò)連接。說實(shí)話我之前一直用的是netstat后來發(fā)現(xiàn)ss在處理大量連接時速度更快輸出格式也更清晰在故障現(xiàn)場那種環(huán)境下快一秒是一秒。我執(zhí)行的是ss -antp | grep :8080 | awk {print $5} | cut -d: -f1 | sort | uniq -c | sort -nr | head -20這條命令的意思很簡單列出所有和目標(biāo)端口8080建立的TCP連接提取對端IP統(tǒng)計每個IP的連接數(shù)量從高到低排前20個。輸出結(jié)果里絕大多數(shù)來源IP都集中在幾個熟悉的網(wǎng)段但有一個IP非常扎眼192.168.7.19單獨(dú)占了將近四千個ESTABLISHED連接。一個正常業(yè)務(wù)調(diào)用方不可能同時和同一臺后端建立四千多個TCP長連接這明顯超出了合理范圍。我順手又看了下TIME_WAIT和SYN_SENT狀態(tài)的數(shù)量TIME_WAIT大概幾千個不算太離譜但SYN_SENT異常多達(dá)到兩百多個這說明這臺服務(wù)器在頻繁向外發(fā)起連接而且很多連不上。一個以接收請求為主的應(yīng)用服務(wù)器向外SYN_SENT數(shù)量激增通常意味著它在做某種回調(diào)、上報或者干脆是被某個惡意腳本當(dāng)成了“跳板”。到這里IP地址已經(jīng)從“一個冷冰冰的數(shù)字”變成了“一個有犯罪嫌疑的地址”。3. IP地址如何一步步交代自己的來源3.1 區(qū)分公網(wǎng)IP與內(nèi)網(wǎng)IP先給IP分類當(dāng)192.168.7.19這個IP出現(xiàn)后我先給IP做了一次歸屬判斷。這一步看起來基礎(chǔ)但真的會有人在這一步翻車。簡單說IPv4地址可以按用途分成幾個大類私有地址段包括10.0.0.0/8、172.16.0.0/12、192.168.0.0/16這些只能在局域網(wǎng)內(nèi)使用公網(wǎng)路由不可達(dá)剩下的才是公網(wǎng)地址需要向運(yùn)營商或云廠商申請后才能對外提供服務(wù)。我把常見地址段整理過一份速查表故障排查時直接用強(qiáng)烈建議人手一份地址段類型用途說明127.0.0.0/8回環(huán)地址本機(jī)自檢網(wǎng)絡(luò)調(diào)試常用10.0.0.0/8私有地址A類大型企業(yè)內(nèi)網(wǎng)、云上VPC容量大172.16.0.0/12私有地址B類企業(yè)內(nèi)部網(wǎng)、容器集群、K8s Service網(wǎng)段192.168.0.0/16私有地址C類辦公室網(wǎng)絡(luò)、家庭路由器、小型局域網(wǎng)169.254.0.0/16鏈路本地地址DHCP獲取失敗時自動分配出現(xiàn)這個多半網(wǎng)絡(luò)配置有問題其余IPv4地址公網(wǎng)地址需向運(yùn)營商或云廠商正式申請可全球路由192.168.7.19 屬于C類私有段毫無疑問是我們內(nèi)網(wǎng)里的某臺機(jī)器。但內(nèi)網(wǎng)私有IP到處都是光知道這個還不夠。我接著用whois和IP歸屬庫做了進(jìn)一步確認(rèn)結(jié)果發(fā)現(xiàn)它是VPC里一個不常用的網(wǎng)段對應(yīng)的是數(shù)據(jù)同步服務(wù)集群里的機(jī)器。你別小看這一步光是判斷“這個IP是不是自己的資產(chǎn)”就能省下大量時間不然你可能會跑到云上查一堆公網(wǎng)IP的歸屬查半天發(fā)現(xiàn)跟自家業(yè)務(wù)半毛錢關(guān)系沒有。3.2 從單個IP擴(kuò)大范圍用IP做流量聚簇單個IP異常不一定能說明問題所以我決定把范圍放大從這臺應(yīng)用服務(wù)器的整個訪問日志里把所有來源IP按連接數(shù)和請求數(shù)聚合了一遍。這一步的目的是看“異常流量到底是孤例還是有組織的一群IP”。我用了awk分析Nginx的access log命令大概是awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -30輸出很快出來了排在前面的不止192.168.7.19一個還有另外十幾個IP全部集中在192.168.7.0/24這個C段連接量從幾百到幾千不等。一個C段里的十幾臺機(jī)器在同一時間集中訪問同一臺后端而且訪問模式高度相似這不像正常的業(yè)務(wù)交織更像一個完整的子系統(tǒng)在向這臺機(jī)器瘋狂地“灌流量”。這時候IP地址的第二個價值體現(xiàn)出來了它能在不依賴應(yīng)用日志的前提下幫你把“肇事流量”畫出一個輪廓。我順藤摸瓜查了這些IP對應(yīng)的服務(wù)器業(yè)務(wù)角色發(fā)現(xiàn)它們都屬于一個批處理任務(wù)集群平時承擔(dān)數(shù)據(jù)清洗和同步工作但按架構(gòu)設(shè)計這個集群根本不應(yīng)該直接訪問8080端口。真正的異常點(diǎn)慢慢浮現(xiàn)了——流量來源確認(rèn)了接下來就該搞清楚它們?yōu)槭裁匆虻竭@里來。4. 找到IP后的一切從網(wǎng)絡(luò)層逼近應(yīng)用層4.1 用IP反向定位調(diào)用方查配置、對架構(gòu)追到192.168.7.0/24這個C段之后我反查了一遍這些機(jī)器上的應(yīng)用配置。在其中一個批處理節(jié)點(diǎn)的配置文件里我找到了一個讓人眼前一黑的操作它的任務(wù)調(diào)度地址寫的是一個舊的服務(wù)發(fā)現(xiàn)地址而這個舊地址的解析結(jié)果竟然指向了那臺連接數(shù)異常的應(yīng)用服務(wù)器。說白了就是批處理集群本來要調(diào)用數(shù)據(jù)中臺的服務(wù)但由于服務(wù)注冊中心里殘留了一條歷史健康檢查記錄它們拿到的服務(wù)實(shí)例IP是已經(jīng)“退役”的舊節(jié)點(diǎn)而舊節(jié)點(diǎn)的IP后來被重新分配給了現(xiàn)在這臺上線的應(yīng)用服務(wù)器。結(jié)果就是所有本該打到數(shù)據(jù)中臺的請求全被硬塞到了這臺應(yīng)用服務(wù)器上應(yīng)用根本沒有承載這個流量的能力連接全部堵塞進(jìn)而拖垮了整個服務(wù)。IP地址在這里起到了一個反向定位器和“記憶載體”的作用。它記錄下了服務(wù)注冊中心的歷史信息記錄下了網(wǎng)絡(luò)轉(zhuǎn)發(fā)路徑的錯位這些信息在應(yīng)用層日志里是完全看不出來的因?yàn)榻邮照埱蟮膽?yīng)用本身并沒有報錯它只是處理不過來。4.2 順著IP看端口、協(xié)議、進(jìn)程確認(rèn)了來源IP之后我又回頭補(bǔ)了一個關(guān)鍵操作在應(yīng)用服務(wù)器上用ss命令查看這些連接對應(yīng)的本地進(jìn)程確認(rèn)到底是哪個進(jìn)程在接收。我當(dāng)時用了這條命令ss -antp | grep 192.168.7.19 | head -5輸出里能看到本地端口8080對應(yīng)的進(jìn)程PID再用ps確認(rèn)進(jìn)程名結(jié)果發(fā)現(xiàn)接收連接的是一個我們內(nèi)部框架的HTTP服務(wù)進(jìn)程但它暴露的端口和配置文件里的端口并不完全一致。這個問題單看IP發(fā)現(xiàn)不了單看進(jìn)程也發(fā)現(xiàn)不了必須把“IP端口進(jìn)程”三個維度拼到一起才看得全。這一環(huán)節(jié)我最想強(qiáng)調(diào)的一點(diǎn)是不要只看IP是誰還要看IP和誰配對。一個IP可以對應(yīng)多個進(jìn)程一個進(jìn)程可以監(jiān)聽多個端口如果你只過濾IP不關(guān)聯(lián)端口和進(jìn)程最多只能找到“誰在訪問”找不到“訪問到了哪個應(yīng)用”。正確做法是先用ss -antp把IP、端口、進(jìn)程三元組全部拉出來再去對應(yīng)用配置任何一步漏了都容易誤判。4.3 真正的根因IP綁定導(dǎo)致流量誤入到這里我已經(jīng)能拼出完整故事了批處理集群通過服務(wù)注冊中心獲取目標(biāo)地址但注冊中心里一個過期實(shí)例注冊信息指向了一個已被回收的IP。這個IP現(xiàn)在是應(yīng)用服務(wù)器的地址于是請求全部誤入。更隱蔽的是由于應(yīng)用服務(wù)器上某個組件啟動時通過配置綁定了一個舊的虛擬IP導(dǎo)致部分返回包里源IP顯示成了另一個不相干的地址這讓前幾步的排查一度被帶偏。根因總結(jié)起來就是一句話IP地址管理混亂服務(wù)注冊信息沒有自動清理機(jī)制加上應(yīng)用啟動配置里寫死了一個廢舊IP。三件事單獨(dú)看都不致命串在一起就成了一個能讓人排查通宵的線上故障。修復(fù)動作其實(shí)不復(fù)雜摘除異常連接更正服務(wù)注冊中心的實(shí)例信息修改應(yīng)用啟動配置讓端口綁定從配置文件讀取而非硬編碼舊IP?;貪L之后流量立刻恢復(fù)成功率回到99.9%前后花了不到半小時。5. 常見問題與排查技巧實(shí)錄5.1 一套順手命令組合這次故障里用到的命令組合我后來整理成了一套固定的排查順序每次遇到類似問題就直接套用。先用dig確認(rèn)域名解析和安全入口然后用ss統(tǒng)計連接接著用awk分析日志最后用whois或IP歸屬庫確認(rèn)IP身份。# 第一步確認(rèn)DNS解析結(jié)果 dig short api.example.com # 第二步查看本機(jī)監(jiān)聽端口、連接數(shù)量統(tǒng)計 ss -lntp ss -antp | grep :8080 | awk {print $5} | cut -d: -f1 | sort | uniq -c | sort -nr # 第三步分析Nginx訪問日志按來源IP聚簇 awk {print $1} access.log | sort | uniq -c | sort -nr | head -30 # 第四步確認(rèn)IP歸屬whois查詢公網(wǎng)歸屬內(nèi)網(wǎng)IP查資產(chǎn)平臺 whois 8.8.8.8這套組合的核心思路是“由外而內(nèi)”先確定入口地址對不對再看連接進(jìn)來沒有然后看流量來自哪些IP最后才輪到看日志定位業(yè)務(wù)層問題。只要不跳步基本不會走太偏。5.2 三個容易看走眼的地方第一個容易看走眼的是NAT和代理。很多內(nèi)網(wǎng)請求經(jīng)過NAT網(wǎng)關(guān)后來源IP會統(tǒng)一變成網(wǎng)關(guān)出口IP如果你只看來源IP會以為是同一個客戶端在刷流量其實(shí)是幾百個客戶端共享出口。第二個是VIP和浮動IP高可用集群里經(jīng)常會有一個IP在多臺機(jī)器間漂移光看IP地址本身沒法定位物理機(jī)必須配合ARP表或者云平臺的API查實(shí)際綁定關(guān)系。第三個是IPv6很多新服務(wù)已經(jīng)開始啟用IPv6地址你如果不把IPv6的連接狀態(tài)納入統(tǒng)計會漏掉一部分流量。順著這三個點(diǎn)展開我們在復(fù)盤時還發(fā)現(xiàn)一個細(xì)節(jié)當(dāng)時我們有一批服務(wù)已經(jīng)支持IPv6但監(jiān)控和日志分析工具只統(tǒng)計了IPv4導(dǎo)致IPv6的流量一直處于“盲區(qū)”。好在這次故障的流量主要是IPv4不然排查起來還要多轉(zhuǎn)好幾個彎。5.3 排查避坑心得IP地址要當(dāng)“動態(tài)證據(jù)”看待這次之后我給團(tuán)隊(duì)定了幾條硬規(guī)矩。第一線上故障排查時凡是涉及“某IP異?!钡呐袛啾仨毻瑫r附上時間點(diǎn)、端口、協(xié)議、進(jìn)程四個維度不能只甩一個IP出來。第二所有服務(wù)注冊信息必須設(shè)置TTL和健康檢查失敗自動摘除避免過期實(shí)例長期留在注冊中心里“詐尸”。第三應(yīng)用的網(wǎng)絡(luò)配置文件一律禁止硬編碼IP全部改為通過環(huán)境變量或配置中心下發(fā)至少也要有自動化校驗(yàn)防止“舊IP被重新分配”這種坑再次出現(xiàn)。我也理解在緊急故障時做到這些并不容易人一慌就容易跳過步驟。但恰恰是這種時候IP地址這種最基礎(chǔ)的信息越能幫你穩(wěn)住心態(tài)。它不像日志里千奇百怪的異常信息它的格式是固定的、可校驗(yàn)的、可追蹤的。只要你愿意多花幾秒鐘去查它一下它幾乎不會撒謊。5.4 故障復(fù)盤清單速查這次故障之后我在復(fù)盤清單里新增了幾項(xiàng)固定檢查內(nèi)容每次出問題都會先過一遍域名解析結(jié)果和實(shí)際服務(wù)架構(gòu)是否一致有沒有多余的歷史記錄內(nèi)網(wǎng)資產(chǎn)平臺里IP段和業(yè)務(wù)線的對應(yīng)關(guān)系是否準(zhǔn)確比如192.168.7.0/24歸哪個團(tuán)隊(duì)服務(wù)注冊中心里有沒有連續(xù)多次健康檢查失敗的實(shí)例它們綁定的IP是否已經(jīng)過期異常流量是單IP還是整段IP整段IP往往意味著配置批量錯誤而不是單點(diǎn)攻擊排查過程中保留所有ss、dig、awk的輸出方便事后做根因分析這一套東西不一定每次都能直接命中根因但至少能保證你從IP維度切入時不會漏掉最關(guān)鍵的“鏈路錯位”問題。尾巴上的幾句實(shí)在話這次故障給我的最大感觸是IP地址從來不只是機(jī)房里的一個網(wǎng)絡(luò)術(shù)語它本質(zhì)上是一條條請求在系統(tǒng)里走過的“足跡”。線上服務(wù)出問題的時候代碼可以騙人日志可以刷屏但網(wǎng)絡(luò)層留下來的連接關(guān)系和時間點(diǎn)很難造假。很多看似復(fù)雜的故障只要你愿意彎下腰去查一下連接是從哪個IP來的、又打到了哪個IP上思路就會一下子豁然開朗。我自己現(xiàn)在排查故障的習(xí)慣已經(jīng)固定下來了任何問題進(jìn)來先花五分鐘把網(wǎng)絡(luò)鏈路和IP關(guān)系捋清楚再決定要不要深入看代碼。這個習(xí)慣幫我省下過很多個不眠夜。真希望這篇復(fù)盤能讓看的人少走一點(diǎn)彎路至少下一次系統(tǒng)出問題的時候除了翻日志你也能想起先看一眼IP。