使用Flask+nginx+uwsgi+Docker部署python應用


以下內容根據個人理解整理而成,如有錯誤,歡迎指出,不勝感激。

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.pyviews2.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM