Flask+Gunicorn+Nginx是最經常使用的Flask部署方案,你們深究過爲什麼用這樣的搭配麼?html
Flask 是一個web框架,而非web server,直接用Flask拉起的web服務僅限於開發環境使用,生產環境不夠穩定,也沒法承受大量請求的併發,在生茶環境下須要使用服務器軟件來處理各類請求,如Gunicorn、 Nginx或Apache,而Gunicorn+Nginx的搭配,好處多多,一方面基於Nginx轉發Gunicorn服務,在生產環境下能補充Gunicorn服務在某些狀況下的不足,另外一方面,若是作一個Web網站,除了服務外,還有不少靜態文件須要被託管,這是Nginx的強項,也是Gunicorn不適合作的事情。因此,基於Flask開發的網站,部署時用Gunicorn和Nginx,是一個很好的選擇。前端
Nginx功能強大,使用Nginx有諸多好處,但用Nginx轉發Gunicorn服務,重點是解決「慢客戶端行爲」給服務器帶來的性能下降問題;另外,在互聯網上部署HTTP服務時,還要考慮的「快客戶端響應」、SSL處理和高併發等問題,而這些問題在Nginx上一併能搞定,因此在Gunicorn服務之上加一層Nginx反向代理,是個一舉多得的部署方案。python
服務器和客戶端的通訊,咱們簡略的分爲三個部分:request,request handling,和response,即客戶端向服務器發起請求,服務器端響應並處理請求,和將請求結果返回客戶端,這三個過程。nginx
一般,request handling這部分即服務端的計算,拼的是服務器的性能,處理是比較高效和穩定的,而request和response部分,影響因素比較多,若是這三個過程放到同一個進程中同步處理,若是request和response部分耗時比較多,會使計算資源被佔據並沒有法及時釋放,致使計算資源沒法有效利用,下降服務器的處理能力。git
上述「慢客戶端行爲」,指的就是request(或response)部分耗時比較多的狀況,Gunicorn剛好會把上面三個過程放到同一個進程中,當出現「慢客戶端行爲」時,效率很低:github
Gunicorn 是一個pre-forking的軟件,這類軟件對低延遲的通訊,如負載均衡或服務間的互相通訊,是很是有效的。但pre-forking系統的不足是,每一個通訊都會獨佔一個進程,當向服務器發出的請求多於服務器可用的進程時,因爲服務器端沒有更多進程響應新的請求,其響應效率會下降。web
對於Web網站或服務而言,因爲request和response延時是不可控的,咱們須要在考慮處理高延遲客戶端請求的狀況。這些請求會佔據服務器端的進程。當慢客戶端直接與服務通訊時,因爲慢客戶端請求會佔據進程,可用於處理新請求的進程就會減小,若是有不少慢客戶端請求把全部進程都佔據後,新的請求只能等待有進程被釋放掉後,獲得響應。另外,若是應用但願有更高的併發,服務器與客戶端的通訊要更高效,異步的通訊會比同步的通訊更有效。mongodb
Nginx這類異步的服務器軟件擅長用不多的內存和cpu開銷來處理大量的請求。因爲他們擅長於同時處理大量客戶端請求,因此慢客戶端請求對他們影響不大。就Nginx而言,如今通常的服務器硬件條件下,同時處理上萬個請求都不在話下。docker
因此把Nginx擋在pre-forking服務前面處理請求是一種很好的選擇。Nginx可以異步、高併發的響應客戶端request(慢客戶端請求對Nginx影響不大),Nginx一旦接收到的請求後馬上轉給Gunicorn服務處理,處理結果再由Nginx以response的形式發回給客戶端。這樣,整個服務端和客戶端的通訊,就由原來僅經過Gunicorn的同步通訊,變成了基於Nginx和Gunicorn的異步通訊,通訊效率和併發能力獲得大大提高。bash
對於網站而言,除了要考慮上面介紹的狀況,還要考慮各類靜態文件的託管問題。靜態文件既包括CSS、JavaScript等前端文件,也包括圖片、視頻和各種文檔等,因此靜態文件要麼可能會比較大,要麼會調用比較頻繁,靜態文件的託管功能,就是要保證各種靜態能正常的加載、預覽或下載,這其實就是Response耗時長的「慢客戶端行爲」。用Gunicorn託管靜態文件,也會嚴重影響Gunicorn的響應效率,而這偏偏又是Nginx擅長的工做,因此靜態文件的託管也交給Nginx搞定就好。
結合上一節的解釋,Flask網站如何部署也很明確了:
常見的服務器軟件是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中去影響服務器效率。
Gunicorn如何部署Flask網站,直接看Flask或Gunicorn官方文件便可,一般只要執行相似下面的一行命令:
/usr/local/bin/gunicorn -w 2 -b :4000 manage:app
用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; } }
其中,作反向代理的配置是:
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; }
我這裏對兩個文件夾的文件作了託管。
Docker具備一次部署,處處運行的好處,把上述傳統部署的方法,封裝到docker image中,而後配合Docker Compose編排服務,在實踐中更方便。
一般,鏡像中包含Flask網站的運行環境,而後把Gunicorn拉起服務做爲image的運行命令便可,好比,個人OctBlog的Dockerfile編寫以下:
# MAINTAINER Gevin <flyhigher139@gmail.com> # DOCKER-VERSION 18.03.0-ce, build 0520e24 FROM python:3.6.5-alpine3.7 LABEL maintainer="flyhigher139@gmail.com" 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"]
這裏,Gevin直接用了最小的Python-alpine鏡像做爲基礎鏡像,大大減小了即將構建的Flask應用鏡像的體積。對於alpine這種只有幾M的極簡image而言,不安裝其餘系統依賴,直接pip install uwsgi
就會報錯。
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; } }
這個配置文件和上一章節的惟一區別是,第10行的proxy_pass http://blog:8000/;
這裏的反向代理的服務爲blog
,是下面Docker-compose中配置的OctBlog網站服務。
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:
其中,爲了讓多個服務能互通,建立了自定義的network webnet
,爲了讓文件能在多個服務之間共享,公用了volume blog-static
。
基於上面的內容,觸類旁通,咱們也能夠梳理出Python 各種web框架作網站部署時的通常套路: