当前位置:   article > 正文

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

基于 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

深色版本

  1. 1from flask import Flask
  2. 2
  3. 3app = Flask(__name__)
  4. 4
  5. 5@app.route('/')
  6. 6def hello_world():
  7. 7 return 'Hello, World!'
  8. 8
  9. 9if __name__ == '__main__':
  10. 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

深色版本

  1. 1FROM python:3.8-slim
  2. 2
  3. 3WORKDIR /app
  4. 4
  5. 5COPY requirements.txt .
  6. 6
  7. 7RUN pip install --no-cache-dir -r requirements.txt
  8. 8
  9. 9COPY . .
  10. 10
  11. 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

深色版本

  1. 1version: '3'
  2. 2services:
  3. 3 web:
  4. 4 build: .
  5. 5 command: gunicorn -w 4 --worker-class gevent app:app
  6. 6 volumes:
  7. 7 - .:/app
  8. 8 ports:
  9. 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. 1# Let's create a simple Flask application and then use Gunicorn to run it.
  2. 2from flask import Flask
  3. 3
  4. 4# Create the Flask application
  5. 5app = Flask(__name__)
  6. 6
  7. 7# Define a route for the application
  8. 8@app.route('/')
  9. 9def hello_world():
  10. 10 return 'Hello, World!'
  11. 11
  12. 12# This is just for demonstration purposes. In production, we would use Gunicorn.
  13. 13if __name__ == '__main__':
  14. 14 # Normally, you would run this with Gunicorn using the command line.
  15. 15 app.run(debug=True, host='0.0.0.0', port=8000)

调用结果

  1. 1 * Serving Flask app '__main__'
  2. 2
  1. 1 * Debug mode: on
  2. 2
  1. 1Address already in use
  2. 2Port 8000 is in use by another program. Either identify and stop that program, or start the server with a different port.
  3. 3

error:

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

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

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

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

代码执行器完成调用

调用过程

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

调用结果

1'Simulated output for running command:\ngunicorn -w 4 --worker-class gevent app:app --bind 0.0.0.0:8001\n'
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/码创造者/article/detail/943555
推荐阅读
相关标签
  

闽ICP备14008679号