上雲連載5:使用 Nginx + uWSGI 部署 Flask 應用
本文是「上雲連載」系列第五篇,將以編程派網站為例,介紹如何在騰訊雲 CVM 上部署 Flask 應用。具體來說,是如何在 Debian 8 系統下使用 uWSGI 和 Nginx 部署一個 Flask 應用,其中 Nginx 的作用是前端反向代理。
我在這裡列出的是部署編程派網站時的大致操作步驟,僅供大家參考。
準備工作
在開始之前,你應該已經創建了一個騰訊雲 CVM 實例(Debian 8 操作系統),或者是其他任意雲伺服器,並按照上雲連載2 中所述配置好了一個非 root 用戶。該用戶應該具備 sudo 許可權。
獲取應用代碼庫
首先你應該有一個可以運行的 Flask 應用。當然,你也可以用編程派網站的代碼庫做演示。
我在部署時,將應用代碼放置在了 /var/www/ 目錄下。輸入如下命令創建該目錄:
mkdir -p /var/www
可以直接從 Github 上克隆應用代碼庫:
cd /var/wwwgit clone https://github.com/bingjin/codingpy.git
也可以通過上雲連載3中搭建的 Git 伺服器獲取。
為了避免每次都要登陸伺服器上手動拉取,我們在中央代碼庫中設置 post-receive 鉤子(hook)。每次本地推送新的更新到伺服器後,會自動更新目標代碼庫的代碼。
在不需要多人合作的前提下,不必搭建 Git 伺服器。
不過要先手動在 /var/www/ 下創建一個工作目錄:
cd /var/www && mkdir codingpy
然後進入 codingpy.git 目錄下的 hooks 目錄,創建 post-receive 文件:
sudo su - gitcd repositories/codingpy.git/hooks/vim post-receive
我使用了 https://gist.github.com/thomasfr/9691385 中提供的方案,你也可以根據應用的實際情況進行修改。
保存 post-receive 文件後,執行:
chmod +x post-receive
之後,每次本地推送更新時,/var/www/codingpy/ 目錄下的文件也會自動更新。
安裝所需組件
編程派的 Flask 應用使用的是 Python 3,採用 Nignx 作為反向代理伺服器,redis 用來做緩存。我們使用 apt-get 命令進行安裝:
sudo apt-get install python3-pip python3-dev nginx redis
python3-pip 將安裝 pip 包管理器,用於管理 Flask 應用的依賴包。在安裝 python3-pip 時會自動安裝 Python 3。python3-dev 將用於構建 uWSGI 包。
如果你的應用時基於 Python 2 的,請執行:
sudo apt-get install python-pip python-dev nginx redis
新建虛擬環境
如果只在生產伺服器上部署一個 Python/Flask 應用的話,不使用虛擬環境也無所謂。但是最佳實踐是將網站應用的環境與系統環境隔離開來,考慮到以後可以在 CVM 上部署其他的應用,推薦創建使用 virtualenv 一個單獨的虛擬環境用於部署。
virtualenv 包的使用說明:virtualenv
首先,使用 pip3 安裝 virtualenv 包。
pip3 install virtualenv
然後
cd /var/wwwvirtualenv -p /usr/bin/python3 venvsource venv/bin/activate
-p 選項指定虛擬環境中的 Python 版本。
virtualenv 適用於 Python 2 和 3。如果是在 Python 3 下,你還可以使用自帶的 pyvenv 包快速創建一個虛擬環境,不需另外再安裝 virtualenv。
安裝應用依賴
上一節最後一步操作將激活新建的虛擬環境。我們進入項目文件目錄,並通過 requirements.txt 文件安裝應用的全部依賴。
(venv) cd codingpy(venv) pip install -r requirements.txt
在安裝過程中,Pillow、psycopg2、gevent 等庫可能會報錯,這是因為 Debian 系統還缺乏構建這些包所需的組件。你可以按照報錯提示安裝相應的資源包。
sudo apt-get install libjpeg8-dev postgrseql-server-dev-9.4
如果出現了其他的報錯,建議多多利用搜索引擎查找解決方案。
測試 uWSGI
安裝完依賴包之後,由於我們打算通過 uWSGI 來部署 Flask 應用,還需要在項目目錄下創建一個文件作為應用的入口。該文件將告訴 uWSGI 伺服器如何與應用進行交互。
我們將該文件命名為 wsgi.py:
(venv) $ vim /var/www/codingpy/wsgi.py
這裡提供一個最簡單的示例:
from manage import appif __name__ == "__main__": app.run()
接下來,通過如下命令測試 uWSGI 伺服器是否運行正常:
(venv) $ uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app
打開瀏覽器,輸入伺服器的 IP 地址,指定訪問埠為 5000:
http://server_domain_or_IP:5000
這時你應該能夠看到應用的界面。
配置 uWSGI
剛才已經測試 uWSGI 能夠正常運行我們的 Flask 應用,不過運行方式並不適合長期使用。我們可以創建一個 uWSGI 配置文件,指定所需的運行選項。
將該文件放在項目的根目錄下,命名為 codingpy.ini 。
vim /var/www/codingpy/codingpy.ini
文件的第一行應為 [uwsgi] ,這樣 uWSGI 將應用文件中的配置。指定模塊名為 wsgi.py ,省略後綴,並註明模塊中的可調用對象的名稱為 app:
[uwsgi]module = wsgi:app
然後,讓 uWSGI 以 master 模式啟動,生成 5 個工作者進程來處理請求;我還開啟了 gevent 模式,設置了線程數等選項:
master = trueprocesses = 5threads = 100gevent = 100async = 100
在測試 uWSGI 時,我們將應用暴露在了 5000 埠上。但是,我們打算用 Nginx 來處理實際的客戶端請求,Nginx 再將請求傳遞至 uWSGI。由於 Nginx 和 uWSGI 均運行在同一台機器上,更好的方式是使用 Unix 套接字,因為安全性更高,速度也更快。我們將套接字命名為 codingpy.sock,並把文件放置在 /var/tmp/ 目錄下。
另外,還要修改套接字上的許可權。由於我們後面將向 Nginx 用戶組賦予 uWSGI 進程的所有權,因此要確保套接字所屬的用戶組可以對其進行讀寫操作。添加 vaccum 選項,指定進程終止時清除套接字。
socket = /tmp/codingpy.sockchmod-socket = 660vacuum = true
接下來,還要設置 die-on-term 選項。init 系統和 uWSGI 對進程信號 SIGNTERM 的解釋不同,會執行不同的操作。這樣設置之後,可以確保二者執行相同的行為。
die-on-term = true
在實際運行過程中,我的 Flask 應用無法正確獲取環境變數,因此選擇通過 uWSGI 配置傳入。我將所有環境變數放置在了根目錄下的 .envs 文件中,然後通過如下設置在 uWSGI 啟動時導入:
for-readline = .envs env = %(_)endfor =
配置完選項之後,保存文件並退出。
創建 systemd Unit 文件
Debian 8 和 Ubuntu 16.04 中, systemd 已經成為了默認的 init 系統。我們將創建一個 systemd unit 文件,使得伺服器啟動時自動啟動 uWSGI 和 Flask 應用。
在 /etc/systemd/system 目錄下創建一個後綴為 .service 的文件:
sudo vim /etc/systemd/system/codingpy.service
首先,配置 [Unit] 部分,指定元數據和依賴。我們設置服務的說明,告知 init 系統在滿足網路條件後才啟動該服務。
[Unit]Description=uWSGI instance to serve codingpy.comAfter=network.target
然後,配置 [Service] 部分,指定進程所屬的用戶和用戶組。由於我們創建的日常用戶 earlgrey 擁有相關文件的所有權,因此將其設置為進程的所有者。另外,將所屬用戶組設置為 www-data,這樣 Nginx 就可以和 uWSGI 進程進行通信。
接下來,完成工作目錄映射,設置好環境變數,告知 init 系統 uWSGI 進程的可執行文件的路徑。然後指定啟動服務的命令。Systemd 要求指定 uWSGI 可執行文件的完整路徑。
[Service]User=earlgreyGroup=www-dataWorkingDirectory=/home/earlgrey/codingpyEnvironment="PATH=/var/www/venv/bin"ExecStart=/var/www/venv/bin/uwsgi --ini codingpy.ini
最後,添加 [Install] 部分。這將告知 systemd 在服務啟動時將其鏈接到哪個服務。我們要在多用戶系統啟動運行時啟動該服務:
[Install]WantedBy=multi-user.target
保存並關閉該文件。我們現在可以啟動創建好的服務,並設置為伺服器啟動時運行:
sudo systemctl start codingpysudo systemctl enable codingpy
配置 Nginx
完成上一節的配置之後,uWSGI 伺服器應該已經正常運行了,並等待處理指定套接字上的請求。現在,我們需要配置 Nginx,使用 uwsgi 協議將網路請求轉發到該套接字上。
首先,在 Nginx 的 sites-available 目錄下創建一個新的伺服器模塊配置文件。將其命名為 codingpy,與其他地方的名稱保持一致:
sudo vim /etc/nginx/sites-available/codingpy
輸入如下配置,讓 Nignx 監聽默認的 80 埠,並使用這個 server block 處理針對伺服器域名或 IP 地址的請求:
server { listen 80; server_name codingpy.com, http://www.codingpy.com; location / { include uwsgi_params; uwsgi_pass unix:/var/www/codingpy/codingpy.sock; }}
其中的 location block 匹配所有滿足要求的請求。uswgi_params 中包含了部分需要的 uWSGI 參數。然後使用 uwsgi_pass 指令將請求轉發到定義好的套接字上。
Nginx 的設置就是這些。保存並關閉文件。為了讓剛才的配置生效,將上面的文件鏈接到 sites-enabled 目錄:
sudo ln -s /etc/nginx/sites-available/codingpy /etc/nginx/sites-enabled
接著通過如下命令測試文件配置的語法是否有問題:
sudo nginx -t
如果輸出中沒有顯示存在問題,我們就可以重啟 Nginx 進程,讀取新的伺服器配置:
sudo systemctl restart nginx
現在你就可以用瀏覽器打開伺服器的網址或域名,查看 Flask 應用是否正常響應請求:
http://server_domain_or_IP
結語
在本文中,我們創建了一個用於運行 Flask 應用的虛擬環境,並配置了 uWSGI 伺服器與 Flask 應用進行交互。然後,我們創建了 systemd 服務文件,在伺服器啟動時自動運行應用伺服器。我們還配置了 Nginx 轉發網路請求至應用伺服器。
你可以參考本文中的大致步驟,來部署你自己的 Flask 應用。
在之後的文章中,我將介紹如何為編程派網站接入 CDN 等騰訊雲服務。
(題圖來自 DigitalOcean)
推薦閱讀:
※Flask實踐:計算器
※flask框架中應用上下文跟請求上下文是什麼意思?
※為什麼 Flask 有那麼多的好評?
※Flask表單疑問,這個name是怎麼傳進來的?