You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask+mod_wsgi部署Apache后SSH隧道启动时请求冻结问题

问题分析与解决方案

我之前处理过类似的mod_wsgi部署Flask应用时的阻塞问题,你的情况核心矛盾在于mod_wsgi的多进程/线程模型和SSH隧道启动的阻塞特性不兼容,再加上潜在的端口冲突问题,最终导致服务完全冻结、无日志输出。

为什么Werkzeug正常,Apache+mod_wsgi就挂了?

Werkzeug默认是单进程单线程运行,如果你在模块全局初始化Controler实例,a.start()的阻塞操作只会在启动时执行一次,完成后就能正常处理请求。但Apache+mod_wsgi默认是多进程模式(通常会启动多个子进程处理请求),每个子进程启动时都会重新执行模块级别的代码——也就是重新初始化Controler,而a.start()是阻塞调用,会导致子进程卡在隧道启动步骤,永远无法就绪处理请求。

另外你的代码里还有个隐藏坑:循环创建2个SSH隧道时,都绑定了本地5555端口,第二个隧道会因为端口被占用而启动失败,进一步加剧阻塞问题。

具体修复步骤

1. 改用非阻塞方式启动SSH隧道

sshtunnel库的open_tunnel返回的SSHTunnelForwarder对象提供了start_in_background()方法,会在后台线程启动隧道,不会阻塞进程初始化。同时要给每个隧道分配唯一的本地端口,避免冲突。修改Controler.py的__initConn方法:

import time  # 记得导入time模块

class Controler():
    ips = 2 # number of machines in the distributed service
    def __init__(self):
        self.con = []# ssh conn
        self.data = []# conn metadata
        self.__initConn()
    def __initConn(self):
        i = 0
        config = SSHConfig()
        config.parse(open('./config'))
        for j in range(self.ips):
            i += 1
            o = config.lookup('server' + str(i))
            # 给每个隧道分配唯一本地端口,避免冲突
            local_port = 5555 + j
            params = dict (
                ssh_address=o['ip'],
                ssh_port=22,
                ssh_host_key=None,
                ssh_username=o['user'],
                ssh_password=o['password'],
                ssh_private_key=None,
                remote_bind_address=('127.0.0.1', local_port),
            )
            a = open_tunnel(**params)
            # 后台启动隧道,不阻塞进程初始化
            a.start_in_background()
            self.con.append(a)
            # 等待隧道激活,避免后续操作时隧道未就绪
            while not a.is_active:
                time.sleep(0.1)

2. 延迟Controler初始化,避免多进程重复创建

不要在模块全局直接实例化Controler,而是用Flask的before_first_request钩子,确保每个进程只初始化一次隧道。修改init.py:

from flask import Flask, jsonify,make_response,abort,g,request,url_for
from flask_httpauth import HTTPBasicAuth
from flask_sqlalchemy import SQLAlchemy
from flask_bson import accept_bson, bsonify
import base64
from controler import Controler

# 先声明全局变量,不立即初始化
ctrl = None
app = Flask(__name__)
# ... 其他配置代码(如数据库、认证配置) ...

@app.before_first_request
def init_controller():
    global ctrl
    # 每个进程只初始化一次Controler
    if not ctrl:
        ctrl = Controler()

@app.route('/api/register', methods=['POST'])
def new_user():
    try:
        username = request.json.get('username')
        password = request.json.get('password')
    except:
        abort(400)
    user = users.new_user(username,password)
    return (jsonify({'username': user.username,'uri':url_for('get_user', username=user.username, _external=True)}), 201)
# ... 其他路由代码 ...

3. 调整mod_wsgi配置(可选)

如果你的应用不需要多进程带来的性能优势,可以在Apache的mod_wsgi配置里设置单进程模式,减少复杂度:

WSGIDaemonProcess your_app processes=1 threads=5 python-path=/path/to/your/app
WSGIProcessGroup your_app

不过更推荐前面的修复方式,保持多进程的性能优势。

验证修复

部署修改后的代码,重启Apache服务,发起请求测试:

  • 检查Apache的错误日志(通常在/var/log/apache2/error.log),确认没有端口冲突或隧道启动错误
  • 测试/api/register等接口,确认请求不再冻结

内容的提问来源于stack exchange,提问作者Álvaro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:19:54