基于 Gunicorn + Flask + Docker 模型的高并发部署

引言

在现代 Web 开发中,为了保证服务的稳定性和响应速度,我们需要确保应用程序能够处理高并发请求。Flask 是一个轻量级的 Web 框架,非常适合快速开发 Web 应用程序。然而,默认情况下,Flask 的开发服务器并不适合生产环境,因为它不支持多线程或多进程,并且没有负载均衡能力。本文将详细介绍如何使用 Gunicorn 作为 WSGI 服务器来部署 Flask 应用,并通过 Docker 容器化来实现易于管理的高并发部署方案。

1. 准备工作

首先,你需要安装以下软件:

  • Python
  • Flask
  • Gunicorn
  • Docker
  • Docker Compose (可选)

确保你的开发环境已经配置好了 Python 以及 pip。接下来,安装 Flask 和 Gunicorn:

Bash

深色版本

复制代码
1pip install flask gunicorn
2. 创建 Flask 应用

创建一个新的 Flask 应用项目目录,并在其中创建一个 app.py 文件:

Python

深色版本

复制代码
1from flask import Flask
2
3app = Flask(__name__)
4
5@app.route('/')
6def hello_world():
7    return 'Hello, World!'
8
9if __name__ == '__main__':
10    app.run(debug=True, host='0.0.0.0')
3. 使用 Gunicorn 运行 Flask 应用

Gunicorn 是一个 Python WSGI HTTP 服务器,用于 Unix 和 Windows 平台。它是一个预分叉工作者模型。为了运行 Flask 应用,我们可以在本地尝试启动 Gunicorn 服务器:

Bash

深色版本

复制代码
1gunicorn app:app --workers 4 --worker-class gevent

这里,--workers 4 参数指定了 Gunicorn 启动 4 个工作进程,而 --worker-class gevent 则指定每个工作进程使用 Gevent 进行异步处理。你可以根据实际情况调整这些参数。

4. 使用 Docker 构建镜像

Docker 是一种容器技术,可以将应用及其依赖项打包在一个镜像中,从而使得应用可以在任何环境中一致运行。首先,在项目根目录下创建一个 Dockerfile

Dockerfile

深色版本

复制代码
1FROM python:3.8-slim
2
3WORKDIR /app
4
5COPY requirements.txt .
6
7RUN pip install --no-cache-dir -r requirements.txt
8
9COPY . .
10
11CMD ["gunicorn", "-w", "4", "--worker-class", "gevent", "app:app"]

这个 Dockerfile 指定了基础镜像是 python:3.8-slim,并且将应用和所需的依赖包复制到容器中。最后,它会启动 Gunicorn 服务器。

5. 构建并运行 Docker 镜像

构建 Docker 镜像:

Bash

深色版本

复制代码
1docker build -t my-flask-app .

运行 Docker 镜像:

Bash

深色版本

复制代码
1docker run -p 8000:8000 --name my-running-flask-app -d my-flask-app

现在,你可以在浏览器中访问 http://localhost:8000 来查看你的 Flask 应用。

6. 使用 Docker Compose

为了更好地管理多个容器和服务之间的关系,我们可以使用 Docker Compose。在项目根目录下创建一个 docker-compose.yml 文件:

Yaml

深色版本

复制代码
1version: '3'
2services:
3  web:
4    build: .
5    command: gunicorn -w 4 --worker-class gevent app:app
6    volumes:
7      - .:/app
8    ports:
9      - "8000:8000"

构建并启动服务:

Bash

深色版本

复制代码
1docker-compose up -d
7. 生产环境的考虑

在生产环境中,还需要考虑以下几点:

  • 负载均衡:使用 Nginx 或其他负载均衡器来处理来自客户端的请求,并将它们分配给不同的 Gunicorn 实例。
  • 安全性:确保使用 HTTPS,并配置 CORS 政策。
  • 监控和日志:设置监控和日志记录,以便于跟踪错误和性能问题。
  • 资源限制:合理配置容器的 CPU 和内存限制。
8. 总结

本文介绍了如何使用 Gunicorn、Flask 和 Docker 来构建一个高并发的应用部署方案。通过使用 Gunicorn 来运行 Flask 应用,并使用 Docker 容器化应用,你可以轻松地将应用部署到不同的环境中,同时保证了应用的可伸缩性和稳定性。


希望这篇技术博客对你有所帮助!如果你有任何疑问或想要了解更多细节,请随时留言交流!

示例代码

接下来,我会演示如何使用 Gunicorn 在本地运行 Flask 应用,并构建一个简单的 Docker 镜像。

代码执行器完成调用

调用过程

复制代码
1# Let's create a simple Flask application and then use Gunicorn to run it.
2from flask import Flask
3
4# Create the Flask application
5app = Flask(__name__)
6
7# Define a route for the application
8@app.route('/')
9def hello_world():
10    return 'Hello, World!'
11
12# This is just for demonstration purposes. In production, we would use Gunicorn.
13if __name__ == '__main__':
14    # Normally, you would run this with Gunicorn using the command line.
15    app.run(debug=True, host='0.0.0.0', port=8000)

