赞
踩
在现代 Web 开发中,为了保证服务的稳定性和响应速度,我们需要确保应用程序能够处理高并发请求。Flask 是一个轻量级的 Web 框架,非常适合快速开发 Web 应用程序。然而,默认情况下,Flask 的开发服务器并不适合生产环境,因为它不支持多线程或多进程,并且没有负载均衡能力。本文将详细介绍如何使用 Gunicorn 作为 WSGI 服务器来部署 Flask 应用,并通过 Docker 容器化来实现易于管理的高并发部署方案。
首先,你需要安装以下软件:
确保你的开发环境已经配置好了 Python 以及 pip。接下来,安装 Flask 和 Gunicorn:
Bash
深色版本
1pip install flask gunicorn
创建一个新的 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')
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 进行异步处理。你可以根据实际情况调整这些参数。
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 服务器。
构建 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 应用。
为了更好地管理多个容器和服务之间的关系,我们可以使用 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
在生产环境中,还需要考虑以下几点:
本文介绍了如何使用 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'
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。