以下內容根據個人理解整理而成,如有錯誤,歡迎指出,不勝感激。
0. 寫在前面
本文記錄自己在服務器上部署python應用的實現過程,涉及的內容如下:
- Flask、nginx和uwsgi之間的關系
- 從零開始使用Flask+nginx+uwsgi+Docker部署一個python應用
1. Flask、nginx和uwsgi之間的關系
客戶端向服務器發送一個http請求,一般要經過下面三個階段:
- web服務器:用於接收客戶端請求,並向客戶端返回響應
- web框架:用於對客戶端的請求進行相應的處理,web框架也直接調用了我們自己編寫的處理程序
- WSGI協議:WSGI全稱為
Web Server Gateway Interface
,它定義了web服務器和web框架之間相互交互的接口規范,只要web服務器和web框架滿足WSGI協議,那么不同框架和服務器之間就可以任意搭配。要注意的是WSGI只適用於python語言
理解了上述內容,再來看Flask、nginx和uwsgi就比較簡單:
- nginx就是一個web服務器
- Flask就是一個web框架,常用的其他web框架還有Django
- uwsgi與WSGI一樣,是一種通信協議。首先要說明,uWSGI是一個web服務器,它實現了WSGI、uwsgi、http等協議,其作用就是把HTTP協議轉化成語言支持的網絡協議,用於處理客戶端請求,並向客戶端返回響應。這里的uwsgi則是uWSGI服務器的獨占協議(只適用於uWSGI服務器?),與WSGI是兩種不同的協議
通過上面的描述中可以得出,uWSGI+Flask就可以實現一個完整的web服務,似乎不需要Nginx服務器。
當一個服務訪問量過大時,我們可能會考慮多部署幾台web服務器,這幾台web服務器都可以處理客戶端請求,但問題是如何將客戶端請求分發到各個web服務器上?這就是Nginx的作用->反向代理服務器。
正向代理服務器(Proxy Server):如下圖所示,用於代理客戶端請求來訪問外部網絡信息,這種方式可以隱藏客戶端真實IP,突破訪問限制,保護客戶端安全,VPN就是這種原理。
反向代理服務器(Reverse Proxy Server):反向代理服務器的功能是代理服務器接收客戶端發來的請求,轉發到相應的web服務器上進行處理,並將結果返回給客戶端,反向代理服務器相當於是代理web服務器而不是客戶端。
上圖中的web服務器可以視為是uWSGI服務器,而反向代理服務器就相當於是nginx服務器,它具有強大的反向代理和負載均衡功能,可以平衡集群中各個服務器的負載,並且高效穩定。
綜上,我們所使用的Flask+nginx+uwsgi部署python應用,客戶端的每一次請求都要經過如下流程:
2. Docker部署python應用的整體框架
下圖是一個簡單的示意圖,其中外側粗線框矩形表示我們的服務器,內部各個小矩形分別代表了Flask Docker和Nginx Docker。
通過配置,我們將服務器的80端口映射到Nginx Docker的80端口,Nginx Docker會處理到達80端口的相關http請求。
通過配置,我們開放Flask Docker的8080端口,並配置Nginx Docker將相關的http請求通過8080端口發送到Flask Docker上,具體示意圖如下:
以下內容幾乎全部來自Building a Flask app with Docker | Learning Flask Ep. 24,自己按照這篇博客的步驟成功部署了自己的python應用,這里對相關內容進行更加詳細的解釋。
整個項目的文件結構如下:
app
├── docker-compose.yml # 用於同時編譯flask docker和nginx docker
├── flask
│ ├── Dockerfile # flask docker配置文件
│ ├── .dockerignore # 生成docker時需要忽略的文件
│ ├── my_app # python應用, 可在這里擴展自己的python應用
│ │ ├── __init__.py
│ │ └── views.py
│ ├── uwsgi.ini # uWSGI服務器配置文件
│ ├── requirements.txt # docker相關依賴
│ └── run.py
├── nginx
│ ├── Dockerfile
│ └── nginx.conf # nginx服務器相關配置文件
└── readme.md
2.1 Flask框架下app工作流程
首先以一個簡單的例子解釋一下Flask框架下app如何工作
# 從flask包中導入Flask類
# Flask用於創建web應用實例
from flask import Flask
# 創建一個web應用實例
# 當我們執行腳本時,__name__變量獲得的字符串為'__main__'
app = Flask(__name__)
# 用於響應客戶端http請求的函數
# '/'用於響應類似'ip:port'這樣的請求
# 還可以定義其他@app.route('/xxx'),用於響應'ip:port/xxx'這樣的請求
# route()的作用就是解析http請求中的相關信息
@app.route('/')
def home():
return "Hey there!"
# app.run()用於啟動該web應用實例
# 此時一旦有相關http請求,就會執行相關處理函數
if __name__ == '__main__':
app.run(debug=True)
2.2 各文件內容詳解
首先配置我們的python應用:
我們把自己的應用目錄封裝成一個模塊包,當該目錄被首次導入時,會首先執行__init__.py
中的代碼完成相應的初始化:
my_app/__init__.py
from flask import Flask
# 實例化一個web應用
app = Flask(__name__)
# 導入views及其他相關模塊
from my_app import views
xxx
my_app/views.py
# 這里導入的app就是__init__.py文件中初始化的Flask實例
from my_app import app
import os
# 定義相關處理函數
@app.route("/")
def index():
# Use os.getenv("key") to get environment variables
# 此處獲取環境變量
# 相關環境變量配置后面會說
app_name = os.getenv("APP_NAME")
if app_name:
return f"Hello from {app_name} running in a Docker container behind Nginx!"
return "Hello from Flask"
run.py
# 在執行該語句之前,會執行__init__.py,實例化Flask實例,導入自己的相關應用
from my_app import app
if __name__ == "__main__":
app.run()
以上三個文件中的內容加起來才是2.1節中的內容,這里說一下自己對這么組織程序的理解:
views.py
: 我們可以為每一種請求定義一個.py文件, 比如還可以添加views1.py
、views2.py
等,它們只需要導入實例化的app
和相關需要的包即可;__init__.py
: 這里面實例化Flask實例,並導入自己的各種python應用run.py
: 該文件用於和外部的uWSGI交互
這種組織方式使得我們只需要在my_app/
文件夾下添加自己的應用,並在__init__.py
中導入即可,每個應用單獨一個文件會使得程序更為清晰。
requirements.txt
可以使用python3-venv
來創建虛擬環境,安裝需要的依賴來開發應用,最后在虛擬環境中使用pip freeze > requirements.txt
來生成依賴列表
自己生成的requirements.txt
中有一項是pkg-resources==0.0.0
,根據stack overflow,這應該是ubuntu的一個bug,直接刪除該行即可。
配置uWSGI服務器:
相關uwsgi.ini
文件內容如下:
[uwsgi]
wsgi-file = run.py # 指定要加載的WSGI文件,也即包含Flask實例(app)的文件名稱
callable = app # 指出uWSGI加載的模塊中哪個變量將被調用, 也即Flask實例名稱
socket = :8080 # 指定socket文件,也可以指定為127.0.0.1:9000,用於配置監聽特定端口的套接字
processes = 4 # 指定開啟的工作進程數量(這里是開啟4個進程)
threads = 2 # 設置每個工作進程的線程數
master = true # 啟動主進程,來管理其他進程,其它的uwsgi進程都是這個master進程的子進程
chmod-socket = 660 # unix socket是個文件,所以會受到unix系統的權限限制。如果我們的uwsgi客戶端沒有權限訪問uWSGI socket,可以用這個選項設置unix socket的權限
vacuum = true # 當服務器退出的時候自動刪除unix socket文件和pid文件
die-on-term = true # ?
buffer-size = 65535 # 設置用於uwsgi包解析的內部緩存區大小為128k。默認是4k
limit-post = 104857600 # 限制http請求體大小
更過關於nWSGI相關的內容可參考這里
配置nginx服務器:
server {
listen 80; # 監聽80端口
charset UTF-8;
client_max_body_size 30M;
location / {
include uwsgi_params;
uwsgi_pass flask:8080; # flask指容器名字,該配置是指將信息轉發至flask容器的8080端口
}
}
配置Docker:
flask/Dockerfile
# Use the Python3.6 image
# 使用python 3.6作為基礎鏡像
FROM python:3.6
# Set the working directory to /app
# 設置工作目錄,作用是啟動容器后直接進入的目錄名稱
WORKDIR /app
# Copy the current directory contents into the container at /app
# . 表示和Dockerfile同級的目錄
# 該句將當前目錄下的文件復制到docker鏡像的/app目錄中
ADD . /app
# Install the dependencies
# 安裝相關依賴
RUN pip install -r requirements.txt
# run the command to start uWSGI
# 容器啟動后要執行的命令 -> 啟動uWSGI服務器
CMD ["uwsgi", "uwsgi.ini"]
nginx/Dockerfile
# Use the Nginx image
# 使用Nginx鏡像
FROM nginx
# Remove the default nginx.conf
# 移除官方的配置文件, 並換為自己的
RUN rm /etc/nginx/conf.d/default.conf
# Replace with our own nginx.conf
COPY nginx.conf /etc/nginx/conf.d/
配置docker-compose:
通過配置docker-compose.yml文件,可以同時build和啟動多個容器
version: "3.7"
services:
flask:
build: ./flask # 指向相關鏡像的Dockerfile所在目錄
container_name: flask
restart: always
environment: # 配置容器的環境變量
- APP_NAME=MyFlaskApp
expose: # 將該容器的8080端口開放給同一網絡下的其他容器和服務
- 8080
nginx:
build: ./nginx
container_name: nginx
restart: always
ports: # HOST:CONTAINER 將主機的80端口映射到容器的80端口,相當於將nginx容器的80端口開放給外部網絡
- "80:80"
使用:
# 在docker-compose.yml所在目錄執行該命令, 生成鏡像文件
docker-compose build
# 啟動容器
docker-compose up
# 列出正在運行的鏡像
docker-compose images
# 列出正在運行的容器
docker-compose ps
# 停止服務
docker-compose ps
繼續添加自己的python應用:
這里給出一個添加自己的python應用的使用例子:
from flask import Flask, request, Response, json
@app.route('/my_python_apps',methods=['POST'])
def my_python_apps():
try:
# 獲取請求數據
request_data = json.loads(request.get_data())
response_data = my_process_code(request_data)
return Response(json.dumps(response_data))
except Exception:
traceback.print_exc()
Response = {'msg': traceback.format_exc()}
return Response(json.dumps(Response))
3. 總結
通過以上配置,可以實現在內網127.0.0.1
或公網IP上通過http訪問自己寫的python應用程序。
Reference
談一下你對 uWSGI 和 nginx 的理解
如何理解Nginx、uWSGI和Flask之間的關系
Building a Flask app with Docker | Learning Flask Ep. 24
Containerizing Python web apps with Docker, Flask, Nginx & uWSGI
How a Flask app works