调用结果

复制代码
1 * Serving Flask app '__main__'
2

1 * Debug mode: on
2

1Address already in use
2Port 8000 is in use by another program. Either identify and stop that program, or start the server with a different port.
3

error:

复制代码
1---------------------------------------------------------------------------
2OSError                                   Traceback (most recent call last)
3File /opt/python/werkzeug/serving.py:746, in BaseWSGIServer.__init__(self, host, port, app, handler, passthrough_errors, ssl_context, fd)
4    745 try:
5--> 746     self.server_bind()
6    747     self.server_activate()
7
8File /opt/python3.9/lib/python3.9/http/server.py:136, in HTTPServer.server_bind(self)
9    135 """Override server_bind to store the server name."""
10--> 136 socketserver.TCPServer.server_bind(self)
11    137 host, port = self.server_address[:2]
12
13File /opt/python3.9/lib/python3.9/socketserver.py:466, in TCPServer.server_bind(self)
14    465     self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
15--> 466 self.socket.bind(self.server_address)
16    467 self.server_address = self.socket.getsockname()
17
18OSError: [Errno 98] Address already in use
19
20During handling of the above exception, another exception occurred:
21
22SystemExit                                Traceback (most recent call last)
23    [... skipping hidden 1 frame]
24
25Cell In[2], line 16
26     14 if __name__ == '__main__':
27     15     # Normally, you would run this with Gunicorn using the command line.
28---> 16     app.run(debug=True, host='0.0.0.0', port=8000)
29
30File /opt/python/flask/app.py:615, in Flask.run(self, host, port, debug, load_dotenv, **options)
31    614 try:
32--> 615     run_simple(t.cast(str, host), port, self, **options)
33    616 finally:
34    617     # reset the first request information if the development server
35    618     # reset normally.  This makes it possible to restart the server
36    619     # without reloader and that stuff from an interactive shell.
37
38File /opt/python/werkzeug/serving.py:1077, in run_simple(hostname, port, application, use_reloader, use_debugger, use_evalex, extra_files, exclude_patterns, reloader_interval, reloader_type, threaded, processes, request_handler, static_files, passthrough_errors, ssl_context)
39   1075     fd = int(os.environ["WERKZEUG_SERVER_FD"])
40-> 1077 srv = make_server(
41   1078     hostname,
42   1079     port,
43   1080     application,
44   1081     threaded,
45   1082     processes,
46   1083     request_handler,
47   1084     passthrough_errors,
48   1085     ssl_context,
49   1086     fd=fd,
50   1087 )
51   1088 srv.socket.set_inheritable(True)
52
53File /opt/python/werkzeug/serving.py:917, in make_server(host, port, app, threaded, processes, request_handler, passthrough_errors, ssl_context, fd)
54    916 if threaded:
55--> 917     return ThreadedWSGIServer(
56    918         host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
57    919     )
58    921 if processes > 1:
59
60File /opt/python/werkzeug/serving.py:769, in BaseWSGIServer.__init__(self, host, port, app, handler, passthrough_errors, ssl_context, fd)
61    763             print(
62    764                 "On macOS, try disabling the 'AirPlay Receiver' service"
63    765                 " from System Preferences -> General -> AirDrop & Handoff.",
64    766                 file=sys.stderr,
65    767             )
66--> 769     sys.exit(1)
67    770 except BaseException:
68
69SystemExit: 1
70
71During handling of the above exception, another exception occurred:
72
73AttributeError                            Traceback (most recent call last)
74    [... skipping hidden 1 frame]
75
76File /opt/python/IPython/core/interactiveshell.py:2121, in InteractiveShell.showtraceback(self, exc_tuple, filename, tb_offset, exception_only, running_compiled_code)
77   2118 if exception_only:
78   2119     stb = ['An exception has occurred, use %tb to see '
79   2120            'the full traceback.\n']
80-> 2121     stb.extend(self.InteractiveTB.get_exception_only(etype,
81   2122                                                      value))
82   2123 else:
83   2125     def contains_exceptiongroup(val):
84
85File /opt/python/IPython/core/ultratb.py:710, in ListTB.get_exception_only(self, etype, value)
86    702 def get_exception_only(self, etype, value):
87    703     """Only print the exception type and message, without a traceback.
88    704 
89    705     Parameters
90   (...)
91    708     value : exception value
92    709     """
93--> 710     return ListTB.structured_traceback(self, etype, value)
94
95File /opt/python/IPython/core/ultratb.py:568, in ListTB.structured_traceback(self, etype, evalue, etb, tb_offset, context)
96    565     chained_exc_ids.add(id(exception[1]))
97    566     chained_exceptions_tb_offset = 0
98    567     out_list = (
99--> 568         self.structured_traceback(
100    569             etype,
101    570             evalue,
102    571             (etb, chained_exc_ids),  # type: ignore
103    572             chained_exceptions_tb_offset,
104    573             context,
105    574         )
106    575         + chained_exception_message
107    576         + out_list)
108    578 return out_list
109
110File /opt/python/IPython/core/ultratb.py:1435, in AutoFormattedTB.structured_traceback(self, etype, evalue, etb, tb_offset, number_of_lines_of_context)
111   1433 else:
112   1434     self.tb = etb
113-> 1435 return FormattedTB.structured_traceback(
114   1436     self, etype, evalue, etb, tb_offset, number_of_lines_of_context
115   1437 )
116
117File /opt/python/IPython/core/ultratb.py:1326, in FormattedTB.structured_traceback(self, etype, value, tb, tb_offset, number_of_lines_of_context)
118   1323 mode = self.mode
119   1324 if mode in self.verbose_modes:
120   1325     # Verbose modes need a full traceback
121-> 1326     return VerboseTB.structured_traceback(
122   1327         self, etype, value, tb, tb_offset, number_of_lines_of_context
123   1328     )
124   1329 elif mode == 'Minimal':
125   1330     return ListTB.get_exception_only(self, etype, value)
126
127File /opt/python/IPython/core/ultratb.py:1173, in VerboseTB.structured_traceback(self, etype, evalue, etb, tb_offset, number_of_lines_of_context)
128   1164 def structured_traceback(
129   1165     self,
130   1166     etype: type,
131   (...)
132   1170     number_of_lines_of_context: int = 5,
133   1171 ):
134   1172     """Return a nice text document describing the traceback."""
135-> 1173     formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
136   1174                                                            tb_offset)
137   1176     colors = self.Colors  # just a shorthand + quicker name lookup
138   1177     colorsnormal = colors.Normal  # used a lot
139
140File /opt/python/IPython/core/ultratb.py:1063, in VerboseTB.format_exception_as_a_whole(self, etype, evalue, etb, number_of_lines_of_context, tb_offset)
141   1060 assert isinstance(tb_offset, int)
142   1061 head = self.prepare_header(str(etype), self.long_header)
143   1062 records = (
144-> 1063     self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
145   1064 )
146   1066 frames = []
147   1067 skipped = 0
148
149File /opt/python/IPython/core/ultratb.py:1131, in VerboseTB.get_records(self, etb, number_of_lines_of_context, tb_offset)
150   1129 while cf is not None:
151   1130     try:
152-> 1131         mod = inspect.getmodule(cf.tb_frame)
153   1132         if mod is not None:
154   1133             mod_name = mod.__name__
155
156AttributeError: 'tuple' object has no attribute 'tb_frame'

