本文經過整理,轉載自Flask 應用如何部署
1. Why Flask+Gunicorn+Nginx
Flask+Gunicorn+Nginx是最常用的Flask部署方案,大家深究過爲何用這樣的搭配麼?
1.1 Why?
Flask 是一個web框架,而非web server,直接用Flask拉起的web服務僅限於開發環境使用,生產環境不夠穩定,也無法承受大量請求的併發,在生茶環境下需要使用服務器軟件來處理各種請求,如Gunicorn、 Nginx或Apache,而Gunicorn+Nginx的搭配,好處多多,一方面基於Nginx轉發Gunicorn服務,在生產環境下能補充Gunicorn服務在某些情況下的不足,另一方面,如果做一個Web網站,除了服務外,還有很多靜態文件需要被託管,這是Nginx的強項,也是Gunicorn不適合做的事情。所以,基於Flask開發的網站,部署時用Gunicorn和Nginx,是一個很好的選擇。
1.2 Anything More?
1、爲什麼需要Nginx轉發Gunicorn服務?
Nginx功能強大,使用Nginx有諸多好處,但用Nginx轉發Gunicorn服務,重點是解決“慢客戶端行爲”給服務器帶來的性能降低問題;另外,在互聯網上部署HTTP服務時,還要考慮的“快客戶端響應”、SSL處理和高併發等問題,而這些問題在Nginx上一併能搞定,所以在Gunicorn服務之上加一層Nginx反向代理,是個一舉多得的部署方案。
2、爲什麼會有“慢客戶端行爲”帶來的服務性能降低問題?
服務器和客戶端的通信,我們簡略的分爲三個部分:request,request handling,和response,即客戶端向服務器發起請求,服務器端響應並處理請求,和將請求結果返回客戶端,這三個過程。
通常,request handling這部分即服務端的計算,拼的是服務器的性能,處理是比較高效和穩定的,而request和response部分,影響因素比較多,如果這三個過程放到同一個進程中同步處理,如果request和response部分耗時比較多,會使計算資源被佔據並無法及時釋放,導致計算資源無法有效利用,降低服務器的處理能力。
上述“慢客戶端行爲”,指的就是request(或response)部分耗時比較多的情況,Gunicorn恰好會把上面三個過程放到同一個進程中,當出現“慢客戶端行爲”時,效率很低:
Gunicorn 是一個pre-forking的軟件,這類軟件對低延遲的通信,如負載均衡或服務間的互相通信,是非常有效的。但pre-forking系統的不足是,每個通信都會獨佔一個進程,當向服務器發出的請求多於服務器可用的進程時,由於服務器端沒有更多進程響應新的請求,其響應效率會降低。
對於Web網站或服務而言,由於request和response延時是不可控的,我們需要在考慮處理高延遲客戶端請求的情況。這些請求會佔據服務器端的進程。當慢客戶端直接與服務通信時,由於慢客戶端請求會佔據進程,可用於處理新請求的進程就會減少,如果有很多慢客戶端請求把所有進程都佔據後,新的請求只能等待有進程被釋放掉後,得到響應。另外,如果應用希望有更高的併發,服務器與客戶端的通信要更高效,異步的通信會比同步的通信更有效。
Nginx這類異步的服務器軟件擅長用很少的內存和cpu開銷來處理大量的請求。由於他們擅長於同時處理大量客戶端請求,所以慢客戶端請求對他們影響不大。就Nginx而言,現在一般的服務器硬件條件下,同時處理上萬個請求都不在話下。
所以把Nginx擋在pre-forking服務前面處理請求是一種很好的選擇。Nginx能夠異步、高併發的響應客戶端request(慢客戶端請求對Nginx影響不大),Nginx一旦接收到的請求後立刻轉給Gunicorn服務處理,處理結果再由Nginx以response的形式發回給客戶端。這樣,整個服務端和客戶端的通信,就由原來僅通過Gunicorn的同步通信,變成了基於Nginx和Gunicorn的異步通信,通信效率和併發能力得到大大提升。
對於網站而言,除了要考慮上面介紹的情況,還要考慮各種靜態文件的託管問題。靜態文件既包括CSS、JavaScript等前端文件,也包括圖片、視頻和各類文檔等,所以靜態文件要麼可能會比較大,要麼會調用比較頻繁,靜態文件的託管功能,就是要保證各類靜態能正常的加載、預覽或下載,這其實就是Response耗時長的“慢客戶端行爲”。用Gunicorn託管靜態文件,也會嚴重影響Gunicorn的響應效率,而這恰恰又是Nginx擅長的工作,所以靜態文件的託管也交給Nginx搞定就好。
2. Flask網站如何部署
結合上一節的解釋,Flask網站如何部署也很明確了:
- 用一個服務器軟件(如Gunicorn)把Flask寫好的應用拉起來
- 用Nginx給上一步拉起的應用做一個反向代理
- 網站涉及到的靜態文件,用Nginx做文件託管
常見的服務器軟件是Gunicorn和uWSGI,由於Gunicorn配置使用簡單,效率也不錯,Gunicorn拉起Flask網站的配置極爲簡單,所以通常用Gunicorn來部署Flask網站是最常見的部署方案。(另,Gevin個人還有一個喜歡Gunicorn的原因是,Gunicorn是純Python寫的,直接pip安裝即可,而uwsgi還要系統裝額外的依賴,這在與docker配合使用時,Gunicorn的簡單性尤爲突出)
對於靜態文件的託管,由於在開發階段通常會基於Flask框架做靜態文件託管的實現,所以當用Gunicorn拉起Flask網站時,網站已經實現了基於Gunicorn的文件託管功能,所以配置Nginx的靜態文件託管URL時,可以直接配置成與基於Gunicorn託管一致的文件路徑,這樣能簡化開發和部署的邏輯,而且由於Nginx比Gunicorn更靠外一層,客戶端請求靜態文件時,Nginx就直接返回Response了,不用擔心會請求到Gunicorn中去影響服務器效率。
2.1 Gunicorn
Gunicorn如何部署Flask網站,直接看Flask或Gunicorn官方文件即可,通常只要執行類似下面的一行命令:
/usr/local/bin/gunicorn -w 2 -b :4000 manage:app
- 1
其中/usr/local/bin/gunicorn
是gunicorn
安裝後的路徑
-w
表示當前服務使用的線程數
-b
表示當前拉起的服務的訪問地址,不寫默認爲localhost
,後面加上端口號
manage:app
有兩個點,前面的manage
是你的flask的啓動文件的路徑,後面不帶.py
後綴,冒號後面的是你想要設置的這個服務的實例名
2.2 Nginx
用Nginx做反向代理和託管靜態文件,也很簡單,我這裏提供一個可用Demo,更多玩法大家自行查閱Nginx文檔吧
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://localhost:8000/;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /media {
alias /usr/share/nginx/html/media;
}
location /static {
alias /usr/share/nginx/html/static;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
其中,做反向代理的配置是:
location / { proxy_pass http://localhost:8000/; proxy_redirect off;
proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
做靜態文件託管的配置是:
location /media { alias /usr/share/nginx/html/media; }
location /static { alias /usr/share/nginx/html/static; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
我這裏對兩個文件夾的文件做了託管。
3. 基於Docker的Flask網站部署
Docker具有一次部署,到處運行的好處,把上述傳統部署的方法,封裝到docker image中,然後配合Docker Compose編排服務,在實踐中更方便。
3.1 構建Flask網站的鏡像
通常,鏡像中包含Flask網站的運行環境,然後把Gunicorn拉起服務作爲image的運行命令即可,比如,我的OctBlog的Dockerfile編寫如下:
# MAINTAINER Gevin <[email protected]>
# DOCKER-VERSION 18.03.0-ce, build 0520e24FROM python:3.6.5-alpine3.7
LABEL maintainer="[email protected]"
RUN mkdir -p /usr/src/app &&
mkdir -p /var/log/gunicorn
WORKDIR /usr/src/app
COPY requirements.txt /usr/src/app/requirements.txt
RUN pip install --no-cache-dir gunicorn &&
pip install --no-cache-dir -r /usr/src/app/requirements.txt
COPY . /usr/src/app
ENV PORT 8000
EXPOSE 8000 5000
CMD ["/usr/local/bin/gunicorn", “-w”, “2”, “-b”, “:8000”, “manage:app”]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
這裏,Gevin直接用了最小的Python-alpine鏡像作爲基礎鏡像,大大減少了即將構建的Flask應用鏡像的體積。對於alpine這種只有幾M的極簡image而言,不安裝其他系統依賴,直接pip install uwsgi
就會報錯。
3.2 Nginx 相關的配置
Nginx上主要做反向代理和靜態文件託管,和上面的配置文件一致,如:
server { listen 80;
server_name localhost; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { proxy_pass http://blog:8000/; proxy_redirect off; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /media { alias /usr/share/nginx/html/media; } location /static { alias /usr/share/nginx/html/static; }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
這個配置文件和上一章節的唯一區別是,第10行的proxy_pass http://blog:8000/;
這裏的反向代理的服務爲blog,是下面Docker-compose中配置的OctBlog網站服務。
3.3 用Docker-compose編排服務
OctBlog的Docker-compose編排文件如下:
version: '3'
services:
blog:
# restart: always
image: gevin/octblog:0.4.1
volumes:
- blog-static:/usr/src/app/static
env_file: .env
networks:
- webnet
mongo:
# restart: always
image: mongo:3.2
volumes:
- /Users/gevin/projects/data/mongodb:/data/db
networks:
- webnet
blog-proxy:
# restart: always
image: nginx:stable-alpine
ports:
- “8080:80”
volumes:
- ./default.conf:/etc/nginx/conf.d/default.conf
- blog-static:/usr/share/nginx/html/static:ro
- blog-static:/usr/share/nginx/html/media:ro
networks:
- webnet
volumes:
blog-static:
networks:
webnet:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
其中,爲了讓多個服務能互通,創建了自定義的network webnet,爲了讓文件能在多個服務之間共享,公用了volume blog-static。
4. 其他Python web網站的部署
基於上面的內容,舉一反三,我們也可以梳理出Python 各類web框架做網站部署時的一般套路:
用一個Python WSGI HTTP Server來部署代碼,如:
Green Unicorn
uWSGI
mod_wsgi
CherryPy
- 1
- 2
- 3
- 4
- 5
- 6
- 7
用Nginx做反向代理
做好靜態文件的託管
5. 注:
服務器軟件除了Nginx外,還有Apache等其他選項,Gevin對其他服務器軟件部署Python網站理解不深入,所以本文並未涉及。
OctBlog的源碼託管在GitHub上,大家可以搜索flyhigher139/OctBlog這個repo查看
本文撰寫時參考了以下內容,大家可以做延伸擴展:
Why flask + nginx + gunicorn:
What benefit is added by using Gunicorn + Nginx + Flask?
Why is setting Nginx as a reverse proxy a good idea?
What are the differences between nginx and gunicorn?
Why do I need Nginx and something like Gunicorn?
About Preforking:
The Preforking Model
Nginx serve flask static files:
How to serve Flask static files using Nginx?
nginx + uwsgi - what is serving static files?
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-095d4a0b23.css" rel="stylesheet">
</div>