學習 Flask,寫完一個 Flask 應用需要部署的時候,就想着折騰自己的服務器。根據搜索的教程照做,對於原理一知半解,磕磕碰碰,只要運行起來了,謝天謝地然后不再折騰了,到下一次還需要部署時,這樣的過程就會重復一次。不知道多少人的膝蓋中箭了呢?我也這樣干過,這么做確實很蠢,所以我決定寫一篇 Flask+uwsgi+Nginx+Ubuntu 的部署教程,解答一些我自己在這個過程中的疑問,從原理到方案,以一個小白的角度,總結一下部署、運維這件事,應該對初學 Flask 需要部署的同學有些幫助。
環境簡介
Ubuntu
我使用的 Ubuntu 系統版本是 14.04,用過幾個 Linux 發行版,現在挑選系統的第一選擇基本就是 Ubuntu 了,因為 Ubuntu 有商業公司 Canonical 做開發維護;使用的人多,有龐大的社區支持;遇到問題容易解決。我折騰過很長時間的 Linux 系統,我對新手的建議是,不要把時間浪費在這上面,應該以解決實際問題為導向,踏實點提高編程能力。裝系統、優化系統、記各種酷炫的命令對於提高編程能力並沒有實際幫助。所以你問我資瓷不資瓷 Ubuntu,我當然是資瓷的啦,用 Ubuntu 當然也會遇到坑,但相比於其他系統會少一些,也會容易解決一點。事實上,Ubuntu 已經成為了服務器的首選,AWS 上被選擇最多的 Linux 發行版就是 Ubuntu。Quora 用的 Linux 發行版也是 Ubuntu,創始人 Adam D'Angelo 在這個回答里解釋了原因。總的來說,沒有特別的理由的話,Ubuntu 理應是首選,經驗多一些之后,如果對某個發行版感興趣,或者想要做一些特別的嘗試,跳出舒適區,試試其他系統也無妨。
uWSGI
我們知道 Flask 中自帶了 web server,通過 Werkzeug,我們可以搭建 WSGI 服務,運行我們的網站,但 Flask 是 Web 框架,並不是 Web 服務器,盡管 Werkzeug 很強大,但只能用於開發,不能用於生產,對於 Web 服務器,我們有更專業的選擇,那就是 uWSGI, uWSGI 是一個全站式的托管服務,它實現了應用服務器(支持多種編程語言)、代理、進程管理器、監視器。取名為 uWSGI 是因為它最早實現的是 Python 語言的 WSGI。
uWSGI 包括四個部分:
- uwsgi協議
- web server 內置支持協議模塊
- application 服務器協議支持模塊
- 進程控制程序
uWSGI 是 C 語言寫的,性能比較高。
推薦閱讀
WSGI, uWSGI, uwsgi 的區別
當我們部署完一個應用程序,瀏覽網頁時具體的過程是怎樣的呢?首先我們得有一個 Web 服務器來處理 HTTP 協議的內容,Web 服務器獲得客戶端的請求,交給應用程序,應用程序處理完,返回給 Web 服務器,這時 Web 服務器再返回給客戶端。Web 服務器與應用程序之間顯然要進行交互,這時就出現了很多 Web 服務器與應用程序之間交互的規范,最早出現的是 CGI,后來又出現了改進 CGI 性能的FasgCGI,Java 專用的 Servlet 規范,Python 專用的 WSGI 規范等等。有了統一標准,程序的可移植性就大大提高了。這里我們只介紹 WSGI。
WSGI 全稱是 Web Server Gateway Interface,也就是 Web 服務器網關接口,它是 Python 語言定義出來的 Web 服務器和 Web 應用程序之間的簡單而通用的接口,基於現存的 CGI 標准設計,后來在很多其他語言中也出現了類似的接口。 總的來說,WSGI 可以分為服務器和應用程序兩個部分,實際上可以將 WSGI 理解為服務器與應用程序之間的一座橋,橋的一邊是服務器,另一邊是應用程序。
按照 web 組件分類,WSGI 內部可以分為三類,web 應用程序,web 服務器,web 中間件。應用程序端的部分通過Python 語言的各種 Web 框架實現,比如 Flask,Django這些,有了框架,開發者就不需要處理 WSGI,框架會幫忙解決這些,開發者只需處理 HTTP 請求和響應,web 服務器的部分就要復雜一點,可以通過 uWSGI 實現,也可以用最常見的 Web 服務器,比如 Apache、Nginx,但這些 Web 服務器沒有內置 WSGI 的實現,是通過擴展完成的。如 Apache,通過擴展模塊 mod_wsgi 來支持WSGI,Nginx可以通過代理的方式,將請求封裝好,交給應用服務器,比如 uWSGI。uWSGI 可以完成 WSGI 的服務端,進程管理以及對應用的調用。WSGI 中間件的部分可以這樣理解:我們把 WSGI 看做橋,這個橋有兩個橋墩,一個是應用程序端,另一個是服務器端,那么橋面就是 WSGI 中間件,中間件同時具備服務器、應用程序端兩個角色,當然也需要同時遵守 WSGI 服務器和 WSGI 應用程序兩邊的限制和需要。更詳細的內容可以看PEP-333 中間件的描述
Flask 依賴的 Werkzeug 就是一個 WSGI 工具包,官方文檔的定義是 Werkzeug 是為 Python 設計的 HTTP和 WSGI 實用程序庫。我們需要注意的是,Flask 自帶的 Werkzeug 是用來開發的,並不能用於生產環境,Flask 是 Web 框架,而 Werkzeug 不是 Web框架,不是 Web 服務器,它只是一個 WSGI 工具包,它在 Flask 的作用是作為 Web 框架的底層庫,它方便了我們的開發。
我們將 uwsgi 和 uWSGI 放在一起講解。uWSGI 是一個 Web 服務器程序,WSGI,上面已經談到,是一種協議,uwsgi 也是一種協議,uWSGI 實現了 uwsgi、WSGI、http 等協議。 uwsgi 的介紹可以看這里,uwsgi 是 uWSGI 使用的一個自有的協議,它用4個字節來定義傳輸數據類型描述。盡管都是協議,uwsgi 和 WSGI 並沒有聯系,我們需要區分這兩個詞。
Nginx
Nginx 是高效的 Web 服務器和反向代理服務器,可以用作負載均衡(當有 n 個用戶訪問服務器時,可以實現分流,分擔服務器的壓力),與 Apache 相比,Nginx 支持高並發,可以支持百萬級的 TCP 連接,十萬級別的並發連接,部署簡單,內存消耗少,成本低,但 Nginx 的模塊沒有 Apache 豐富。Nginx 支持 uWSGI 的 uwsgi 協議,因此我們可以將 Nginx 與 uWSGI 結合起來,Nginx 通過 uwsgi_pass
將動態內容交給 uWSGI 處理。
官方文檔在這
最好的 Nginx 教程在這
uWSGI 和 Nginx 的關系
從上面的講解中,我們知道,uWSGI 可以起到 Web 服務器的作用,那么為什么有了 uWSGI 還需要 Nginx 呢?
最普遍的說法是 Nginx 對於處理靜態文件更有優勢,性能更好。其實如果是小網站,沒有靜態文件需要處理,只用 uWSGI 也是可以的,但加上 Nginx 這一層,優勢可以很具體:
-
對於運維來說比較方便,如果服務器被某個 IP 攻擊,在 Nginx 配置文件黑名單中添加這個 IP 即可,如果只用 uWSGI,那么就需要在代碼中修改了。另一方面,Nginx 是身經百戰的 Web 服務器了,在表現上 uWSGI 顯得更專業,比如說 uWSGI 在早期版本里是不支持 https 的,可以說 Nginx 更安全。
-
Nginx 的特點是能夠做負載均衡和 HTTP 緩存,如果不止一台服務器,Nginx 基本就是必選項了,通過 Nginx,將資源可以分配給不同的服務器節點,只有一台服務器,也能很好地提高性能,因為 Nginx 可以通過 headers 的Expires or E-Tag,gzip 壓縮等方式很好地處理靜態資源,畢竟是 C 語言寫的,調用的是 native 的函數,針對 I/O做了優化,對於動態資源來說,Nginx 還可以實現緩存的功能,配合 CDN 優化(這是 uWSGI 做不到的)。Nginx 支持epoll/kqueue 等高效網絡庫,能夠很好地處理高並發短連接請求,性能比 uWSGI 不知道高到哪里去了。
-
如果服務器主機上運行了PHP,Python 等語言寫的多個應用,都需要監聽80端口,這時候 Nginx 就是必選項了。因為我們需要一個轉發的服務。
所以說,Nginx 基本也是必選項。
部署准備工作
這里我假設我們拿到的是一台全新的服務器。 一般來說,Linux 系統都會預裝 Python 的,但不一定裝了 easy_install 工具,我們可以通過 apt-get install python-setuptools
來安裝 easy_install,再通過 easy_install 安裝 pip。
搞定 Python 環境
$ sudo apt-get install python-setuptools
$ sudo easy_install pip
我們也可以直接裝 pip:
$ sudo apt-get install python-pip
這樣,我們就可以通過 pip 安裝 virtualenv,為 flask 項目構建虛擬環境。
$ sudo pip install virtualenv
Nginx
$ sudo apt-get install nginx
啟動 nginx 的方法:
$ sudo /etc/init.d/nginx start
這時候在瀏覽器地址欄輸入服務器的 ip 地址,看到下面的頁面就表明 Nginx 已經啟動了:
安裝 uWSGI
在安裝 uWSGI 前,需要解決 uWSGI 的依賴問題,因為 uWSGI 是一個 C 語言寫的應用,所以我們需要 C 編譯器,以及 python 開發相關組件:
$ sudo apt-get install build-essential python-dev
$ sudo pip install uwsgi
到這,我們就安裝好了 uWSGI,
開干
首先,我們把應用程序上傳到服務器中,我在用 git 管理項目,所以只需要 git clone 一下就可以了:
$ git clone http://url/of/you/git/repo
如果你需要從本地上傳項目文件,可以用 scp 命令,這里就不啰嗦用法了。總之我們將項目文件放到服務器,然后就可以用 virtualenv 管理 Python 環境:
$ virtualenv ENV
$ source ENV/bin/activate # 激活虛擬環境
$ pip install -r requirement.txt # 解決依賴問題
$ deactivate # 退出依賴環境
這里就用 Flask 的7行代碼做示例吧,我新建了一個文件夾,名為 helloflask,將下面的內容:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5001)
保存為hello.py,運行試試,在瀏覽器輸入服務器公網 ip 地址,加端口號5001就可以看到結果。
好了,現在我們用 Nginx 來承擔 Web 服務。
刪掉 Nginx 的默認配置文件:
$ sudo rm /etc/nginx/sites-enabled/default
有心的話,其實可以從 Nginx 默認配置中了解一些配置參數,當然最靠譜的途徑還是看 Nginx 的文檔。這里只簡單嘗試 Nginx,下面給出一個簡單的配置:
server {
listen 80;
server_name your.website.url
charset utf-8;
client_max_body_size 75M;
location / { try_files $uri @yourapplication; }
location @yourapplication {
include uwsgi_params;
uwsgi_pass unix:/home/frank/Documents/helloflask/helloflask_uwsgi.sock;
}
}
我們可以將上述內容保存為 helloflask_nginx.conf,稍作解釋:server_name 可以是域名,也可以寫 ip 地址,uwsgi_pass 是表明 Nginx 與 uwsgi 的交流方式,我這里用的是 sock 文件,當然你也可以用指定端口號的形式,具體可以看這里。將 Nginx 配置文件用軟鏈接鏈接到 Nginx 配置文件夾中:
sudo ln -s /home/frank/Documents/helloflask/helloflask_nginx.conf /etc/nginx/conf.d/
重啟 Nginx:
sudo /etc/init.d/nginx restart
這時刷新一下之前打開的服務器公網 ip(或綁定的域名),這時看到的就不是「Welcome to Nginx」,而是「502 Bad Way」,因為我們還沒有啟動 uWSGI,現在我們將下面的內容保存為 helloflask_uwsgi.ini(用 xml 的格式也是可以的,具體可以看文檔):
#application's base folder
base = /home/frank/Documents/helloflask
#python module to import
app = hello
module = %(app)
home = %(base)/ENV
pythonpath = %(base)
#socket file's location
socket = /home/frank/Documents/helloflask/%n.sock
#permissions for the socket file
chmod-socket = 666
#the variable that holds a flask application inside the module imported at line #6
callable = app
#location of log files
logto = /home/frank/Documents/helloflask/%n.log
稍稍解釋一下,socket 指定的是與 nginx 進行通信的端口文件。其他的參數,如線程數,處理器數等,可以查看文檔后進行配置。上面的內容都是可以通過 uwsgi 命令的參數指定的,在命令行中敲入一行命令就可以了,為了「可持續發展」,當然是用文件保存下來比較好。
通過 uwsgi 命令,--ini 參數:
$ uwsgi --ini helloflask_uwsgi.ini &
指定配置文件,后台運行 uwsgi, 這時再刷新一下之前打開的頁面,就可以看到應用正常運行了。
我嘗試了在一台服務器上運行多個應用,其實只需要改一下文件名,分別處理 uWSGI 和 Nginx 的配置文件即可(Nginx 的配置,可以寫在同一個文件中,寫兩個 server 就行了)
常用命令
nginx 常用命令
啟動命令:
$ sudo nginx
或
$ sudo /usr/sbin/nginx
停止 nginx
$ sudo nginx -s stop
平滑啟動 nginx
sudo nginx -s reload
所謂平滑啟動就是在不停止 nginx 的情況下,重啟 nginx,重新加載配置文件,用新的工作進程代替舊的工作進程。
總結
曾經玩過 PHP,相比於 PHP 的幾乎一鍵式部署,Python 的部署確實要繁瑣很多,但 Python 的強大之處在於語言簡潔優雅,畢竟人生苦短,有得便有失,不過我相信這個繁瑣是暫時的。
最后給出的一個簡單的示例,其實是不夠規范的,比如應用文件應該放在 /var/www/ 下,log 文件應該放到系統的 log 文件夾下等等,這個只是簡單示例,更多配置內容,我們應該通過 uWSGI、Nginx 的文檔學習。
參考資料: