【Flask】 利用uWSGI和Nginx發佈Flask應用

  由於Flask比較容易上手,以前也拿flask寫過幾個小項目,不過當時天真地覺得只要在服務器上nohup跑一個python腳本就算是成功發佈了這個flask項目。實際上這還面臨不少問題,好比並發性很差,不支持異步(雖然也能夠在run裏面加上threaded之類的參數來解決,但終究不是正途)等等。真正通用的作法應該是用某些web容器來啓動項目。接下來講明作法,整個過程主要參考了這篇文章(https://segmentfault.com/a/1190000004294634)php

  我測試部署的系統是CentOS7 x86_64,環境搭建部分(包括安裝python,安裝flask以及flask相關依賴)的工做就跳過了。從安裝uWSGI開始講起。html

 

■  uwsgi的安裝和配置  python

  uWSGI是一個由python實現的web容器,能夠兼容性比較好地發佈Django,Flask等pythonweb框架的應用。由於本質上來講uwsgi是python的一個模塊,因此能夠用pip install uwsgi直接來安裝它。nginx

  安裝完成以後能夠在一個合適的目錄創建一個uwsgi服務器的配置文件。好比我選擇在項目的根目錄創建了一個uwsgiconfig.ini的文件。順便一提,除了ini格式的配置,uwsgi還支持json,xml等多種多樣的配置格式。這裏以ini格式爲例。web

  一個典型的配置文件以下:sql

[uwsgi]
socket = 127.0.0.1:5051
pythonpath = /home/wyz/flask
module = manage
wsgi-file = /home/wyz/flask/manage.py callable
= app processes = 4 threads = 2 daemonize = /home/wyz/flask/server.log

  依次解釋一下這些配置項。socket指出了一個套接字,至關於爲外界留出一個uwsgi服務器的接口。須要注意的是,socket不等於http。換句話說用這個配置起來的uwsgi服務器是沒法直接經過http請求成功訪問的,這一點後面還會提到,是遇到的一個坑。json

  pythonpath指出了項目的目錄,module指出了項目啓動腳本的名字而緊接着的wsgi-file指出了真正的腳本的文件名。callable指出的是具體執行.run方法的那個實體的名字,通常而言都是app=Flask(__name__)的因此這裏是app。processes和threads指出了啓動uwsgi服務器以後,服務器會打開幾個並行的進程,每一個進程會開幾條線程來等待處理請求,顯然這個數字應該合理,過小會使得處理性能很差而太大則會給服務器自己帶來太大負擔。daemonize項的出現表示把uwsgi服務器做爲後臺進程啓動,項的值指向一個文件代表後臺中的全部輸出都重定向到這個日誌中去。flask

  以上這些配置項都是一些最爲常見的配置項,實際上uwsgi還有不少不少配置。。除了寫一個配置文件的啓動方式以外,還有命令行的啓動方式,這裏就很少說了。請須要的本身百度。。【抱歉】segmentfault

  此外上面也說到此次碰到的一個坑,就是關於socket和http的差異。從概念上來講,socket自己不是協議而是一種具體的TCP/IP實現方式,而HTTP是一種協議且基於TCP/IP。具體到這個配置這裏來,若是我只配了socket = 127.0.0.1:5051的話,經過瀏覽器或者其餘HTTP手段是沒法成功訪問的。而在uwsgi這邊的日誌裏會提示請求包的長度超過了最大固定長度。另外一方面,若是配置的是http = 127.0.0.1:5051的話,那麼就能夠直接經過通常的http手段來訪問到目標。但這會引發nginx沒法正常工做。正確的作法應該是,若是有nginx在uwsgi以前做爲代理的話應該配socket,而若是想讓請求直接甩給uwsgi的話那麼就要配http。後端

  配置完成以後就能夠鍵入 uwsgi 配置文件.ini來啓動uwsgi,再查看日誌(若是配置了daemonize的話)若是最終沒有報錯,ps也能看到processes指定個數的uwsgi進程在跑的話說明成功啓動。若是直接把uwsgi做爲留給外部的鏈接接口發佈應用的話固然也能夠,可是通常而言咱們確定還要在uwsgi前面再加上一個nginx。nginx的好處在於能夠進行安全過濾,防DDOS攻擊,多臺機器的負載均衡等工做。

  關於uwsgi服務器的中止,官方文檔說能夠uwsgi -HUP之類的命令操做,可是這須要找到這個uwsgi的pid,目前爲止我都仍是很粗暴地killall -9 uwsgi了。。

■  nginx的安裝和配置

  最開始用yum install nginx裝了好多此仍是報缺乏libpcre.so.0的錯,網上搜了一通發現多是由於我用的是CentOS7版本的系統而yum源中仍是適用於CentOS6的包。因此不如去網上找個rpm包或者直接下個源碼包來編譯安裝。。。

   nginx經常使用命令:

  nginx  啓動nginx

  nginx -s stop/reload  中止nginx/重載配置文件

  nginx -v  查看版本

  nginx -t  測試配置文件是否有語法上的錯誤等

  安裝完成後默認的nginx的配置文件位於/etc/nginx/conf.d/default.conf,我直接修改了這個文件。在修改以前能夠考慮先備個份。若是須要指定配置文件開啓nginx能夠加入-c參數。其實nginx默認讀取的文件是/etc/nginx/nginx.conf,打開這個文件看看能夠看到在其http塊中有些include /etc/nginx/conf.d/*.conf,因此在那裏的default.conf能夠直接寫server塊。

  以前也瞭解過一點關於nginx的配置問題,其要義大概就是nginx的配置文件格式比較要緊,好比要有大括號,句尾有分號等等。另外以#開頭的行都是註釋,均可以不用管。在nginx的這個配置中咱們主要修改如下內容:

    server {
        listen       80;         //默認的web訪問端口
        server_name  xxxxxx;     //服務器名
        #charset koi8-r;
        access_log  /home/wyz/flask/logs/access.log;    //服務器接收的請求日誌,logs目錄若不存在須要建立,不然nginx報錯
        error_log  /home/wyz/flask/logs/error.log;         //錯誤日誌

        location / {

            include        uwsgi_params;     //這裏是導入的uwsgi配置

            uwsgi_pass     127.0.0.1:5051;   //須要和uwsgi的配置文件裏socket項的地址
                                             //相同,不然沒法讓uwsgi接收到請求。

            uwsgi_param UWSGI_CHDIR  /home/wyz/flask;     //項目根目錄

            uwsgi_param UWSGI_SCRIPT manage:app;     //啓動項目的主程序(在本地上運行
                                                     //這個主程序能夠在flask內置的
                                                     //服務器上訪問你的項目)
}
}

  這樣配置完後,當外部有一個80端口的請求送到本機時,先讓nginx開始處理。nginx進行一些處理以後轉發給這裏配置的uwsgi_pass地址,恰好傳送給uwsgi處理。再由uwsgi來調用項目中的代碼處理請求返回。再來回味一下上面那個坑,若是當時僅僅配了一個http項而沒有配置socket的話,就會致使一切容器啓動都順利,可是當我把請求發送給80端口的時候遲遲不來響應,直到超時。

   * 經網友提醒,這實際上是一個Nginx和uWSGI之間配置協同的一個問題。若是uWSGI直接經過HTTP方式對外提供服務,那麼nginx中須要配置proxy_pass,指出HTTP服務具體套接字,從而實現請求的轉發(參考zabbix安裝時的nginx配置就是這樣的)。而若是將uWSGI配置爲socket,經過socket對外提供服務(因爲socket不涉及具體的協議,外部無法直接經過uWSGI端口訪問服務也更加安全一些。好比能夠在nginx中配置一些URL的拒接防止sql注入之類的),那麼nginx配置就應該得是uwsgi_pass來實現請求的轉發。 proxy_pass配置的時候寫http://,即表示是走http協議的;uwsgi_pass的時候未指出協議,表示走socket。

  當應用開始運行起來以後,個人這個項目根目錄的結構是這樣的;

  其中access.log和error.log分別記錄了送到nginx處的請求的記錄以及nginx部分中發生的錯誤的記錄。項目的入口app.run被寫在manage.py中,server.log記錄的則是uwsgi服務器的運行情況。

  以上項目仍是一個很是簡單的flask項目,不知道隨着代碼變複雜起來這麼作來發布flask應用會不會遇到各類各樣的問題。。總以前途仍是險阻吶。

 

■  部署websocket項目時的坑

  不久前作了一個帶websocket的小flask項目,然而部署時歷經各類問題。。最後都仍是沒能徹底解決。

  首先是一個,由於要帶websocket因此咱們須要在uwsgi啓動的配置文件中寫上合適的配置項,好比像下面這個同樣:

[uwsgi]
project = /root/ICManage
pythonpath = /root/ICManage
wsgi-file = /root/ICManage/manage.py
chdir = %(project)
module = manage
callable = app

master = true
processes = 1
#threads = 2

socket = 127.0.0.1:5050
chmod-socket = 664

#buffer-size = 32768

http-websockets = 1
gevent = 1000
async = 30

daemonize = /home/hips/ICManage/uwsgi/logs/server.log

 

  project指出了項目目錄,%(project)是對已配置項project進行一個取值,設置master是首先開啓一個uwsgi的管理進程,而後由它開啓若干個worker子進程,當子進程掛掉的時候還會自動重啓。這些實際上是對上面通常性配置描述的一個補充,並非決定websocket特性的。

  決定websocket特性的則是http-websockets,gevent,async這些配置項,他們指出了經過這個配置文件啓動的uwsgi進程是支持websocket的(uwsgi版本在2.0以後纔開始支持websocket)。另外還有一個很重要的改動:processes改爲了1,而且註釋去掉了threads配置。若是不去掉threads,這會和gevent衝突,致使的現象就是經過nginx訪問uwsgi程序時總會返回502 bad gateway。若是processes設置大於1,那麼致使的現象就是socket通訊老是遲緩且沒有規律。這主要是由於websocket的通訊是要基於一個sessionid的,而每一個進程接受請求時給出的sessionid都不一樣。uwsgi在作均衡的時候可能把發向某一個進程的請求發給了另外一個進程,而那個進程顯然沒有處理這個請求的上下文,致使返回400 bad request,因此在socket通訊時老是會涌現出大量的400和502錯誤。

  把processes改爲1顯然不是一個萬全之策,如此,性能上就出現了問題,這個要如何解決還有待研究。

  而後貼出改形成兼容websocket以後的nginx配置,至少我是這麼配置啓動以後能夠正常運行:

server {
    listen       80;
    server_name  192.168.1.101;
    #charset koi8-r;
    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:5050;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

}

 

■  在一個nginx下部署多個應用的location配置簡單說明

  上述location配置能夠保證咱們直接訪問這個IP(端口默認是80)就能夠看到web應用響應的界面。可是有一個問題,若是這個機器上有好多應用呢?此時應該考慮在nginx的配置中體現出多應用的方法。一個簡單的辦法就是多加幾條location配置來把指向不一樣URI的訪問路由到不一樣的應用上去。

  然而這個過程並無說說的這麼簡單。好比沿用上面的例子,假如在這個nginx上咱們還要部署一個到zabbix的路由,那麼能夠把配置文件改爲這樣:(只寫location部分):

location ^~ / {
    include uwsgi_param;
    uwsgi_pass 127.0.0.1:5050;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

location ^~ /zabbix/ {
    proxy_pass http://127.0.0.1:8881/zabbix/;
    proxy_redirect default;
    proxy_set_header HOST $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

 

  把location /中間加上一個^~,是指出了URI從頭開始匹配「/」的將所有轉發到這個路由,固然/zabbix/開頭的URI因爲下面還配置了一條^~ /zabbix/,因此會轉發到zabbix下面去。這個匹配和轉發的詳細規則能夠學習下nginx的配置明細,就再也不多說。

  上面的location配置中,使用了include uwsgi_param,因此緊跟的配置項是uwsgi_pass,注意這個配置項無需也不能寫出http://和後面的URI,這也就意味着,原生請求的URI只能一一對應到uwsgi_pass設置的值的這個根URL上去。考慮的這邊下面配置了^~ /zabbix/,因此綜合來看,除了http://xxxx:xx/zabbix/以及其餘zabbix開頭的URI以外都會路由到5050端口的那個web應用中去,而且請求URI不會被nginx作任何加工,好比原生請求指向http://xxxx:xx/a/b/c/ 那麼最終路由到的地址就是127.0.0.1:5050/a/b/c/。這看起來彷佛理所固然,可是若是改爲location ^~ /fullpack/ 呢,此時若是原生請求是http://xxxx:xx/upload/,那麼最終路由到的是127.0.0.1:5050/fullpack/upload仍是127.0.0.1:5050/upload/呢?答案是後者,也就是說nginx未對URI作任何加工。

  相反的,看經過proxy_pass方法配置的location。在下面的配置中若是原生請求是http://xxxx:xx/zabbix/a/b/c/,那麼最終請求路由到的是127.0.0.1:8881/zabbix/a/b/c/,能夠當作將原生的URI,去掉了開頭的/zabbix/,而後再把剩餘部分拼接到127.0.0.1:8881/zabbix/後面,雖然這裏湊巧兩邊都是/zabbix/,可是若是把location的換成/zbx/,那麼就能夠發現,原生的/zbx/a/b/請求將會路由到8881端口的/zabbix/a/b/請求。這證實了nginx對proxy_pass方式的配置收到的URI是有處理的。

■  經過nginx訪問時自動加末尾斜槓的問題

  在上面的實驗中,其實我遇到了一個小坑。就是配置完nginx以後訪問每次都是404,通過緣由排查,發現是這麼回事:

  在後端代碼中,我寫的是@app.route('/info',methods=['GET','POST'])這樣的。當不使用uwsgi+nginx部署,而是用flask自帶的web服務器進行測試時,我訪問xxxx:xx/info,能夠訪問到界面。可是經過nginx訪問時,nginx會把全部末尾不帶斜槓的非文件類請求都加上斜槓,而且給出301迴應,而後重定向到有斜槓的URL下。這多是由於其餘一些比較經典的WEB開發語言中請求每每是一個文件如.php,.aspx,.html等,而python的框架其實是把一個「目錄」節點做爲一個html文件給出了。這就使得末尾要加上一個斜槓,才能讓nginx知道這是一個指向目錄的請求。

  解決的辦法也很簡單,經過瀏覽器直接發起GET請求的頁面(也就是必定要通過nginx訪問的),路由設置時記得加上末尾的斜槓就行了。由於不一樣過鍵盤打到瀏覽器地址欄這種方式的GET請求(好比頁面的一個超連接的href值,或者AJAX發起指向的URL)都是不會自動補齊斜槓的,因此其餘那些頁面也都不會受影響。另外加了斜槓的設置也能夠估計沒加斜槓的請求,好比我改爲@app.route('/info/')以後,瀏覽器地址欄裏打/info會自動補齊成/info/,而點擊頁面上href="/info"或者經過程序手段如requests.get('xxxx/info')也均可以訪問到那個頁面的。要是反過來,route('/info')而href="/info/"則不行。

相關文章
相關標籤/搜索