誤從Nginx到后端的全鏈路診斷)
1. 先別急著改代碼Libvio訪問異常的整體排查思路做運(yùn)維或者自己折騰過Web服務(wù)的人基本都遇過這個(gè)場(chǎng)景服務(wù)明明已經(jīng)部署好了日志也正常但用戶一多或者某次改完配置頁面上突然打不開瀏覽器里要么來個(gè)“403 Forbidden”要么來個(gè)“502 Bad Gateway”。這兩個(gè)錯(cuò)誤碼一個(gè)是“我有權(quán)不讓你看”一個(gè)是“我背后的人沒理我”看著相似實(shí)際排查方向完全兩回事。我最近就在維護(hù)一個(gè)Libvio視頻管理服務(wù)這個(gè)服務(wù)負(fù)責(zé)把內(nèi)部視頻庫(kù)的元數(shù)據(jù)和轉(zhuǎn)碼任務(wù)統(tǒng)一封裝成HTTP接口供內(nèi)部的播放客戶端調(diào)用。剛上線的半個(gè)月里403和502交替出現(xiàn)搞得我一度懷疑是不是代碼寫錯(cuò)了。最后把所有原因一條一條揪出來發(fā)現(xiàn)大部分問題不在業(yè)務(wù)代碼而在一層層網(wǎng)關(guān)、代理、鑒權(quán)和系統(tǒng)配置上。這篇文章我就拿Libvio這個(gè)服務(wù)作為例子完整還原我是怎么排查和修復(fù)403、502的。無論你是運(yùn)維、后端開發(fā)還是自己在家搭了個(gè)小網(wǎng)站遇到同類問題照著這個(gè)思路走大概率能省掉一半瞎折騰的時(shí)間。1.1 403和502到底在說什么要排查問題先得搞清楚HTTP狀態(tài)碼的含義。403 Forbidden的意思是服務(wù)器接收到了你的請(qǐng)求也認(rèn)得你但出于權(quán)限、規(guī)則或身份校驗(yàn)的原因拒絕讓你訪問。注意這里和401不一樣401是“你還沒證明你是誰”403是“你已經(jīng)證明身份了但你沒權(quán)限”或者“服務(wù)器故意不給你”。常見觸發(fā)點(diǎn)包括IP黑名單、防盜鏈規(guī)則、Nginx的allow/deny配置、Token過期、簽名校驗(yàn)失敗、文件系統(tǒng)權(quán)限不對(duì)。502 Bad Gateway則完全是另一層意思它表示網(wǎng)關(guān)或者代理服務(wù)器比如Nginx收到了請(qǐng)求但在把請(qǐng)求轉(zhuǎn)發(fā)給上游服務(wù)比如后端API進(jìn)程時(shí)上游沒有返回有效響應(yīng)。你可以把它理解成打電話到前臺(tái)前臺(tái)幫你轉(zhuǎn)分機(jī)但分機(jī)沒人接或者對(duì)方接通后直接掛了。常見觸發(fā)點(diǎn)包括后端進(jìn)程崩潰、超時(shí)時(shí)間太短、后端資源耗盡、Unix Socket權(quán)限不對(duì)、防火墻攔截了內(nèi)部請(qǐng)求。這兩個(gè)錯(cuò)誤碼雖然觸發(fā)點(diǎn)不同但排查時(shí)有一條共性原則先確定錯(cuò)誤到底是誰返回的再順著鏈路往下查。我見過不少人一看到403就去改后端權(quán)限代碼結(jié)果后端根本沒問題是Nginx層攔截的也有看到502就重啟后端的結(jié)果重啟之后還是502因?yàn)檎嬲膯栴}出在后端的依賴服務(wù)比如數(shù)據(jù)庫(kù)或緩存已經(jīng)掛了。1.2 排查前必備的三件套在開始動(dòng)手之前先把工具準(zhǔn)備好。我認(rèn)為真正高效的排查離不開這三樣?xùn)|西第一能直接登錄服務(wù)器的終端最好是root或至少能看系統(tǒng)日志和Nginx日志的權(quán)限。第二后端服務(wù)、Nginx的訪問日志和錯(cuò)誤日志并且確認(rèn)它們的輪轉(zhuǎn)策略沒有把歷史日志清得太快。第三一個(gè)能穩(wěn)定復(fù)現(xiàn)問題的請(qǐng)求命令最簡(jiǎn)單的就是curl。我習(xí)慣用一個(gè)標(biāo)準(zhǔn)的curl命令保存成腳本出問題時(shí)直接跑curl -v -X GET \ -H Host: libvio.example.internal \ -H User-Agent: Mozilla/5.0 \ -H Authorization: Bearer your_token_here \ http://127.0.0.1:8080/api/video/list加-v是為了看完整請(qǐng)求和響應(yīng)頭手動(dòng)指定Host、User-Agent、Authorization則是為了盡量模擬真實(shí)訪問。排查期間不要嫌麻煩每改一個(gè)配置就重新跑一次這個(gè)命令觀察響應(yīng)頭變化。很多時(shí)候錯(cuò)誤碼不會(huì)告訴你答案但響應(yīng)頭里塞著答案比如Server頭是誰的、Via頭是哪個(gè)代理加的、WWW-Authenticate有沒有出現(xiàn)這些都能幫你縮小范圍。2. 403錯(cuò)誤把“拒絕訪問”拆成四層來看403這個(gè)狀態(tài)碼是我在Libvio上線初期遇到最多的。一開始我以為只要后端接口返回403那就是用戶權(quán)限的問題。后來排查到第三天才發(fā)現(xiàn)同一個(gè)403可能是四五個(gè)完全不同的原因造成的。我把它拆成四層網(wǎng)關(guān)入口層、鑒權(quán)簽名層、業(yè)務(wù)權(quán)限層、文件系統(tǒng)層。每一層的排查方法都不同。2.1 第一步確認(rèn)這個(gè)403到底是誰返回的拿到一個(gè)403先別急著查業(yè)務(wù)邏輯。第一步應(yīng)該看響應(yīng)頭里的Server字段。比如Nginx默認(rèn)返回的403響應(yīng)頭會(huì)帶“Server: nginx/1.24.0”如果是后端框架比如Python的Flask或者Go的net/http返回的403響應(yīng)頭一般帶的是后端服務(wù)標(biāo)識(shí)。還有一種情況是頁面經(jīng)過了多個(gè)反代你要看Via頭或者X-Powered-By頭。我處理Libvio時(shí)遇到過最迷惑的一次瀏覽器里訪問首頁是403但直接訪問后端端口卻正常。后來用curl打請(qǐng)求頭才發(fā)現(xiàn)403根本不是后端返回的而是最外層Nginx返回的默認(rèn)錯(cuò)誤頁因?yàn)閘ocation規(guī)則里配置了一個(gè)return 403;而那條規(guī)則原本是給某個(gè)已下線功能留的結(jié)果正則匹配范圍太寬把所有/api/路徑都吞了。所以拿到403先確認(rèn)來源真的能省下兩個(gè)小時(shí)。2.2 網(wǎng)關(guān)攔截先從IP黑白名單和Referer防盜鏈查起確認(rèn)是Nginx層返回的403之后最值得優(yōu)先懷疑的就是IP黑白名單和防盜鏈規(guī)則。Libvio當(dāng)時(shí)在Nginx里配過類似這樣的規(guī)則location /video/ { allow 10.0.0.0/8; allow 172.16.0.0/12; deny all; }這條規(guī)則本意是只允許內(nèi)網(wǎng)網(wǎng)段訪問視頻流看起來沒什么問題。但實(shí)際用起來之后我發(fā)現(xiàn)了一個(gè)坑——如果Libvio服務(wù)架在負(fù)載均衡器后面而負(fù)載均衡器沒開透?jìng)髡鎸?shí)客戶端IP那么Nginx看到的“客戶端IP”實(shí)際上是負(fù)載均衡器的IP也就是允許網(wǎng)段內(nèi)的IP。這樣倒還好不會(huì)誤殺。但如果你用的是正則表達(dá)式允許列表寫錯(cuò)一個(gè)掩碼或者多個(gè)location嵌套就很容易把本該放行的請(qǐng)求給deny了。防盜鏈Referer校驗(yàn)也是一個(gè)經(jīng)典的403來源。很多視頻服務(wù)會(huì)做防盜鏈只允許特定域名來源的請(qǐng)求訪問資源。配置成下面這種valid_referers none blocked example.internal; if ($invalid_referer) { return 403; }這個(gè)配置的坑在于none代表空Refererblocked代表被代理或防火墻抹掉Referer的請(qǐng)求。如果你的請(qǐng)求恰好帶了一個(gè)不在白名單里的Referer或者客戶端用了某些隱私模式導(dǎo)致Referer和預(yù)期不一致就會(huì)觸發(fā)403。排查時(shí)建議先用curl直接請(qǐng)求不加Referer看看能否通過如果通過了再逐步加Referer頭復(fù)現(xiàn)。2.3 鑒權(quán)Token過期和簽名校驗(yàn)失敗如果不是Nginx層攔截而是后端返回的403那大概率是鑒權(quán)出了問題。Libvio的接口走的是“臨時(shí)簽名URL”模式每次生成一個(gè)帶expire時(shí)間和簽名的URL。這個(gè)機(jī)制本身沒什么問題問題經(jīng)常出在時(shí)間上。比如生成簽名的服務(wù)器時(shí)間和用戶發(fā)起請(qǐng)求的時(shí)間差超過誤差范圍簽名就會(huì)被判無效即使URL還沒過期。我當(dāng)時(shí)遇到的一個(gè)實(shí)際案例是某臺(tái)容器宿主機(jī)的時(shí)間比正常時(shí)間慢了兩分鐘但簽名是從另一臺(tái)時(shí)間同步正常的機(jī)器上生成出來的結(jié)果用戶拿到URL后立刻訪問被后端判定“請(qǐng)求時(shí)間超出允許范圍”返回403。排查時(shí)我直接看后端日志發(fā)現(xiàn)里面寫的是expire check failed: now - expire 300于是意識(shí)到是時(shí)間偏移。除了時(shí)間差簽名校驗(yàn)失敗還常見于密鑰不一致。比如部署新版本時(shí).env文件里的簽名密鑰被重置了舊用戶手里的Token自然全部失效。排查這種問題最簡(jiǎn)單的辦法是取一個(gè)已知有效的Token在代碼里跑一次校驗(yàn)看看是密鑰庫(kù)不一致還是算法變了。還有就是檢查是否有多個(gè)實(shí)例像Libvio這種服務(wù)我經(jīng)常會(huì)在兩臺(tái)機(jī)器上各跑一個(gè)后端實(shí)例如果兩臺(tái)實(shí)例配置里面的SECRET_KEY不一樣用戶請(qǐng)求被負(fù)載均衡分發(fā)到另一臺(tái)上就會(huì)偶發(fā)403讓人非常抓狂。2.4 時(shí)間不同步一個(gè)容易被忽略的系統(tǒng)級(jí)原因鑒權(quán)簽名校驗(yàn)和時(shí)間相關(guān)所以系統(tǒng)時(shí)間同步是整個(gè)鏈路里看起來不起眼、卻特別重要的環(huán)節(jié)。我第一次意識(shí)到這個(gè)問題是因?yàn)長(zhǎng)ibvio頁面偶爾出現(xiàn)403而且只出現(xiàn)在中午和下午的特定時(shí)間段。后來我登到服務(wù)器上執(zhí)行date一看服務(wù)器系統(tǒng)時(shí)間比標(biāo)準(zhǔn)時(shí)間慢了近50秒。原來那臺(tái)虛擬機(jī)開了節(jié)能模式又沒有安裝NTP客戶端導(dǎo)致時(shí)間累積偏移。解決辦法很簡(jiǎn)單安裝ntp或chrony并開啟自動(dòng)同步# Ubuntu/Debian apt install chrony -y systemctl enable --now chrony # CentOS/RHEL yum install chrony -y systemctl enable --now chronyd timedatectl set-ntp true對(duì)于所有依賴時(shí)間戳簽名的服務(wù)這一項(xiàng)必須是上線檢查清單里的固定條目。2.5 403排查的幾條實(shí)操心得搞了這么多次403排查我總結(jié)了幾個(gè)非常實(shí)用的經(jīng)驗(yàn)第一把所有可能出現(xiàn)403的規(guī)則盡量做成“白名單模式”而不是“黑名單模式”。比如Nginx的deny規(guī)則只用來封禁明確要拉黑的IP而不是默認(rèn)拒絕所有流量。白名單誤傷容易暴露黑名單誤傷則是靜默的。第二排查時(shí)一定要保存一份完整的請(qǐng)求頭和響應(yīng)頭至少包括User-Agent、Referer、Authorization、Cookie、Accept。我遇到過用戶訪問403但我自己在服務(wù)器上curl一模一樣的URL卻是200差別只在于瀏覽器會(huì)自動(dòng)帶上Cookie和Referer而我沒有。不要靠猜要看頭。第三后端日志一定要打印“最終拒絕原因”而不是籠統(tǒng)地打印“403”。我在Libvio里是這樣處理的所有鑒權(quán)失敗都輸出一個(gè)結(jié)構(gòu)化的錯(cuò)誤碼字段比如AUTH_EXPIRED、AUTH_SIGN_INVALID、AUTH_IP_DENIED。這樣處理錯(cuò)誤日志時(shí)能快速定位到是哪一段邏輯拒絕了請(qǐng)求。3. 502錯(cuò)誤網(wǎng)關(guān)與后端“失聯(lián)”的五個(gè)高頻原因如果說403是權(quán)限問題那502就更像是“連接問題”。Nginx在作為反代時(shí)如果發(fā)現(xiàn)自己連不上后端、后端連接超時(shí)或者后端返回了非法響應(yīng)就會(huì)給客戶端返回502。Libvio出現(xiàn)502的頻率比403低一些但一出現(xiàn)往往就是整體不可用影響更大。我按原因從高到低列幾個(gè)常見的。3.1 502的本質(zhì)代理與上游之間的連接失敗先看Nginx層的502是怎么產(chǎn)生的。Nginx收到請(qǐng)求后需要以proxy_pass配置的地址為準(zhǔn)去連接上游。連接建立后Nginx把請(qǐng)求轉(zhuǎn)發(fā)給上游然后等待響應(yīng)。整個(gè)過程分成proxy_connect_timeout連接超時(shí)、proxy_send_timeout發(fā)送超時(shí)、proxy_read_timeout讀取響應(yīng)超時(shí)三個(gè)階段。只要這三個(gè)階段里任何一個(gè)超時(shí)或者TCP連接被重置Nginx就會(huì)返回502。所以排查502的第一步是確認(rèn)后端服務(wù)本身是否可用、是否監(jiān)聽在正確的端口上。我一般直接用curl打后端地址繞過Nginxcurl -v http://127.0.0.1:8080/health如果這個(gè)命令正常再回頭查Nginx和后端的連接問題如果命令本身超時(shí)、拒絕連接那就可以直接確定后端的問題。3.2 后端進(jìn)程崩潰最直接也最容易被忽視的后端進(jìn)程崩潰在業(yè)務(wù)量上來之后特別常見尤其是在進(jìn)程處理到某個(gè)異常請(qǐng)求時(shí)直接panic導(dǎo)致整個(gè)進(jìn)程退出。Libvio后端當(dāng)時(shí)用的是單個(gè)Python進(jìn)程并發(fā)能力有限遇到某個(gè)視頻轉(zhuǎn)碼任務(wù)消耗了太多內(nèi)存時(shí)直接OOM被內(nèi)核殺掉Nginx自然返回502。排查時(shí)先看進(jìn)程狀態(tài)systemctl status libvio-backend journalctl -u libvio-backend --since 10 minutes ago如果發(fā)現(xiàn)進(jìn)程反復(fù)重啟那幾乎可以斷定是崩潰循環(huán)。這時(shí)候不要急著重啟一次應(yīng)該先看崩潰棧和資源監(jiān)控記錄。Libvio后來引入了兩部分修改一是為后端進(jìn)程配置了CPU和內(nèi)存限制避免單個(gè)任務(wù)把整機(jī)拖死二是加入了自動(dòng)重啟策略比如systemd里的Restarton-failure。如果用的是Docker或Kubernetes這類容器化方案就檢查容器的重啟策略和健康檢查探針。記住自動(dòng)重啟只是止血真正要做的是找出為什么會(huì)崩潰。一個(gè)長(zhǎng)期解決不了崩潰原因的服務(wù)就算自動(dòng)重啟也會(huì)把故障變成常態(tài)。3.3 超時(shí)配置從默認(rèn)60秒到分級(jí)超時(shí)后端進(jìn)程沒崩潰但Nginx還是報(bào)502這種情況十有八九是超時(shí)配置問題。Nginx默認(rèn)的proxy_read_timeout是60秒。Libvio的某個(gè)接口需要同步處理視頻切片如果文件比較大響應(yīng)時(shí)間可能超過60秒。這時(shí)候Nginx會(huì)主動(dòng)斷開連接返回502但后端可能還在處理任務(wù)日志里沒有異常。用戶看到的只是“頁面打不開”后端卻覺得“我還在干著呢”。解決思路不能是簡(jiǎn)單地把超時(shí)調(diào)到很大的數(shù)而是要分級(jí)處理。我的方案是對(duì)需要長(zhǎng)時(shí)間運(yùn)行的任務(wù)把接口改成異步模式——提交任務(wù)后立刻返回202 Accepted客戶端用job_id輪詢?nèi)蝿?wù)狀態(tài)。這樣一來同步接口的響應(yīng)時(shí)間大多能控制在幾百毫秒Nginx的超時(shí)配置就不需要為極端場(chǎng)景妥協(xié)。如果實(shí)在沒法改異步再考慮給特定location單獨(dú)加長(zhǎng)超時(shí)location /api/video/transcode/ { proxy_read_timeout 300s; proxy_connect_timeout 5s; proxy_send_timeout 30s; }這里要特別說明proxy_connect_timeout不建議設(shè)置太長(zhǎng)因?yàn)槿绻蠖朔?wù)真的不可達(dá)5秒就能發(fā)現(xiàn)問題而不是讓用戶白白等上30秒。3.4 資源耗盡CPU、內(nèi)存、句柄一個(gè)都不能少另一種隱蔽的502是后端進(jìn)程明明活著但資源不夠用。最常見的是內(nèi)存不足導(dǎo)致系統(tǒng)開始大量使用Swap進(jìn)程響應(yīng)變得極慢Nginx讀不到響應(yīng)最終超時(shí)返回502。還有一種情況是文件句柄耗盡后端打不開新的日志文件或數(shù)據(jù)庫(kù)連接服務(wù)“假活”。排查資源問題我通常用這幾個(gè)命令# 查看系統(tǒng)負(fù)載和內(nèi)存 top -b -n 1 | head -30 free -h # 查看進(jìn)程打開的文件數(shù) ls /proc/PID/fd | wc -l # 查看TCP連接數(shù) ss -s如果發(fā)現(xiàn)文件句柄數(shù)接近上限ulimit -n的默認(rèn)值經(jīng)常是1024對(duì)后端服務(wù)來說太小了就需要適當(dāng)調(diào)高。Libvio后來在systemd服務(wù)單元里明確設(shè)置了LimitNOFILE65535。連接數(shù)方面如果大量TCP連接處于TIME_WAIT狀態(tài)也可能耗盡本地端口這時(shí)候需要調(diào)整內(nèi)核參數(shù)比如net.ipv4.tcp_tw_reuse。3.5 防火墻和Socket連接限制人想不到的“隱形殺手”防火墻導(dǎo)致的502也相當(dāng)常見。很多企業(yè)內(nèi)網(wǎng)的安全策略會(huì)限制后端服務(wù)監(jiān)聽端口對(duì)外的訪問但Nginx和后端在同一臺(tái)機(jī)器上時(shí)走的是回環(huán)地址127.0.0.1一般不會(huì)受影響。問題出在 Nginx 與后端在不同機(jī)器上的架構(gòu)里。我遇到過一件特別隱晦的事Libvio的Nginx和后端在兩臺(tái)服務(wù)器上中間有防火墻規(guī)則只放行了8080端口。某次安全組調(diào)整后防火墻規(guī)則中的子網(wǎng)掩碼寫錯(cuò)了導(dǎo)致部分后端服務(wù)器的回包被丟棄。結(jié)果就是Nginx能建立TCP連接但收不到后端的數(shù)據(jù)表現(xiàn)為隨機(jī)性502。排查手法是同時(shí)抓包在Nginx服務(wù)器上抓能看到TCP握手成功、但是發(fā)出的請(qǐng)求沒有響應(yīng)在后端服務(wù)器上抓能看到請(qǐng)求確實(shí)到達(dá)了響應(yīng)也發(fā)出了但被防火墻攔了。這種問題不看兩邊抓包很難定位。還有一類502隱藏在Unix Socket的權(quán)限上。如果Nginx通過proxy_pass http://unix:/tmp/libvio.sock;連后端那么Socket文件的屬主和屬組必須允許Nginx進(jìn)程用戶讀寫。我曾經(jīng)因?yàn)橹貑⒑蠖朔?wù)后Socket文件被重新生成為root:root而Nginx以www-data用戶運(yùn)行導(dǎo)致連接被拒出現(xiàn)502。解決方式是讓后端進(jìn)程和Nginx運(yùn)行同一個(gè)組或者直接把Socket權(quán)限設(shè)為0660并確保屬主正確。4. 從前端到后端把異常攔截在發(fā)生之前排查了這么多403和502我最深的感覺是事后修Bug不如事前列好防線。與其天天盯著監(jiān)控不如把日志、健康檢查、預(yù)警機(jī)制做扎實(shí)。這個(gè)部分是我的實(shí)戰(zhàn)精華建議直接抄作業(yè)。4.1 日志規(guī)范與錯(cuò)誤碼上報(bào)Libvio的日志一開始很混亂后端用的是Python標(biāo)準(zhǔn)庫(kù)的printNginx日志也沒有統(tǒng)一格式。出問題時(shí)兩邊日志很難對(duì)應(yīng)。后來我把整個(gè)鏈路的日志規(guī)范做了一次統(tǒng)一每個(gè)請(qǐng)求從Nginx進(jìn)來的那一刻就生成一個(gè)trace_id放在HTTP頭和日志變量里后端收到后也透?jìng)鬟@個(gè)ID。這樣查問題時(shí)只要拿到用戶的報(bào)錯(cuò)截圖里的trace_id就能在Nginx和后端日志里同時(shí)搜到同一個(gè)請(qǐng)求。日志按天分文件并保留30天同時(shí)接入統(tǒng)一日志采集方便按關(guān)鍵字搜索。后端在鑒權(quán)、數(shù)據(jù)庫(kù)訪問、外部請(qǐng)求超時(shí)等關(guān)鍵節(jié)點(diǎn)都打上了結(jié)構(gòu)化日志格式為keyvalue字段包括event、reason、latency_ms、remote_addr。舉個(gè)例子原來后端日志是[2025-04-01 10:00:01] ERROR: access denied現(xiàn)在變成[2025-04-01 10:00:01] levelERROR eventauth_deny reasontoken_expired user_id123 trace_idabc123這樣的日志一眼就能看出拒絕的具體原因而不是只看到一個(gè)干巴巴的“access denied”。有了統(tǒng)一的trace_id體系后我們查問題的速度至少快了一倍。4.2 健康檢查與自動(dòng)摘除對(duì)于502問題預(yù)防的關(guān)鍵是讓網(wǎng)關(guān)不要在“節(jié)點(diǎn)”上硬扛。Libvio用的是Nginx加兩個(gè)后端實(shí)例并且開啟了Nginx的被動(dòng)健康檢查機(jī)制upstream libvio_backend { server 10.0.0.11:8080 max_fails3 fail_timeout10s; server 10.0.0.12:8080 max_fails3 fail_timeout10s; keepalive 32; }這個(gè)配置含義是每個(gè)后端實(shí)例在10秒內(nèi)如果連續(xù)失敗3次就會(huì)被自動(dòng)標(biāo)記為不可用暫停轉(zhuǎn)發(fā)請(qǐng)求。之后Nginx會(huì)定期探測(cè)恢復(fù)情況一旦實(shí)例恢復(fù)健康會(huì)自動(dòng)重新加入。這個(gè)機(jī)制能避免請(qǐng)求打到已掛掉的后端上從源頭減少用戶能感知到的502。同時(shí)后端要提供一個(gè)輕量的健康檢查接口app.route(/healthz) def healthz(): return {status: ok}, 200注意這個(gè)接口里不要做重操作比如查數(shù)據(jù)庫(kù)或加載視頻元數(shù)據(jù)不然健康檢查本身就變成一種壓力。我曾經(jīng)犯過這個(gè)錯(cuò)健康檢查每5秒查一次數(shù)據(jù)庫(kù)數(shù)據(jù)庫(kù)慢查詢時(shí)健康檢查也跟著慢導(dǎo)致Nginx以為后端掛了。4.3 常見問題速查表從現(xiàn)象到解決方案我把排查過程中整理出來的經(jīng)驗(yàn)濃縮成一張速查表遇到問題時(shí)按圖索驥就好。現(xiàn)象可能原因首查命令/工具解決方向直接訪問Nginx返回403IP黑白名單、location規(guī)則誤匹配curl -v查看響應(yīng)頭Server逐層檢查Nginx配置調(diào)整allow/deny和location優(yōu)先級(jí)帶Referer時(shí)403不帶時(shí)正常防盜鏈規(guī)則過期或白名單缺失curl -v -H Referer:...更新valid_referers配置同一URL部分請(qǐng)求403簽名密鑰不一致或多實(shí)例部署檢查各實(shí)例SECRET_KEY配置統(tǒng)一密鑰盡量將密鑰集中管理偶發(fā)403且與時(shí)間相關(guān)服務(wù)器時(shí)間未同步date、timedatectl安裝chrony開啟NTP同步Nginx錯(cuò)誤日志出現(xiàn)connect() failed后端端口未監(jiān)聽ss -lntp啟動(dòng)后端進(jìn)程或修改監(jiān)聽地址后端進(jìn)程反復(fù)重啟OOM或panicjournalctl -u、dmesg定位崩潰棧配置自動(dòng)重啟調(diào)優(yōu)資源請(qǐng)求超過60秒后返回502默認(rèn)讀超時(shí)太短查看Nginx錯(cuò)誤日志異步化慢接口或?qū)μ囟╨ocation調(diào)大proxy_read_timeout502時(shí)后端內(nèi)存爆滿內(nèi)存不足free -h、top增加資源配置排查內(nèi)存泄漏Unix Socket連接502Socket權(quán)限不對(duì)ls -l /tmp/libvio.sock修改Socket屬主和權(quán)限跨機(jī)器部署時(shí)隨機(jī)502防火墻丟棄回包兩端同時(shí)抓包對(duì)比修改防火墻策略做連通性測(cè)試健康檢查本身變慢導(dǎo)致錯(cuò)誤摘除健康檢查邏輯太重壓測(cè)/healthz精簡(jiǎn)健康檢查邏輯不查重型依賴這張表不一定覆蓋所有場(chǎng)景但對(duì)大多數(shù)由Nginx和后端構(gòu)成的Web服務(wù)來說已經(jīng)能命中至少七成問題。5. 老運(yùn)維給新手的幾句實(shí)在話最后再聊幾句我的個(gè)人體會(huì)。這批403和502排查下來我覺得最核心的心法是定位永遠(yuǎn)比修復(fù)重要。很多人看到報(bào)錯(cuò)第一反應(yīng)是用“重啟大法”我也不反對(duì)重啟但重啟前至少要留好證據(jù)。在Libvio的日志規(guī)范建立起來之前我做過很多次“盲重啟”結(jié)果就是問題復(fù)現(xiàn)了卻找不到根因白忙一場(chǎng)。另外建議每次修改配置前保存?zhèn)浞莶⑶颐鞔_記錄改動(dòng)時(shí)間和改動(dòng)原因。Nginx配置文件最容易因?yàn)橐粋€(gè)多余的空格或者一個(gè)錯(cuò)誤的括號(hào)引起大范圍故障。Libvio上線初期有一次修改proxy_pass時(shí)末尾多了一個(gè)斜杠導(dǎo)致路徑被重寫部分接口直接404。你看又一個(gè)錯(cuò)誤碼冒出來了。所以我現(xiàn)在改任何配置文件都會(huì)先復(fù)制原文件保存為nginx.conf.bak-yyyymmdd然后執(zhí)行nginx -t驗(yàn)證語法再平滑重載。這套習(xí)慣救了我很多次。最后一個(gè)小技巧如果你剛開始接觸這些排查流程可以從一條命令開始練起那就是curl -v。它會(huì)把整個(gè)請(qǐng)求和響應(yīng)的細(xì)節(jié)都擺在你面前。無論問題看起來多復(fù)雜先把這個(gè)命令輸出里的每一行都看懂你已經(jīng)比大多數(shù)只會(huì)刷新頁面的人強(qiáng)很多了。403和502看起來嚇人但只要鏈路清晰、日志完整、心態(tài)穩(wěn)住它們不過就是一串進(jìn)入服務(wù)器的HTTP報(bào)文順著拆解就行。