Nginx 管理與內部機制

最後更新: 2022-12-19 02:06:42 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

Nginx 是事件驅動的 HTTP、串流與郵件代理伺服器。應將其設定、程序權限與上游逾時視同正式環境程式碼:重新載入前先驗證變更,並保留一份已知可用的設定。

事件驅動 I/O #

Nginx 以非阻塞事件迴圈為核心。主程序讀取設定並啟動工作程序;每個工作程序接著在 epoll、kqueue 或平台提供的同等就緒通知後端等待。工作程序會為每個對端維護一筆連線記錄,並只在通訊端就緒時推進各項請求。

因此,單一事件迴圈不需要為每個請求建立執行緒,便能處理大量連線。此模型也說明了記錄與緩衝策略的重要性:上游、用戶端與磁碟寫入各自推進時,工作程序仍須保持回應能力。

隔離設定 #

測試本機建置時,請以明確的前綴與設定啟動執行個體。記錄檔與暫存檔等相對路徑會以此前綴解析。

sh
nginx -p "$PWD" -c nginx.conf
nginx -t -p "$PWD" -c nginx.conf

對於非特權的本機程序,請使用非特權監聽連接埠,並將 pid、記錄檔,以及每個請求主體或上游暫存路徑設為可寫入的位置。daemon off; 與 master_process off; 可簡化前景偵錯,但不適合正式環境。

include 指令會以文字方式納入設定,其路徑可相對於前綴,也可使用絕對路徑。請以 nginx -t 驗證完整的 include 關係圖,並以 nginx -T 檢查展開後的設定。重新載入時,主程序會剖析並驗證新設定;成功後啟動新的工作程序,並要求舊工作程序完成目前工作後退出。若剖析或資源取得失敗,舊設定仍會繼續提供流量服務。

虛擬伺服器與 CORS #

Nginx 依據監聽器與 Host 標頭選擇 HTTP 虛擬伺服器。在同一監聽器中,選擇順序依次為完全符合的 server_name、最長的前置萬用字元、最長的後置萬用字元,最後才是第一個符合的規則運算式。server_name .example.com 是頂層網域與 *.example.com 的簡寫;單獨的 *.example.com 不會符合頂層網域。

選定伺服器後,Nginx 會依前綴與規則運算式規則尋找 location。完全符合的 location 會立即勝出;否則,Nginx 會記住最長的前綴,並依序評估適用的規則運算式。^~ 修飾詞會在該前綴符合後阻止規則運算式檢查。內部重新寫入可能啟動另一次 location 搜尋,因此代理行為與可見請求不同時,應追蹤最終 URI。

允許認證資訊時,CORS 必須明確指定獲准的來源。* 不能與 Access-Control-Allow-Credentials: true 搭配使用。只有在符合允許清單後才反映來源,並加入 Vary: Origin,因為共享快取不得將含認證資訊的回應重複用於其他來源。

nginx
map $http_origin $cors_origin {
    default "";
    "~^https://(app|admin)\.example\.com$" $http_origin;
}

server {
    location /api/ {
        if ($cors_origin != "") {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Access-Control-Allow-Credentials true always;
            add_header Vary Origin always;
        }
        proxy_pass http://api_upstream;
    }
}

處理預檢 OPTIONS 請求時,請使用相同的允許清單,並明確列出獲准的方法與標頭。若上游以 Host 標頭進行授權或產生 URL,請勿將不受信任的 Host 標頭轉送至該上游;必要時,將 Host 設為預期的上游名稱。

經代理的 GET 請求 #

HTTP 將 GET 定義為安全方法,但應用程式仍可能讓其執行成本高昂。GET 請求中的內容沒有通用的既定語意,因此除非私有協定已定義並保護此行為,否則上游不應依賴 GET 主體。 上游故障後,Nginx 可以依 proxy_next_upstream 重試代理請求;因此,重試可能使上游應用程式多次執行同一個 GET。GET 處理常式不應產生副作用;請設定明確的重試策略,並對建立工作的操作使用冪等鍵或其他方法。

上游回應逾時無法證明上游未執行任何操作。請在代理與應用程式邊界記錄請求 ID,再以這些 ID 區分代理重試、用戶端重試或重複訊息。

代理預設會保留請求方法與主體,同時使用由 Nginx 控制的標頭建立新的上游請求。請明確設定授權資訊與轉送中繼資料:

nginx
location /api/ {
    proxy_set_header Host api.internal.example;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_pass http://api_upstream/;
}

由於此處的 proxy_pass 包含 URI,轉送前會將符合 /api/ 的正規化部分替換為 /。若 proxy_pass 不含 URI,Nginx 會依文件所述的重新寫入規則轉送請求 URI。變更尾端斜線時,請檢查此項差異。 除非上游協定也明確處理或捨棄原始請求主體,否則請勿以 proxy_method 將會修改狀態的請求轉換為 GET。

原始碼架構 #

Nginx 會啟動一個讀取設定並管理工作程序的主程序。工作程序使用 epoll 或 kqueue 等事件機制、在 ngx_connection_t 中維護連線,並透過處理常式階段推進請求處理。模組會宣告類型(核心、事件、HTTP、串流或郵件),並註冊指令、設定建立與合併函式,以及執行期處理常式。

每個模組都會公開一個全域 ngx_module_t 描述項。模組類型會告知 Nginx 哪些設定結構與初始化掛鉤屬於同一組。核心原始檔與額外模組會在建置期間連接,而少數模組可以建置為動態模組,稍後再載入。

HTTP 請求處理分為重新寫入、存取、內容與記錄等階段。剖析請求行與標頭後,請求會依序通過這些階段,因此模組可以掛接其中一個階段,而無須掌控整條請求路徑。

記錄內部機制 #

ngx_log_error_core() 是核心記錄函式。ngx_log_error() 與 ngx_log_debug() 等較高階巨集會包裝此函式,並遵循設定的記錄層級。只有建置與執行期設定皆允許時,偵錯記錄才會編譯並啟用。

error_log 指令控制錯誤的輸出位置。存取記錄彼此獨立,而且刻意不與錯誤記錄視為同一通道。在偵錯版本中,debug 記錄層級適合用來追蹤請求經過設定剖析、location 查找、階段處理常式與上游處理的過程。

請使用固定版本的原始碼發行版搭配 --with-debug 建置偵錯執行檔,以專用前綴執行,並檢查 debug 層級的錯誤記錄。閱讀原始碼時,先從設定剖析與循環初始化著手,再沿著事件迴圈、HTTP 請求行剖析、location 設定、階段處理常式與上游處理,追蹤一條已接受的連線。

ACdlmiiennitstfrlaotwosrswiogrnkaelrsc,onrfeilgoaduwpmosartskrteeerarmsworker

參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)