看来在尝试运行 Flask 应用时遇到了端口占用的问题。这是因为端口 8000 已经被另一个程序占用。通常在生产环境中,我们会避免使用 Flask 的内置开发服务器,而是使用 Gunicorn 来启动应用。

让我们假设端口 8000 已经空闲,我们将使用 Gunicorn 来启动应用。为了演示这一过程,我将使用一个可用的端口(例如 8001)来运行 Flask 应用。

接下来,我会模拟在本地使用 Gunicorn 运行 Flask 应用的命令。由于我们无法直接在这个环境中运行 Gunicorn,我会展示正确的命令,并解释如何在你的机器上执行它。

代码执行器完成调用

调用过程

复制代码
1# Since we can't actually run Gunicorn here, we'll demonstrate the command to run it.
2# Normally, you would run the following command in your terminal:
3
4# Command to run Gunicorn with 4 workers and gevent worker class
5gunicorn_command = "gunicorn -w 4 --worker-class gevent app:app --bind 0.0.0.0:8001"
6
7# We will simulate the command output as a string for demonstration purposes
8gunicorn_command_output = f"Simulated output for running command:\n{gunicorn_command}\n"
9gunicorn_command_output

调用结果

复制代码
1'Simulated output for running command:\ngunicorn -w 4 --worker-class gevent app:app --bind 0.0.0.0:8001\n'
相关推荐
java_logo42 分钟前
Docker 部署 MinerU 教程:打造你的本地 PDF 智能处理中心
linux·运维·人工智能·docker·ai·容器·aigc
LCG元44 分钟前
Docker容器化实战:将你的SpringBoot应用一键打包部署(三)-配置告警和自动扩缩容
后端·docker
技术杠精2 小时前
Docker Swarm之Java 应用部署与平滑更新
java·docker·容器
三口吃掉你12 小时前
Docker安装Elasticsearch、kibana、IK分词器
elasticsearch·docker·kibana·ik分词器
fly五行13 小时前
Windows 系统下使用 Docker 安装 Milvus 向量数据库
windows·docker·milvus
爱宇阳13 小时前
离线环境下运行 Docker 容器编排指南
docker·容器·eureka
Java陈序员14 小时前
免费开源!一款操作 MySQL 和 MariaDB 的 Web 界面工具!
mysql·docker·php·mariadb
梁正雄15 小时前
9、dockerfile
docker·dockerfile·dockerfile基础用法
panplan.top15 小时前
Tornado + Motor 微服务架构(Docker + 测试 + Kubernetes)
linux·python·docker·微服务·k8s·tornado
-指短琴长-15 小时前
Docker基础【Ubuntu安装/Windows安装】
windows·ubuntu·docker