如何為 Flask Web 應用配置 Nginx

lsvih發表於2018-02-06

如何為 Flask Web 應用配置 Nginx

簡介

在本文中,我將介紹什麼是 Nginx 以及如何為 Flask Web 應用配置 Nginx。本文是《部署 Flask 應用》系列文章的一部分。我曾找到過多份關於 Nginx 及其配置的文章,但我希望能更深入其細節,瞭解如何使用 Nginx 為 Flask Web 應用服務以及如何為此進行配置。Nginx 的配置檔案有點讓人困惑,因為大多數的文件僅僅是簡單羅列了一個配置檔案,而沒有對配置中每一步做了什麼進行任何解釋。希望本文能讓你清晰地理解如何為你的應用配置 Nginx。

什麼是 Nginx?

在 Nginx(發音為“engine-X”)的官網中,有著這個工具的概要描述:

Nginx 是一款免費、開源、高效能的 HTTP 伺服器以及反向代理,同時也可以作為 IMAP/POP3 代理伺服器。Nginx 以其高效能、穩定性、豐富的功能、簡單的配置、低資源消耗而聞名。

我們可以擴充理解此說明…… Nginx 是一個可以為你的 Web 應用處理 HTTP 請求的伺服器。對於典型的 Web 應用,Nginx 可以配置為 HTTP 請求進行以下操作:

  • 將請求 反向代理 至上游伺服器(例如 Gunicorn、uWsgi、Apache 等)。
  • 為靜態資源(Javascript 檔案、CSS 檔案、影象、文件、靜態 HTML 檔案)提供服務。

同時 Nginx 也提供了負載均衡功能,可以讓多個上游伺服器為請求提供服務,不過在本文中暫不討論此功能。

下圖為描述 Nginx 如何為 Flask Web 應用提供服務的簡圖:

生產環境中的 Nginx

Nginx 會處理來自因特網(比如來自你應用的使用者)的 Http 請求。根據你對 Nginx 的配置,它可以直接提供並向請求源返回靜態內容(Javascript 檔案、CSS 檔案、影象、文件、靜態 HTML 檔案)。此外,它也能將請求反向代理至 WSGI(Web Server Gateway Interface)以讓你在 Flask Web 應用中生成動態內容(HTML)並返回給使用者。

上面的示意圖假定使用者使用了 Docker,但不使用 Docker 時 Nginx 的配置也與此十分相似(僅僅省略了圖中容器的概念)。

為什麼你需要 Nginx 與 Gunicorn?

Nginx 作為一個 HTTP 伺服器,在許多應用中都被使用:列表。它提供了許多的功能,但無法直接為 Flask 應用提供服務。而 Gunicorn 可以做到這一點。Nginx 收到 HTTP 請求,並將其傳遞給 Gunicorn 交由你的 Flask 應用進行處理(比如你在 view.py 中定義的路由)。Gunicorn 是一個 WSGI 伺服器,可以處理 HTTP 請求,並將它們通過路由交給任何支援 WSGI 的 python 應用處理(比如 Flask、Django、Pyramid 等)。

Nginx 配置檔案的結構

注意:本文應用的是 Nginx v1.11.3,配置檔案所在的位置根據你 Nginx 版本的不同會有所變化,比如 /opt/nginx/conf/。

根據你安裝、使用 Nginx 方式的不同,配置檔案的結構會略有不同。大多數的配置結構如下所示:

結構 1

如果你使用的是從原始碼編譯得到的 Nginx 或者官方的 Docker 映象,那麼配置檔案在 /etc/nginx/ 中,主配置檔案為 /etc/nginx/nginx.conf。在 /etc/nginx/nginx.conf 的最下面的一行會將位於 /etc/nginx/conf.d/ 目錄下的其餘配置檔案內容載入配置中:

  • include /etc/nginx/conf.d/*.conf;

結構 2

如果你是通過包管理器(比如 Ubuntu 的 apt-get)安裝的 Nginx,那麼你的 /etc/nginx/ 下會有下面兩個子目錄:

  • sites-available – 包含為多個網站準備的多個配置檔案。
  • sites-enabled – 包含一個指向 sites-available 目錄中配置檔案的軟連結。

這兩個目錄繼承於 Apache,將應用於 Nginx 的配置。

由於我的 Flask 應用使用的是 Docker 部署,因此在本文將主要關注上面的結構 1。

Nginx 的配置

Nginx 的頂層配置檔案是 nginx.conf。Nginx 接受多層級的配置檔案,這也使得使用者可以針對自己的應用進行彈性的配置。如需瞭解配置檔案中各引數的詳細資訊,可以參閱 Nginx 官方文件

在 Nginx 中,由配置塊(block)來組織各個配置引數。以下為在本文中我們將提到的配置塊:

  • Main – 定義於 nginx.conf(所有不屬於配置塊的引數均屬 Main 塊)
  • Events – 定義於 nginx.conf
  • Http – 定義於 nginx.conf
  • Server – 定義於 application_name.conf

將這些配置塊拆分至不同的檔案,可以讓你在 nginx.conf 中定義 Nginx 的高階別配置,在其它的 *.conf 檔案中為你的應用定義虛擬主機或伺服器的引數。

nginx.conf 詳細說明

安裝 Nginx 時自帶的預設 nginx.conf 檔案可以適用於大多數伺服器的初步配置。讓我們仔細探查 nginx.conf 的內容,並思考如何擴充這裡的預設設定。

Main 部分

nginx.conf 的 main 配置塊(即那些不在配置塊中的引數)為:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
複製程式碼

第一個引數(user)將定義 Nginx 伺服器的擁有者以及執行使用者。當 Nginx 通過 Docker 容器執行時,使用預設值就夠了。

第二個引數(worker_processes)定義了 worker processes(工作程式)的數量。此引數推薦的預設值為當前伺服器使用核心的數量。對於基礎的虛擬私有伺服器(VPS)來說,預設值 1 就是個不錯的選擇。當你擴充 VPS 效能時可以增加這個數字。

第三個引數(error_log)定義了錯誤日誌在檔案系統中存放的位置,並能額外定義一個引數來規定需要記錄日誌的最小錯誤等級。這個引數使用預設值即可。

第四個引數(pid)定義了用於儲存 Nginx 主程式 pid 的檔案位置。這個引數使用預設值即可。

events 配置塊

events 配置塊定義了一些會影響連線處理的引數。它也是 Nginx.conf 檔案中第一個配置塊:

events {
    worker_connections  1024;
}
複製程式碼

在這個配置塊中僅有一個單獨的引數(worker_connections),定義了工作程式可以開啟的最大併發連線數。預設值定義了總共可用 1024 個連線,無需更改(但你需要計算使用者請求站點及請求 WSGI 伺服器的連線數)。

http 配置塊

http 配置塊定義了一些關於 Nginx 如何處理 HTTP Web 流量的引數。它是 nginx.conf 檔案中第二個配置塊:

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
複製程式碼

第一個引數(include)指定了需要引入的配置檔案,在此引入的是位於 /etc/nginx/ 的 mime.types 檔案,這個檔案定義了各種 Nginx 支援的檔案型別。此引數應該保持預設值。

第二個引數(default_type)指定了預設給使用者返回的檔案型別。對於 Flask 應用來說,返回的是動態生成的 HTML 檔案,因此這個引數應改為 default_type text/html;

第三個引數(log_format)指定了日誌的格式,應當保持預設值。

第四個引數(access_log)指定了 Nginx 日誌的訪問位置,應當保持預設值。

第五個引數(send_file)以及第六個引數(tcp_nopush)稍微有點複雜。可以參閱《優化 Nginx》一文來了解這些引數(包括 tcp_nodelay)的詳細情況。由於我們打算用 Nginx 來傳遞靜態內容,因此可以這麼設定這些引數:

    sendfile        on;
    tcp_nopush     on;
    tcp_nodelay    on;
複製程式碼

第七個引數(keepalive_timeout)定義了與客戶端保持連線的超時時長,應當保持預設值。

第八個引數(gzip)定義了 gzip 壓縮演算法的使用方法,以減少傳輸資料量。雖然資料量減少了,但也因此增加平臺在壓縮過程中的效能消耗,好處兩兩抵消,因此保持它的預設值(off)。

第九個,也是最後一個引數(include)定義了位於 /etc/nginx/conf.d/ 下字尾名為 .conf 的其它配置檔案。現在我們將使用這些配置檔案定義靜態內容伺服器以及 WSGI 伺服器的反向代理。

nginx.conf 的最終配置

在 nginx.conf 預設設定之上,我們需要根據需要調整一些引數(並加上註釋),下面為最終版本的 nginx.conf:

# Define the user that will own and run the Nginx server
user  nginx;
# Define the number of worker processes; recommended value is the number of
# cores that are being used by your server
worker_processes  1;

# Define the location on the file system of the error log, plus the minimum
# severity to log messages for
error_log  /var/log/nginx/error.log warn;
# Define the file that will store the process ID of the main NGINX process
pid        /var/run/nginx.pid;

# events block defines the parameters that affect connection processing.
events {
   # Define the maximum number of simultaneous connections that can be opened by a worker process
   worker_connections  1024;
}

# http block defines the parameters for how NGINX should handle HTTP web traffic
http {
   # Include the file defining the list of file types that are supported by NGINX
   include       /etc/nginx/mime.types;
   # Define the default file type that is returned to the user
   default_type  text/html;

   # Define the format of log messages.
   log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

   # Define the location of the log of access attempts to NGINX
   access_log  /var/log/nginx/access.log  main;

   # Define the parameters to optimize the delivery of static content
   sendfile        on;
   tcp_nopush     on;
   tcp_nodelay    on;

   # Define the timeout value for keep-alive connections with the client
   keepalive_timeout  65;

   # Define the usage of the gzip compression algorithm to reduce the amount of data to transmit
   #gzip  on;

   # Include additional parameters for virtual host(s)/server(s)
   include /etc/nginx/conf.d/*.conf;
}
複製程式碼
為靜態內容部署及反向代理配置 Nginx

如果你檢視預設的 /etc/nginx/conf.g/default.conf,可以看到它提供了一個簡單的伺服器配置塊,並給了許多取消註釋即可使用的可選配置。我們不會挨個去研究這個檔案中的配置,而是直接探討對於我們部署靜態內容以及 WSGI 反向代理有用的關鍵引數。以下是推薦的 application_name.conf 配置:

# Define the parameters for a specific virtual host/server
server {
   # Define the directory where the contents being requested are stored
   # root /usr/src/app/project/;

   # Define the default page that will be served If no page was requested
   # (ie. if www.kennedyfamilyrecipes.com is requested)
   # index index.html;

   # Define the server name, IP address, and/or port of the server
   listen 80;
   # server_name xxx.yyy.zzz.aaa

   # Define the specified charset to the “Content-Type” response header field
   charset utf-8;

   # Configure NGINX to deliver static content from the specified folder
   location /static {
       alias /usr/src/app/project/static;
   }

   # Configure NGINX to reverse proxy HTTP requests to the upstream server (Gunicorn (WSGI server))
   location / {
       # Define the location of the proxy server to send the request to
       proxy_pass http://web:8000;

       # Redefine the header fields that NGINX sends to the upstream server
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

       # Define the maximum file size on file uploads
       client_max_body_size 5M;
   }
}
複製程式碼

伺服器配置塊為特定的虛擬主機或伺服器定義了引數。通常為你在 VPS 上部署的單個 Web 應用。

第一個引數(root)定義了被請求的內容所儲存的位置。當 Nginx 收到使用者請求時,它便會在此目錄中查詢。由於在預設的”/“路徑中定義過了,因此可以註釋掉這個不必要的引數。

第二個引數(index)定義了在請求未指定頁面時(比如訪問 www.kennedyfamilyrecipes.com)所得到的預設頁面。由於我們使用的是 Flask Web 應用生成的動態內容,因此需要註釋掉這個引數。

前兩個引數(root 和 index)都包含在此配置檔案中,在一些情況下可以用於 Nginx 的配置。

第三個引數(server_name)和第四個引數(listen)需要一同使用。如果你的 Web 應用程式已經部署好了,那麼你需要設定這些引數為:(注,埠預設為 80,此時不需要填)

server {
   …
   Listen 192.241.229.181;
   …
}
複製程式碼

如果你除了 www.kennedyfamilyrecipes.com 之外還要部署另一個 Flask 應用 blog.kennedyfamilyrecipes.com,那麼你需要將”server“配置塊拆開,分別配置”user_name“和”listen“:

server {
    listen 80;
    server_name *.kennedyfamilyrecipes.com;

    . . .

}

server {
    listen 80;
    server_name blog.kennedyfamilyrecipes.com;

    . . .

}
複製程式碼

Nginx 將選擇最匹配請求的”server_name“。也就是說對”blog.kennedyfamilyrecipes.com“的請求會優先匹配”blog.kennedyfamilyrecipes.com“而不是”*.kennedyfamilyrecipes.com“。

第五個引數(charset)定義了響應頭”Content-Type“的字符集值,應當設定為”utf-8“。

第一個”location“配置塊定義了 Nginx 需要遞送位於以下位置的靜態內容:

  location /static {
       alias /usr/src/app/project/static;
   }
複製程式碼

location 配置塊定義瞭如何處理請求的 URI(域名或 IP、埠號之後的部分)。在這第一個 location 配置塊(/static)中,我們定義了 Nginx 將會處理來自 www.kennedyfamilyrecipes.com/static/ 的請求,檢索位於 /usr/src/app/project/static 目錄下的檔案。例如,請求 www.kennedyfamilyrecipes.com/static/img/img_1203.jpg 將會返回位於 /usr/src/app/project/static/img/img_1203.jpg 的圖片檔案。如果檔案不存在,則向使用者返回 404 錯誤碼(NOT FOUND)。

第二個 location 配置塊("/")定義反向代理。這個 location 配置塊會定義 Nginx 如何將請求傳遞給 我們的 Flask 應用介面所在的 WSGI(Gunicorn)伺服器。仔細看看其中的每個引數:

   location / {
       proxy_pass http://web:8000;
       proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       client_max_body_size 5M;
   }
複製程式碼

第一個引數(proxy_pass)定義了接收轉發請求的代理伺服器的位置。如果你想將請求轉發至本機的伺服器時可以使用:

proxy_pass http://localhost:8000/;
複製程式碼

如果你希望將請求轉發給指定的 Unix socket 時(比如和 Nginx 執行在同一臺機器中的 Gunicorn 伺服器),可以使用:

proxy_pass http://unix:/tmp/backend.socket:/
複製程式碼

如果你使用 Docker 容器執行的 Nginx,希望與容器中的 Gunicorn 進行通訊,那麼可以直接使用執行 Gunicorn 的容器名稱:

proxy_pass http://web:8000;
複製程式碼

第二個引數(proxy_pass_header)可以讓你重新定義發往上游伺服器(比如 Gunicorn)的請求的頭部。這個引數可以進行以下四次設定:

  • Nginx 伺服器的名稱及埠(Host $host)
  • 原始客戶端請求的模式(比如是 http 請求還是 https 請求)(X-Forwarded-Proto $scheme)
  • 使用者的 IP 地址(X-Real-IP $remote_addr)
  • 至當前節點位置,客戶端經過的所有代理的 IP 地址(X-Forwarded-For $proxy_add_x_forwarded_for)

第三個引數(client_max_body_size)定義了檔案上傳允許的最大大小,對於需要上傳檔案的 Web 應用來說非常重要。由於影象大小一般在 2 MB 內,因此在這兒設定 5 MB 基本上可以滿足任何影象。

總結

本文介紹了什麼是 Nginx 伺服器,以及如何為一個 Flask 應用對其進行配置。Nginx 是大多數 Web 應用的關鍵元件,它為使用者提供靜態內容、反向代理請求至上游伺服器(在我們的 Flask Web 應用中是 WSGI),以及負載均衡(本文未提及)。希望看完本文後你能更輕鬆地理解 Nginx 的配置!

引用資料

How to Configure NGINX (Linode)

NGINX Wiki

NGINX Pitfalls and Common Mistakes

How to Configure the NGINX Web Server on a VPS (DigitalOcean)

Understanding NGINX Server and Location Block Selection Algorithms (DigitalOcean)

NGINX Optimization: Understanding sendfile, tcp_nodelay, and tcp_nopush


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章