閱讀984 返回首頁    go 阿裏雲 go 技術社區[雲棲]


觸類旁通,從400錯誤看Nginx常見故障與修複

作者介紹

林偉壕網絡安全DevOps新司機,先後在中國電信和網易遊戲從事數據網絡、網絡安全和遊戲運維工作。對Linux運維、虛擬化和網絡安全防護等研究頗多,目前專注於網絡安全自動化檢測、防禦係統構建。

 

眾所周知,Nginx是目前最流行的Web Server之一,也廣泛應用於負載均衡、反向代理等服務,使用過程中可能因為對Nginx工作原理、變量含義、參數大小等問題的理解錯誤,導致Nginx工作異常。

 

因此,本文將從一個Nginx錯誤代碼400引發的故障入手,談談如何分析和修複常見的Nginx異常。

 

20170323101605385.jpg 

故障簡述

 20170323101605385.jpg

 

小明某天中午在線優化一個敏感服務的Nginx配置時,發現5分鍾內Nginx errorlog裏出現了大量400錯誤,於是迅速回滾了Nginx配置。

 

20170323101605385.jpg 

故障詳情

 20170323101605385.jpg

 

原來的Nginx配置存在重複或者需廢棄的內容,於是在多次diff了新舊兩份配置內容後,小明認為最新配置是不影響業務的,因此在線推送更新配置後,直接reload了Nginx,出於double check原則,在線觀察了5分鍾Nginx日誌:

 


\

 

發現出現大量類似下麵的400錯誤:

 

\

 

400錯誤的產生,很可能影響服務端或客戶端的後續業務邏輯判斷,因此需要引起重視。

 

20170323101605385.jpg 

處理過程

 20170323101605385.jpg

 

20170323101721459.gif節點1

 

當時回滾配置後,小明先在搜索引擎查找了Nginx 400錯誤的可能原因和解決辦法,初步確定有下麵兩種可能:1是空主機頭,2是請求包頭過大。

 

小明跟客戶端同學確認了客戶端請求方式,發現他們使用的是類似telnet的方式發起的http請求,類似下麵的:

 


\

 

為了方便後續排查,小明參考線上環境臨時搭建了一套Nginx測試環境,重現了故障:

 


\

 

後來小明了解到原來客戶端不是從代碼的http庫調用, 而是按照上麵的方式走TCP/telnet傳遞http參數來調用服務端http接口。但是為什麼一樣的客戶端請求方式,舊配置完全ok,新配置則會出現大量400錯誤?

 

20170323101721459.gif節點2

 

至此,小明懷疑自己沒有完全diff出新舊兩份配置的差別,於是他使用vimdiff再次對比新舊兩份配置。下麵僅貼出關鍵配置:

 

舊配置:

 

\
\

 


新配置:

 

\

 

本次排查中,小明考慮的重點是新配置裏遺漏了某些配置,於是他把location ~ (.*)的相關邏輯加上,發現問題依舊:

 


\

 

20170323101721459.gif節點3

 

既然前麵往缺失配置的思路走不通,下麵就按照新增配置的思路排查,結果發現新配置增加了一些包頭信息,小明懷疑是請求包過大,於是優先排查了Nginx針對包頭大小的設置,其中有這麼幾個配置:

 

  • client_header_buffer_size:默認是1k,所以header小於1k的話是不會出現問題的。

  • large_client_header_buffers:該命令用於設置客戶端請求的Header頭緩衝區的大小,默認值為4KB。

  • 客戶端請求行不能超過large_client_header_buffers指令設置的值,客戶端請求的Header頭信息不能大於large_client_header_buffers指令設置的緩衝區大小,否則會報“Request URL too large”(414)或者“Bad-request”(400)錯誤,如果客戶端Cookie信息較大,則須增加緩衝區大小。於是小明將client_header_buffer_size和large_client_header_buffers都設置為128k。結果問題也重現了。

 

接下來,小明發現新配置中多了“proxy_set_header Host $http_host;”查找了Nginx官方文檔發現跟$http_host類似功能的還有$server_name和$host等變量,在他將$http_host更換成$host後,問題修複了。

 

20170323101605385.jpg 

原因分析

 20170323101605385.jpg

 

根據Nginx官方文檔介紹,400狀態碼含義如下:

 


\

 

上麵是http1.1的rfc關於host部分的解釋,從上麵我們了解到如果一個http1.1的請求沒有host域,那麼server應該給client段發送400的狀態碼,表明這個請求server不能處理。而對於Nginx server來說,也遵循這樣的方式,說明client發送了一個無效的請求,Nginx server無法處理,於是返回了400的狀態碼。

 

另外,關於$host和$http_host這兩個變量的區別如下:

 


\

 

本次故障中,客戶端的調用方式沒有使用host 參數,傳遞了空的Host頭給服務端,一旦Nginx設置了proxy_set_header Host $http_host,空Host頭就傳給了後端。然而,在http 1.1的規範中,Host隻要出現空,就會返回400,所以出現了這個故障。而對於需要在Host字段裏帶上端口信息的,則仍需要配置proxy_set_header Host $http_host。

 

最後,需要注意的是,400錯誤不一樣會影響業務,需要看具體的業務處理邏輯,比如使用nagios的check_tcp插件對Nginx server端口做檢測或者使用keepalived的tcp_check功能對後端Nginx端口的存活做檢測,這兩種情況都會在Nginx errorlog中產生400的請求。

 

