1. 程式人生 > >從400錯誤引發的故障入手,談談如何分析和修復常見的Nginx異常

從400錯誤引發的故障入手,談談如何分析和修復常見的Nginx異常

nginx
作者介紹

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

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

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

故障簡述

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

故障詳情

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

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

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

處理過程

節點1

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

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

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

後來小明瞭解到原來客戶端不是從程式碼的http庫呼叫, 而是按照上面的方式走TCP/telnet傳遞http引數來呼叫服務端http介面。但是為什麼一樣的客戶端請求方式,舊配置完全ok,新配置則會出現大量400錯誤?

節點2

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

舊配置:

新配置:

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

節點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後,問題修復了。

原因分析

根據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或者四次揮手斷開連線。

經驗教訓

運維規範

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

技術學習方法

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

Web日誌分析

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

引申

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

403錯誤

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

可能的原因有兩種:
Nginx程式使用者無許可權訪問web目錄檔案
Nginx需要訪問目錄,但是autoindex選項被關閉

修復方法:
授予Nginx程式使用者許可權讀取web目錄檔案
設定autoindex目錄為on

413錯誤

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

修復方法:
在Nginx.conf增加client_max_body_size的設定,這個值預設是1M,可以增加到8M以提高檔案大小限制;
如果執行的是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的配置:

將max_children由之前的10改為30,這樣操作是為了保證有充足的php-cgi程序可以被使用。
將request_terminate_timeout由之前的0秒改成60秒,這樣使php-cgi程序處理指令碼的超時時間提高到60秒,可以防止程序被掛起以提高利用效率。

參考資料

原文出處:http://dbaplus.cn/news-21-1129-1.html
Nginx 400狀態碼排查
http://www.68idc.cn/help/jiabenmake/qita/20140707115201.html
Nginx變數大全
https://openresty.org/download/agentzh-Nginx-tutorials-en.html
Nginx報502、504、400、413錯誤
http://blog.csdn.net/miltonzhong/article/details/9195855