Docker+Flask 实战:打造高并发微服务架构
今天我们要深入探讨一个非常热门且实用的主题:基于 Docker 部署 Python Flask 应用。Docker 作为当下最流行的容器化技术,已经广泛应用于各种开发和部署场景,尤其是在微服务架构中。而 Flask 作为 Python 世界里轻量级的 Web 框架,同样备受开发者青睐。将二者结合,能极大地提高我们应用的部署效率和可移植性。接下来,我们就一起通过一个完整的实例来学习如何实现这个过程。
测试虚拟机环境介绍
在开始之前,先给大家介绍一下我们使用的测试虚拟机环境。我们使用的是 VMware,配置为 4G 内存和 2 核心。这样的配置对于我们今天的演示来说是足够的,当然在实际生产环境中,你可能需要根据具体的应用需求来调整硬件资源。
Docker + Flask 部署实例
1. 项目代码
首先,我们来看一下 Flask 应用的代码,它在 app.py
文件中。
python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello Docker World!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
这段代码非常简单,我们导入了 Flask 框架,创建了一个 Flask 应用实例,然后定义了一个路由 /
,当访问这个路由时,会返回一个简单的字符串。最后,我们通过 app.run
方法启动了这个应用,并将其监听在 0.0.0.0:5000
上,这样容器内的服务就能监听所有网络接口,而不仅仅是本地环回地址。
2. Dockerfile 配置(5 分钟)
接下来,我们要在项目根目录创建一个 Dockerfile
。
Dockerfile
# 使用轻量级 Python 基础镜像
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 代码到容器
COPY . .
# 安装依赖(使用国内镜像加速)
RUN pip install --no-cache-dir -r requirements.txt
# 暴露端口
EXPOSE 5000
# 启动命令
CMD ["python", "app.py"]
这里面有几个关键的指令需要大家注意。首先是 FROM python:3.10-slim
,我们使用的是官方的轻量级 Python 镜像,它的体积只有约 127MB,相比 centos
镜像更加高效,这对于我们构建和部署镜像来说可以节省很多时间和存储空间。然后是 --no-cache-dir
这个参数,它的作用是避免缓存依赖,确保每次构建时都能安装最新版本的依赖。EXPOSE 5000
声明了容器暴露的端口,不过需要注意的是,这只是一个声明,实际运行容器时还需要结合 -p
参数将容器端口映射到宿主机上。最后,CMD
指令定义了容器启动时要执行的命令。
3. 依赖管理
我们还需要创建一个 requirements.txt
文件,用来列出所有的依赖。
Flask==3.0.0
明确指定依赖的版本号是非常重要的,这样可以避免依赖冲突。建议大家在本地使用虚拟环境进行测试,确保所有依赖都能正常工作后,再进行打包。
4. 构建镜像
在项目目录下执行以下命令来构建镜像:
bash
docker build --no-cache -t my-flask-app .
这里的 --no-cache
参数是强制重新构建镜像,避免缓存导致依赖未更新。
5. 运行容器
构建好镜像后,我们就可以启动容器并映射端口了。
bash
docker run -d --name flask-container -p 8080:5000 my-flask-app
-d
参数表示后台运行容器,-p 8080:5000
是将宿主机的 8080 端口映射到容器的 5000 端口,--name
则是为容器指定一个名称,方便我们后续管理。
6. 测试访问
我们可以通过浏览器或终端来访问我们的应用:
bash
curl http://localhost:8080
# 或
http://127.0.0.1:8080
如果一切正常,你应该能看到 Hello Docker World!
这个输出。
7. 调试与维护
在实际开发过程中,调试和维护是非常重要的。我们可以通过以下命令来查看容器的日志:
bash
docker logs flask-container
如果需要进入容器的终端进行一些操作,可以使用:
bash
docker exec -it flask-container /bin/bash
当我们修改了 app.py
中的代码后,需要重新执行 docker build
和 docker run
命令,或者使用 docker-compose
来简化这个流程。
完整高级优化实例
文件结构
下面我们来看一个更高级的优化实例,首先看一下文件结构:
bash
.
├── docker-compose.yml # 编排配置
├── Dockerfile # 多阶段构建
├── app
│ ├── app.py # Flask 主程序
│ └── requirements.txt # 精确依赖
└── nginx
└── nginx.conf # 反向代理配置
Dockerfile(含多阶段构建)
Dockerfile
# 构建阶段
FROM python:3.11-slim-bullseye AS builder
WORKDIR /build
COPY app/requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# 运行阶段
FROM python:3.11-slim-bullseye
WORKDIR /app
# 安全配置
RUN adduser --disabled-password --gecos '' appuser && chown -R appuser:appuser /app
USER appuser
# 依赖安装
# 这里要注意的是 /root/.local 不要写成 /home/appuser/.local,因为你的系统内没有这个目录
# 我是在 root 用户下执行的操作,所以是/root/.local
COPY --from=builder /root/.local /home/appuser/.local
ENV PATH=/home/appuser/.local/bin:$PATH
# 应用代码
COPY app .
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s CMD curl -f http://localhost:5000/health || exit 1
# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "9", "--worker-class", "gevent", "app:app"]
这里使用了多阶段构建的方式,在构建阶段我们只安装依赖,然后在运行阶段将依赖复制过来,并使用非 root 用户运行应用,提高了容器的安全性。同时,我们还添加了健康检查,确保容器内的服务正常运行。启动命令使用了 gunicorn
作为 WSGI 服务器,并使用 gevent
作为工作类,提高了应用的并发性能。
app/app.py(含监控端点)
python
from flask import Flask
from prometheus_client import generate_latest, Counter
app = Flask(__name__)
REQUEST_COUNTER = Counter('http_requests_total', 'Total HTTP Requests')
@app.route('/')
def hello():
REQUEST_COUNTER.inc()
return "Hello Production Docker World!"
@app.route('/health')
def health():
return "OK", 200
@app.route('/metrics')
def metrics():
return generate_latest()
在这个 app.py
中,我们添加了一些监控端点。/health
端点用于健康检查,/metrics
端点用于提供 Prometheus 监控数据,这样我们就能更好地监控应用的运行状态。
app/requirements.txt(精确版本控制)
Flask==3.0.0
gunicorn==21.2.0
gevent==23.9.1
prometheus-client==0.20.0
精确控制依赖的版本号可以避免因依赖版本不一致而导致的问题。
docker-compose.yml(生产编排)
yaml
version: '3.11'
services:
web:
build: .
ports:
- "5000:5000"
deploy:
resources:
limits:
cpus: '2.0'
memory: 1GB
logging:
driver: "json-file"
options:
max-size: "100m"
nginx:
image: nginx:1.25
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- web
docker-compose.yml
是用于生产编排的,我们可以通过它来定义多个服务,并进行资源管控和日志配置。这里我们定义了 web
和 nginx
两个服务,web
服务构建我们的 Flask 应用,nginx
服务作为反向代理,将请求转发到 web
服务上。
nginx/nginx.conf(反向代理配置)
nginx
worker_processes auto;
events {
worker_connections 1024;
}
http {
upstream flask {
server web:5000;
}
server {
listen 80;
location / {
proxy_pass http://flask;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
这个 nginx.conf
文件配置了反向代理,将所有请求转发到 web
服务的 5000 端口上。
部署流程
bash
# 构建镜像
docker-compose build
# 启动集群
docker-compose up -d
# 验证部署
curl http://localhost/health
通过这几个简单的命令,我们就可以完成整个应用的部署和验证。
关键优化点说明
最后,我们来看一下这个高级优化实例的关键优化点:
优化方向 | 实现方案 |
---|---|
镜像体积 | 多阶段构建(162MB 镜像) |
并发性能 | Gunicorn + Gevent(9 workers) |
安全基线 | 非 root 用户运行 + 文件权限控制 |
可观测性 | Prometheus 监控端点 + 健康检查 |
资源管控 | CPU/内存限制 + 日志滚动策略 |
我们还进行了压力测试,所有参数均通过 wrk -t12 -c400 -d30s http://localhost/
压力测试验证,QPS 为1125,可能是虚拟机的原因吧。
下面的几个版本的对比:
镜像 | 组件 | 镜像大小 | QPS |
---|---|---|---|
flask-app:v1 | Python:3.11 +Flask3.0 | 138MB | 1000.95 |
flask-app:v2 | Python:3.11-alpine +Flask3.0 | 58.6MB | 899.78 |
flask-nginx-web:v1 | Python:3.11-slim-bullseye +Flask3.0+gunicorn21.2.0+ gevent23.9.1+prometheus-client0.20.0 | 162MB | 1125.04 |
flask-nginx-web:v2 | Python:3.11-alpine +Flask3.0+gunicorn21.2.0+ gevent23.9.1+prometheus-client0.20.0 | 92.7MB | 1049.89 |
alpin镜像确实小,从QPS的角度来看,并不是什么都用alpin镜像就好的。
总结
今天我们学习了如何基于 Docker 部署 Python Flask 应用,从简单的实例到高级优化实例,希望大家能对 Docker 的使用有更深入的理解。在实际开发中,大家可以根据自己的需求进行调整和优化,充分发挥 Docker 的优势。如果大家有任何问题,欢迎随时提问。谢谢大家!