原因也很簡單,就是一般tcp check的方式,就是建立tcp連接,但是沒有發送任何數據,當然也沒有Host頭,然後再reset或者四次揮手斷開連接。

 

20170323101605385.jpg 

經驗教訓

 20170323101605385.jpg

 

20170323101721459.gif運維規範

 

細心的同學會發現本次配置更新是在大中午操作,而且也沒有在測試環境測試通過,這在流程上是不嚴謹的。雖然Nginx等web服務的配置更新基本上通過熱更就可以了,但沒有灰度測試或者在測試環境測試,一是無法提前發現問題,二是無法控製業務影響。所以,在運維規範上看,即使是熱更也應當在測試環境測試正常後再同步到線上,其他的更新則應在業務低穀時操作。

 

20170323101721459.gif技術學習方法

 

本次故障的產生,很大程度上就是運維同學不理解Nginx變量的定義和區別,直接從搜索引擎上找了些配置,檢查覺得正確就推到了線上。這裏仍需要重申的是,以官方文檔為準!互聯網上很多知識或者配置有各種各樣的問題,隨時都有暗坑在裏邊,隻有啃過官方文檔才能避免誤讀。

 

20170323101721459.gifWeb日誌分析

 

針對這裏的Nginx錯誤日誌查看,我們看到小明是用在線命令查看的,其實現在有很多web日誌分析工具或係統,比如ELK(ElacticSearch+LogStash+Kibana),隻需要配置好grok正則,是可以通過可視化界麵實時監控web服務質量的。

 

20170323101721459.gif引申

 

上麵介紹了Nginx 400錯誤的可能原因和解決辦法,但實際工作中,我們遇到的可不止這麼一點。於是,由此引申出去的是,針對那些Nginx常見錯誤如何去排查和解決。

 

  • 403錯誤

 

403是很常見的錯誤代碼,一般就是未授權被禁止訪問的意思。

 

可能的原因有兩種:

  1. Nginx程序用戶無權限訪問web目錄文件

  2. Nginx需要訪問目錄,但是autoindex選項被關閉

 

修複方法:

  1. 授予Nginx程序用戶權限讀取web目錄文件

  2. 設置autoindex目錄為on

 

\

 

  • 413錯誤

 

在上傳時Nginx返回了413錯誤:“413 Request Entity Too Large”,這一般就是上傳文件大小超過Nginx配置引起。

 

修複方法:

  1. 在Nginx.conf增加client_max_body_size的設置,這個值默認是1M,可以增加到8M以提高文件大小限製;

  2. 如果運行的是php,那麼還要檢查php.ini,這個大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,這樣就不會因為提交數據大小不一致出現的錯誤。

 

post_max_size = 8M
upload_max_filesize = 2M

 

  • 502錯誤

 

Nginx 502 Bad Gateway的含義是請求的PHP-CGI已經執行,但是由於某種原因(一般是讀取資源的問題)沒有執行完畢而導致PHP-CGI進程終止。一般來說Nginx 502 Bad Gateway和php-fpm.conf的設置有關。

 

修複方法:
1、查看FastCGI進程是否已經啟動

 

ps -aux | grep php-cgi
 

2、檢查係統Fastcgi進程運行情況


除了第一種情況,fastcgi進程數不夠用、php執行時間長、或者是php-cgi進程死掉也可能造成Nginx的502錯誤。


運行以下命令判斷是否接近FastCGI進程,如果fastcgi進程數接近配置文件中設置的數值,表明worker進程數設置太少。


netstat -anpo | grep "php-cgi" | wc -l
 

3、FastCGI執行時間過長
 

根據實際情況調高以下參數值


fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;

 

  • 504錯誤

 

Nginx 504 Gateway Time-out的含義是所請求的網關沒有請求到,簡單來說就是沒有請求到可以執行的PHP-CGI。


Nginx 504 Gateway Time-out一般與Nginx.conf的設置有關。


頭部太大這種情況可能是由於Nginx默認的fastcgi進程響應的緩衝區太小造成的, 這將導致fastcgi進程被掛起,如果你的fastcgi服務對這個掛起處理的不好,那麼最後就極有可能導致504 Gateway Time-out。


默認的fastcgi進程響應的緩衝區是8K,可以調大以下參數:

 

  • fastcgi_buffer_size 128k;
    fastcgi_buffers 8 128k;

  • fastcgi_busy_buffers_size 由 128K 改為 256K;
    fastcgi_temp_file_write_size 由 128K 改為 256K。

 

此外,也可能是php-cgi的問題,需要修改php.ini的配置:

 

  1. 將max_children由之前的10改為30,這樣操作是為了保證有充足的php-cgi進程可以被使用。

  2. 將request_terminate_timeout由之前的0秒改成60秒,這樣使php-cgi進程處理腳本的超時時間提高到60秒,可以防止進程被掛起以提高利用效率。

 原文發布時間為:2017-03-23

本文來自雲棲社區合作夥伴DBAplus

最後更新:2017-05-16 11:31:26

  上一篇:go  DB賬號防泄密,請立刻為數據庫加把鎖!(附演示視頻)
  下一篇:go  10款常見MySQL高可用方案選型解讀