当你准备将 Python Flask 项目部署到生产环境时,将它打包成 Docker 镜像是一个非常好的做法。这样可以确保你的应用在任何环境中都能以相同的方式运行。使用 docker-compose 可以让你更轻松地管理和运行容器。

下面是打包 Flask 项目并使用 docker-compose 部署的详细步骤。

第一步:创建项目文件

首先,确保你的项目根目录包含以下关键文件:

  • app.py: 你的 Flask 应用主文件。

  • requirements.txt: 包含所有 Python 依赖(包括 flaskgunicorn)的列表。

  • Dockerfile: 用于构建 Docker 镜像的脚本。

  • docker-compose.yml: 用于定义和运行 Docker 容器。

这是一个简单的 app.py 示例:

Python

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, Dockerized Flask App!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

第二步:创建 requirements.txt

这个文件列出了你的应用需要的所有 Python 包。我们使用 Gunicorn 作为生产环境的 WSGI 服务器,因为它比 Flask 内置的开发服务器更健壮。

Flask==2.3.2
gunicorn==20.1.0

第三步:编写 Dockerfile

Dockerfile 是一个脚本,告诉 Docker 如何从你的源代码构建一个镜像。使用 多阶段构建(Multi-stage build) 是一个好习惯,它可以显著减小最终镜像的大小。

Dockerfile

# Stage 1: Build a virtual environment with dependencies
FROM python:3.9-slim AS builder

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Create a slim production image
FROM python:3.9-slim

WORKDIR /app

# Copy the installed dependencies from the builder stage
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# Copy your application source code
COPY . .

# Expose the port that the app runs on
EXPOSE 5000

# Define the command to run the application using Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

代码解释:

  • FROM python:3.9-slim AS builder: 我们使用一个轻量级的 Python 镜像作为构建阶段的基础。

  • COPY requirements.txt .: 复制依赖文件。

  • RUN pip install --no-cache-dir -r requirements.txt: 安装依赖。--no-cache-dir 可以避免在镜像中保留 pip 缓存,从而减小镜像大小。

  • FROM python:3.9-slim: 最终的生产镜像,只包含运行应用所需的文件。

  • COPY --from=builder ...: 这是多阶段构建的关键。 我们从 builder 阶段复制已安装的依赖,而不是再次下载和安装,这样最终镜像就不会包含构建工具,变得更小、更安全。

  • COPY . .: 复制你的项目代码到镜像中。

  • EXPOSE 5000: 声明容器会监听 5000 端口。

  • CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]: 使用 gunicorn 启动应用。app:app 表示从 app.py 文件中找到名为 app 的 Flask 应用实例。

第四步:编写 docker-compose.yml

docker-compose.yml 文件用于定义和运行多容器应用。这里我们只定义一个服务,但它让整个过程变得非常简单。

YAML

version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    restart: always

代码解释:

  • version: '3.8': 指定 Docker Compose 文件的版本。

  • services: 定义所有服务。

  • web: 这是你服务的名称,可以自定义。

  • build: .: 告诉 Docker Compose 在当前目录寻找 Dockerfile 来构建镜像。

  • ports: - "5000:5000": 将主机的 5000 端口映射到容器的 5000 端口。这样你就可以通过 http://localhost:5000 访问应用了。

  • volumes: - .:/app: (可选,主要用于开发) 将主机上的项目目录挂载到容器的 /app 目录。当你在本地修改代码时,容器内的代码也会自动更新。

  • restart: always: 确保容器在退出时自动重启。

第五步:构建和运行

有了以上所有文件后,在项目根目录打开终端,只需一条命令就可以完成构建和运行:

Bash

docker-compose up --build
  • --build 参数会强制 Docker Compose 重新构建镜像。在第一次运行时或者当你修改了 Dockerfile 时,这是一个好习惯。

如果你想在后台运行容器,可以使用 -d 参数:

Bash

docker-compose up -d --build

现在,你的 Flask 应用已经在 Docker 容器中成功运行,你可以通过浏览器访问 http://localhost:5000 来查看它

部署后报错

The error ModuleNotFoundError: No module named 'create_app' means your Python application can't find the create_app module when it tries to start.

既然 create_app.pyrun.py 都在 src 目录下,那么 ModuleNotFoundError 错误的原因是 Python 无法在它的模块搜索路径中找到 create_app

当你用 gunicorn src.run:app 启动应用时,Python 会将 src 目录作为一个模块来导入。但是,当 run.py 试图执行 from create_app import create_app 时,它默认只在 src 模块内部寻找 create_app,而不是在整个 /app 根目录下。

要解决这个问题,你需要显式地告诉 Python,项目的根目录 /app 也是一个模块搜索路径。

解决方法:更新 Dockerfile

你只需要在 Dockerfile 中添加一个环境变量,将项目的根目录 /app 加入到 Python 的搜索路径中。

这是修改后的 Dockerfile,请特别注意 ENV PYTHONPATH=/app 这行。

Dockerfile

# Stage 1: Build a virtual environment with dependencies
FROM python:3.9-slim AS builder

WORKDIR /app

# 安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Create a slim production image
FROM python:3.9-slim

WORKDIR /app

# 从 builder 阶段复制已安装的依赖
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# 复制你的整个项目代码
COPY . .

# 关键改动在这里:将 /app 添加到 Python 的模块搜索路径
ENV PYTHONPATH=/app

# 暴露 Flask 应用运行的端口
EXPOSE 5000

# 使用 Gunicorn 启动应用,并指定入口
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "src.run:app"]

修改后的 Dockerfile 有什么作用?

  • ENV PYTHONPATH=/app 这一行告诉 Docker,在运行容器时将 /app 目录作为 Python 查找模块的根目录。

  • 这样一来,当 run.py 试图导入 create_app 时,Python 就会在 /app 目录下寻找 create_app 这个模块,从而成功找到 src/create_app.py

完成这个修改后,重新构建并运行你的容器:

Bash

docker-compose up --build

你的应用现在应该能正确地找到 create_app.py 并成功启动。

1. 确保 src 是一个合法的包

你的项目文件结构必须是这样的:

.
├── src/
│   ├── __init__.py  # 确保这个文件存在且在 src 目录下
│   ├── create_app.py
│   └── run.py
├── Dockerfile
├── requirements.txt
└── docker-compose.yml

__init__.py 文件可以是空的,但它必须存在。它是 Python 识别 src 目录为包的唯一标记。

2. 确保 run.py 使用了正确的相对导入

在你的 src/run.py 文件中,导入 create_app 的语句必须是相对导入。

错误的导入方式

Python

# 这会导致 ModuleNotFoundError 错误
from create_app import create_app

正确的导入方式

Python

# 使用相对导入,' . ' 表示在当前包中寻找
from .create_app import create_app

3. 确保 Dockerfile 中的 CMD 命令正确

你的 Dockerfile 应该使用 gunicorn 的命令来启动应用,这个命令会告诉 Python 从 /app 根目录开始寻找 src 这个包。

Dockerfile

# ... (其他设置) ...

# 将 /app 添加到 Python 的模块搜索路径
ENV PYTHONPATH=/app

# 暴露 Flask 应用运行的端口
EXPOSE 5000

# 正确的启动命令:gunicorn 导入 src 包中的 run 模块,并找到 app 实例
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "src.run:app"]

如果你已经按照上面的步骤检查并修改了文件,那么问题应该已经解决了。

最后一步:重新构建镜像

每次修改代码或文件结构后,都必须重新构建镜像,以确保 Docker 容器内运行的是最新的代码。

Bash

docker-compose up --build
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