使用uWSGI、Nginx與Docker Compose部署Python Web Application(Flask, Django)—首部曲

林育銘
8 min readApr 1, 2021

在Python的Web框架中,我們經常以python manage.py runserver來啟動Flask或Django的專案。然而,在生產環境中,程序的部署推薦使用Gunicorn或uWSGI,來處理HTTP的動態請求。

Gunicorn與uWSGI作為Web Application(Flask、Django)與Web Server(Nginx)的通信協議。在生產環境下,前端傳來的請求不會直接訪問Web Application,而會以Nginx作為直接對外的窗口(反向代理),負責轉發HTTP的動態請求給Web Application以及直接訪問靜態文件,達到高併發高穩定的要求。

我們根據上述說明,簡單畫一張示意圖,來釐清這其中的概念:

前端會將Http請求發送給Nginx,再由Nginx負責轉發。如需涉及數據庫的操作等,Nginx會透過Socket形式,將請求轉發至uWSGI,再由uWSGI透過wsgi協議,調用Python的Web Application(Django, Flask);待Web Application處理完業務邏輯後,會將數據傳遞給uWSGI,Nginx接收到uWSGI的響應後再回覆給前端,便是Web框架常見的流程。

專案的目錄配置

demo/ <--當前位置
├── docker_develop/
│ ├── docker-compose.yml
│ ├── hotel.env
│ └── nginx/
│ ├── site_enabled/
│ │ └── default.conf
│ └── nginx.conf
├── Dockerfile
├── demo <-- 放置自己Web Application的專案
│ └── __init__.py
├── entrypoint.sh
├── manage.py
├── requirements.txt
└── uwsgi.ini

uWSGI的參數配置

首先,我們來配置uWSGI的參數vim uwsgi.ini

[uwsgi]module = manage:appchdir = /usr/src/appprocesses = 10
threads = 4
enable_threads = true
master = true
socket = :8700
socket-timeout = 30
harakiri = 30
listen = 4096
buffer-size = 65535
chmod-socket = 660
vacuum = true
log-slow = 3000
logto = /var/log/api.log
# Moniterstats = :5002

以上是筆者開發時經常使用到的參數配置,讓我們來了解一下上述參數的意義。

chdir為文件啟動的位置。socket則是uWSGI與Nginx通信的端口。

由於Python GIL(全局解釋器鎖)的存在,導致Web Application一次僅能處理一個前端請求,我們可以透過process參數的配置,實現多進程,以因應高併發的狀況發生。

再者,大部分Web Application都是IO密集型的操作,例如數據庫的增刪改查,將阻塞IO時間釋出給其他線程執行,提高整體性能,注意enable_thread必須配置為True,才會發揮多線程功能。

如配置stats參數,我們可以查看每個process被請求的次數、平均回覆的時間等;配置log-slowlogto參數,有助於程序性能優化;配置listen參數,設定uWSGI最多能同時接收Nginx請求次數。

我們可以將master設定為True,來啟動主進程,以便管理其他子進程,當終止主進程時,也會同時停止其他子進程。

我們也可以針對Nginx與uWSGI連線設定超時時間(socket-timeout參數),一旦超時狀況發生,Nginx便會自動斷開與uWSGI的連線,然而服務器仍會繼續處理該次請求,僅前端無法收到來自服務器的響應。我們也可以配置harakiri參數,設定uWSGI與Web Application的連線超時時間,如超時則該進程會自動中斷。

配置完畢後,我們可以在local測試uWSGI,首先確保環境中有uwsgi的package($ pip3 install uwsgi),接著我們就可以啟動uWSGI了。

$ uwsgi --ini uwsgi.ini

如想查看更多關於uWSGI的配置,可以自行瀏覽其文檔

Nginx的參數配置

再來,配置Nginx的相關設定$ vim docker_develop/nginx/nginx.conf

user  nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
use epoll;
worker_connections 1024;
multi_accept on;
}
http {
limit_req_zone $binary_remote_addr zone=byip:10m rate=3r/s;
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" '
'$request_time $upstream_response_time';
'"$http_user_agent" "$http_x_forwarded_for"' ;
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;
server_tokens off;
# proxy buffer
proxy_buffer_size 128k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
uwsgi_buffers 32 32k;
uwsgi_buffer_size 128k;
# Virtual Host Configs
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

worker_connections設置為1024,代表Nginx一次可處理1024個併發數。我們也可以至/var/log/nginx/error.log查看Nginx的錯誤日誌。

$ vim docker_develop/nginx/sites-enabled/default.conf

server {
listen 80 ;
server_name 0.0.0.0;
location / {
include uwsgi_params;
uwsgi_pass flask:8700;
uwsgi_read_timeout 30;
uwsgi_connect_timeout 30;
}
}

Nginx會監聽80 port,根據前端傳來的url(資源路徑),透過不同location的配置機制,將請求分發到不同的後端程序,或是直接讀取Nginx的靜態資源轉發給前端。以上面配置為例,當location為/時,Nginx會將請求轉發至Flask的Docker容器8700 port。

include uwsgi_params表示nginx使用默認的uwsgi配置;uwsgi_connect_timeout 表示nginx與uwsgi連接的超時時間;uwsgi_read_timeout 接收uWSGI應答的超時時間,默認為60秒。

至此便介紹完Python Web Application與前端的交互過程,以及uWSGI和Nginx的參數配置,下節將介紹如何透過Docker-Compose的工具,啟動Flask應用程序與Nginx,完成部署的動作,請讀者拭目以待。如讀者認為該篇文章有所助益,可多多分享並且拍手,表示對我的支持與鼓勵。

--

--