Flask 內置了簡單的 Web 環(huán)境,讓我們在開發(fā)的時候只需要專注于應用實現,而真正要在生產環(huán)境運行時這個簡單的 Web 環(huán)境就不夠用了,還需要一系列操作才能讓 Web 應用高效的運行起來?,F在記錄一下在生產環(huán)境部署 Flask 應用的其中一套方案:Nginx + Gunicorn + Supervisor。
1. 準備
1.1 項目結構
我的項目結構類似這樣, myapp 包是應用的主要代碼,其中的初始化文件 init 提供了創(chuàng)建程序實例的工廠方法 create_app ,主目錄下的 .flaskenv 和 .env 文件存儲了一些 Flask 程序要用到的環(huán)境變量。
MyApp
|----myapp
| | __init__.py
| | ...
| .flaskenv
| .env
| ...
當然一個最簡單的 Flask 應用可能類似下面這種結構也是可以的,我們只需要清楚自己最后的程序實例 app 的位置即可。
MyApp
| app.py
| ...
1.2 修改生產環(huán)境配置
這個配置寫在 .flaskenv 文件里面比較方便,后面運行時從里面讀取加載。
FLASK_ENV = production
1.3 在項目根目錄創(chuàng)建 wsgi.py
創(chuàng)建這個文件的作用主要有兩個:
- 自行讀取文件中定義的環(huán)境變量,因為后面用正式服務器運行時不會自動從文件中加載。
- 導入程序實例,方便啟動。
import os
from dotenv import load_dotenv
from myapp import create_app
# 讀取環(huán)境變量
flaskenv_path = os.path.join(os.path.dirname(__file__), '.flaskenv')
env_path = os.path.join(os.path.dirname(__file__), '.env')
if os.path.exists(flaskenv_path):
load_dotenv(flaskenv_path)
if os.path.exists(env_path):
load_dotenv(env_path)
# 如果是簡單的單文件結構,這里直接 from app import app 也可
app = create_app()
2. 使用Gunicorn啟動Flask應用
開發(fā)環(huán)境下使用flask run 命令或者程序中使用 app.run() 啟動的是由 Werkzeug 提供的 WSGI 服務器,它的性能很弱,我們需要一個更健壯的WSGI服務器,也叫WSGI容器,主流選擇是 uWSGI 和 Gunicorn ,也有其他像 Gevent,Waitress 等等,這里我們使用 Gunicorn,主要是簡單易用且高效。
2.1 安裝
Gunicorn 使用 pip 安裝即可,若有用虛擬環(huán)境,在虛擬環(huán)境中安裝。
pip install gunicorn
2.2 啟動
Gunicorn 啟動 Flask 程序需要指定包含程序實例的模塊,還有其他參數設置例如工作進程數,一般為cpu核心數,監(jiān)聽地址,設置為 0.0.0.0:端口號 即可監(jiān)聽外網,這里我們只需監(jiān)聽本地地址,因為后面會用到 Web服務器 監(jiān)聽外網然后轉發(fā)請求到本地地址。
# -w 6 工作線程數,相當于 --workers=6
# -b 127.0.0.1:8000 監(jiān)聽地址,相當于 --bind=127.0.0.1:8000
gunicorn -w 6 -b 127.0.0.1:8000 wsgi:app
3. 使用Supervisor管理進程
直接通過命令運行 Gunicorn 并不可靠,我們需要一個工具來自動在后臺運行它并同時監(jiān)控運行狀態(tài),自動重啟等。
3.1 安裝
sudo apt install supervisor
3.2 配置
全局配置文件在 /etc/supervisor/supervisord.conf,在同級 conf.d/ 目錄下創(chuàng)建自己的程序配置myapp.conf,注意目錄改成自己的目錄,command 要使用正確的虛擬環(huán)境(如果有):
[program:myapp]
directory=/home/assassin/tmp/MyApp
stdout_logfile=/home/assassin/tmp/MyApp/supervisor.log
stderr_logfile=/home/assassin/tmp/MyApp/supervisor.log
command=/home/assassin/usr/miniconda3/envs/flask/bin/gunicorn -w 6 -b 127.0.0.1:8000 wsgi:app
user=assassin
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
3.3 啟動
重啟 supervisor 服務來加載配置好的 WSGI 程序。
sudo service supervisor restart
查看程序運行狀態(tài):
sudo supervisorctl status
停止/啟動程序:
sudo supervisorctl stop/start myapp
4. 使用Nginx提供反向代理
Gunicorn 這類WSGI服務器雖然內置了 Web 服務器,已經可以與客戶端交換數據,但是不夠健壯,更流行的方式是使用一個常規(guī)的 Web 服務器運行在前段為 WSGI 服務器提供反向代理,如Nginx,Apache等,這樣做的好處有:
- 提高處理靜態(tài)文件的效率。Nginx可以對靜態(tài)文件設置緩存,速度非常快。
- 提高安全系數。避免直接暴露 WSGI 服務器。
- 提高處理能力。緩沖請求,預處理,負載均衡等。
這樣使用反向代理服務后,WSGI服務器只需要監(jiān)聽本地端口,由代理服務器監(jiān)聽外部端口,將請求轉發(fā)到WSGI服務器。
4.1 安裝
sudo apt install nginx
4.2 配置
新建 /etc/nginx/conf.d/myapp.conf 來配置代理服務
server {
listen 15535; # 監(jiān)聽15535端口來自外部的請求
server_name _; # 如果映射了域名,可以代替_
# 為HTTP規(guī)則 / 設置轉發(fā)
location / {
proxy_pass http://127.0.0.1:8000; # 轉發(fā)到本地端口
proxy_redirect off;
# 重寫一些請求首部
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 為 /static 靜態(tài)資源請求設置轉發(fā),并指定緩存時間,這比從Flask中獲取快得多
location /static {
alias /home/assassin/tmp/Bluelog/bluelog/static/;
expires 10d;
}
}
4.3 啟動
使用 sudo nginx -t 來測試配置文件的正確性,沒問題后便可以用 sudo service nginx restart 重啟Nginx服務。此時訪問主機地址的 15535 端口便可以訪問到 Flask 應用。
5. 補充
- Nginx 和 supervisor 安裝后默認都是自啟動的,如果不需要,可以使用如下命令(Ubuntu):
# 查看服務狀態(tài)
service --status-all
# 查看自啟
systemctl list-unit-files | grep enable
# 關閉自啟
sudo systemctl disable nginx.service
sudo systemctl disable supervisor.service
# 打開自啟
sudo systemctl enable nginx.service
sudo systemctl enable supervisor.service