使用Eventlet与Docker部署Flask-SocketIO应用时遭遇语法错误
部署Flask-SocketIO应用到Render时的Eventlet/Gunicorn兼容错误
我有一个演示客户端摄像头视频流的Flask应用,本地运行正常,但在Render上通过Docker结合Gunicorn部署时,出现以下错误:
flask-client-camera | flask-client-camera | Error: class uri 'eventlet' invalid or not found: flask-client-camera | flask-client-camera | [Traceback (most recent call last): flask-client-camera | File "/usr/local/lib/python3.8/site-packages/gunicorn/util.py", line 124, in load_class flask-client-camera | return pkg_resources.load_entry_point("gunicorn", flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 534, in load_entry_point flask-client-camera | return get_distribution(dist).load_entry_point(group, name) flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2930, in load_entry_point flask-client-camera | return ep.load() flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2517, in load flask-client-camera | return self.resolve() flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2523, in resolve flask-client-camera | module = __import__(self.module_name, fromlist=['__name__'], level=0) flask-client-camera | File "/usr/local/lib/python3.8/site-packages/gunicorn/workers/geventlet.py", line 18 flask-client-camera | from gunicorn.workers.async import AsyncWorker flask-client-camera | ^ flask-client-camera | SyntaxError: invalid syntax flask-client-camera | ]
相关配置文件
Dockerfile
FROM python:3.8.13-slim-bullseye WORKDIR /app RUN apt-get -y update && apt-get install -y \ wget \ ffmpeg \ libsm6 \ libxext6 RUN pip install --upgrade setuptools COPY requirements.txt . RUN pip install -r requirements.txt ADD . . CMD gunicorn --worker-class eventlet -w 1 app:app
Flask应用代码(app.py)
import base64 import os import cv2 import numpy as np from flask import Flask, render_template, send_from_directory from flask_socketio import SocketIO, emit app = Flask(__name__, static_folder="./templates/static") app.config["SECRET_KEY"] = "secret!" socketio = SocketIO(app) @app.route("/favicon.ico") def favicon(): """ The favicon function serves the favicon.ico file from the static directory. :return: A favicon """ return send_from_directory( os.path.join(app.root_path, "static"), "favicon.ico", mimetype="image/vnd.microsoft.icon", ) def base64_to_image(base64_string): """ The base64_to_image function accepts a base64 encoded string and returns an image. The function extracts the base64 binary data from the input string, decodes it, converts the bytes to numpy array, and then decodes the numpy array as an image using OpenCV. :param base64_string: Pass the base64 encoded image string to the function :return: An image """ base64_data = base64_string.split(",")[1] image_bytes = base64.b64decode(base64_data) image_array = np.frombuffer(image_bytes, dtype=np.uint8) image = cv2.imdecode(image_array, cv2.IMREAD_COLOR) return image @socketio.on("connect") def test_connect(): """ The test_connect function is used to test the connection between the client and server. It sends a message to the client letting it know that it has successfully connected. :return: A 'connected' string """ print("Connected") emit("my response", {"data": "Connected"}) @socketio.on("image") def receive_image(image): """ The receive_image function takes in an image from the webcam, converts it to grayscale, and then emits the processed image back to the client. :param image: Pass the image data to the receive_image function :return: The image that was received from the client """ # Decode the base64-encoded image data image = base64_to_image(image) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) frame_resized = cv2.resize(gray, (640, 360)) encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90] result, frame_encoded = cv2.imencode(".jpg", frame_resized, encode_param) processed_img_data = base64.b64encode(frame_encoded).decode() b64_src = "data:image/jpg;base64," processed_img_data = b64_src + processed_img_data emit("processed_image", processed_img_data) @app.route("/") def index(): """ The index function returns the index.html template, which is a landing page for users. :return: The index """ return render_template("index.html") if __name__ == "__main__": socketio.run(app, port=os.getenv("PORT", default=5000), debug=os.getenv("DEBUG", default=True), host='0.0.0.0')
项目目录结构
├── app.py ├── Dockerfile ├── LICENSE.md ├── README.md ├── render.yaml ├── requirements.txt └── templates ├── index.html └── static ├── favicon.ico └── script.js
requirements.txt
Flask-SocketIO==4.3.1 python-engineio==3.13.2 python-socketio==4.6.0 Flask==2.0.3 Werkzeug==2.0.3 opencv_python==4.7.0.68 numpy==1.24.2 gunicorn==18.0 eventlet==0.33.3
问题排查与修复
核心原因
gunicorn 18.0是发布于2014年的老旧版本,它的geventlet.py文件使用了Python2风格的相对导入语法,在Python3.8环境下会触发SyntaxError,同时旧版gunicorn对eventlet的兼容性也较差。
修复步骤
- 更新gunicorn版本:将requirements.txt中的
gunicorn==18.0替换为gunicorn==20.1.0(该版本是兼容Python3.8+且支持eventlet worker的稳定版本) - 可选优化:将eventlet升级到
0.34.0,进一步提升兼容性 - 保持Dockerfile的CMD命令不变,依然使用
gunicorn --worker-class eventlet -w 1 app:app - 重新构建Docker镜像并部署到Render
修改后的requirements.txt核心依赖片段:
gunicorn==20.1.0 eventlet==0.34.0
内容的提问来源于stack exchange,提问作者Ifeanyi Nneji
相关产品推荐
相关产品推荐

