Flask 應用如何部署

Gevin發表於2018-07-05

how-to-deploy-flask-apps

本文同步發表於我的公眾號Gevin View,可以點選此連結檢視

我原來寫過一篇《基於nginx和uWSGI在Ubuntu上部署Django》的文章,很受歡迎。這是個指導開發者一步一步部署的教程,雖然基於此,有心的開發者能悟出一些更加深入的內容,但文章本身並沒有點透,可能有不少新手看完文章,只能知其然而不知其所以然。

Flask部署的非常簡單,網上搜尋如何部署Flask應用,能搜到中英文的很多資料,但基本上也都幾句話帶過,這讓很多稍有經驗的“新手”很沒底。

所以Gevin前段時間仔細研究了一些相關資料,寫了這篇比較深入的介紹Flask部署的文章,既為Flask(並推廣到其他Python web框架)部署做了更加深入了闡述,也算是對上面Django部署文章在另一個維度上的補充。希望對大家能有所幫助。

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網站如何部署也很明確了:

  1. 用一個伺服器軟體(如Gunicorn)把Flask寫好的應用拉起來
  2. 用Nginx給上一步拉起的應用做一個反向代理
  3. 網站涉及到的靜態檔案,用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

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;  
    }
}

其中,做反向代理的配置是:

    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;  
    }

我這裡對兩個資料夾的檔案做了託管。

3. 基於Docker的Flask網站部署

Docker具有一次部署,到處執行的好處,把上述傳統部署的方法,封裝到docker image中,然後配合Docker Compose編排服務,在實踐中更方便。

3.1 構建Flask網站的映象

通常,映象中包含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就會報錯。

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;  
    }
}

這個配置檔案和上一章節的唯一區別是,第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:

其中,為了讓多個服務能互通,建立了自定義的network webnet,為了讓檔案能在多個服務之間共享,公用了volume blog-static

4. 其他Python web網站的部署

基於上面的內容,舉一反三,我們也可以梳理出Python 各類web框架做網站部署時的一般套路:

  1. 用一個Python WSGI HTTP Server來部署程式碼,如:
  2. 用Nginx做反向代理
  3. 做好靜態檔案的託管

5. 注:

  1. 伺服器軟體除了Nginx外,還有Apache等其他選項,Gevin對其他伺服器軟體部署Python網站理解不深入,所以本文並未涉及。
  2. OctBlog的原始碼託管在GitHub上,大家可以搜尋flyhigher139/OctBlog這個repo檢視

本文撰寫時參考了以下內容,大家可以做延伸擴充套件:

Why flask + nginx + gunicorn:

About Preforking:

Nginx serve flask static files:

最後

前段時間,『極客時間』的專欄《從0開始學架構》,第18課《單伺服器高效能模式:PPC與TPC》,對pre-fork 及相關知識有更深的講解,對此感興趣的看我在本文留言區的廣告吧


注:轉載本文,請與Gevin聯絡




如果您覺得Gevin的文章有價值,就請Gevin喝杯茶吧!

|

歡迎關注我的微信公眾賬號

Flask 應用如何部署

相關文章