AI 量化平台 Linux 部署与公网访问排障实录

AI 量化平台 Linux 部署与公网访问排障实录

  • 日期:2026-10-14
  • 环境:阿里云 ECS(CentOS/Alibaba Linux,公网 IP x.x.x.x),项目路径 /root/ai_stock_server/ai-quant,Python 3.12(源码编译安装于 /usr/local/python312)
  • 目标:在 Linux 服务器上跑通 AI 股票量化平台(python server.py --port 8080),并通过 nginx 反向代理实现公网访问
  • 排障主线:Python 依赖问题 → 解释器残缺问题 → 安全组端口问题 → nginx 域名分发问题 → 浏览器缓存假象

问题一:ModuleNotFoundError: No module named 'sklearn',且 pip install sklearn 构建失败

现象

启动服务报 ModuleNotFoundError: No module named 'sklearn';执行 pip install sklearn 后 pip 下载到一个可疑的 sklearn-0.0.post12.tar.gz(仅 2.6 kB),随后 Getting requirements to build wheel ... error 构建失败。

原因

sklearn 是一个废弃的占位空壳包,作者故意让它安装时报错,目的是引导用户安装正确的包。PyPI 上真正的包名是 scikit-learn(安装后 import 时模块名仍然是 sklearn,代码无需修改)。报错输出中也明确提示:

The ‘sklearn’ PyPI package is deprecated, use ‘scikit-learn’ rather than ‘sklearn’ for pip commands.

解决

pip install scikit-learn
# 国内源慢可换清华源:
pip install scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple/

同时检查 requirements.txt,把其中的 sklearn 改为 scikit-learn。

验证:

python -c "from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor; print('ok')"

问题二:依赖装齐后仍报错 —— ImportError: The scipy install you are using seems to be broken

现象

pip install -r requirements.txt 成功执行,scikit-learn 1.9.1、scipy、joblib、narwhals、threadpoolctl 等均已安装。

但再次启动服务时,报错链变成了 scipy 加载失败:

File ".../scipy/_lib/_ccallback_c.py", line 1, in <module>
    from . import _ccallback_c
...
ModuleNotFoundError: No module named '_ctypes'
...
ImportError: The scipy install you are using seems to be broken,
(external modules cannot be imported), please try reinstalling.

原因

关键在 ModuleNotFoundError: No module named '_ctypes'。

这台机器的 Python 3.12 是源码编译安装的(路径 /usr/local/python312/),当初编译时系统缺少 libffi-devel 开发库,导致 _ctypes 这个标准库 C 扩展根本没有被编译出来。scipy 依赖 ctypes,因此 import scipy 必然崩溃。

这是解释器本身残缺,不是 pip 包的问题——所以重装 scipy / scikit-learn 多少遍都无效。

判定方法:

python -c "import ctypes"   # 同样报 No module named '_ctypes' 即确诊

解决(方案 A:补齐依赖后重新编译 Python,推荐)

# 1. 安装编译依赖
yum install -y libffi-devel gcc make openssl-devel bzip2-devel \
    readline-devel sqlite-devel zlib-devel

# 2. 解压 Python 源码(没有源码就重新下载同版本)
wget https://www.python.org/ftp/python/3.12.x/Python-3.12.x.tgz
tar -xzf Python-3.12.x.tgz          # 解压,-C 可指定目录
cd Python-3.12.x

# 3. 重新编译安装到同一 prefix(site-packages 里的包全部保留)
./configure --prefix=/usr/local/python312 --with-ensurepip=install
make -j4
make altinstall    # 用 altinstall,避免覆盖系统软链

编译输出中留意 “Failed to build these modules” 列表,确认 _ctypes 不在其中。

验证:

python -c "import ctypes; print('ctypes ok')"
python -c "import scipy; print('scipy ok')"

备选方案 B:换 Miniconda(预编译 Python,ctypes/ssl 齐全),代价是所有依赖要重装一遍。本例选择了方案 A,因为已装好的依赖可以原地保留。

修复后服务启动成功(python ./server.py --port 8080 进程正常运行)。


问题三:nginx 反代配好,公网访问 x.x.x.x:8081 超时

现象

nginx 配置 /etc/nginx/conf.d/stock.conf:监听 8081,反代到 127.0.0.1:8080。

浏览器访问 http://x.x.x.x:8081 报 ERR_CONNECTION_TIMED_OUT(响应超时)。

原因

报错是 TIMED_OUT(超时)而不是 REFUSED(拒绝连接)——这个区别是关键线索:

浏览器报错含义排查方向
ERR_CONNECTION_TIMED_OUT数据包发出去石沉大海(被防火墙/安全组静默丢弃)云安全组、iptables/firewalld
ERR_CONNECTION_REFUSED包到达了机器,但端口没人监听nginx 是否 listen、配置是否加载

即:阿里云安全组没有放行 8081 端口。8081 是非标准端口,不会像 80/443 那样默认放行。

解决

阿里云控制台 → ECS 实例 → 安全组 → 入方向 → 手动添加规则:

  • 端口范围:TCP 8081/8081
  • 授权对象:0.0.0.0/0

修改即时生效,无需重启。添加后公网访问 http://x.x.x.x:8081 成功打开量化平台。


问题四:希望 stock.agenticbtx.com(默认 80 端口)直接访问量化平台

现象

同一台服务器上有两个服务:

  • stock.agenticbtx.com(80 端口)→ 错误地显示了”宝宝益智乐园”(游戏站)
  • stock.agenticbtx.com:8081 → 正常显示 AI 量化平台

原因

nginx 在 80 端口上按 server_name 分发请求。stock.conf 当时只 listen 8081,没有任何 80 端口的 server 块精确匹配 stock.agenticbtx.com,请求便落到了游戏配置(30-game-http.conf)上,被当作默认站接走。

解决

在 stock.conf 中增加一行 listen 80;(nginx 匹配规则:精确 server_name 优先于 default_server,加上后不影响游戏域名):

server {
    listen 8081;
    listen 80;                        # 新增这一行
    server_name stock.agenticbtx.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_read_timeout 300s;      # 量化回测接口响应慢,放宽超时
        proxy_send_timeout 300s;
        client_max_body_size 50m;
    }

    location /ws {                    # WebSocket 支持(实时行情)
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

改完执行:

nginx -t && systemctl reload nginx

问题五(插曲):配置改完”还是不行”——浏览器缓存假象

现象

stock.conf 加上 listen 80 并 reload 后,浏览器访问 stock.agenticbtx.com 依然显示游戏页。

排查思路:绕开浏览器,在服务端自证清白

“改了没生效”的候选原因有四类:没 reload / server_name 撞车 / HTTPS 自动跳转 / 浏览器缓存。用 curl 在服务器本机直接验证,把问题域切开:

# 1. 带 Host 头直接打 80 端口,看返回状态
curl -sI -H "Host: stock.agenticbtx.com" http://127.0.0.1/

结果:HTTP/1.1 200 OK,且 grep -rn "server_name" /etc/nginx/conf.d/ 确认无撞车(游戏站是 game.agenticbtx.com,catchall 配置是 .bak 后缀不会被加载)。

# 2. 进一步确认返回内容是谁
curl -s -H "Host: stock.agenticbtx.com" http://127.0.0.1/ | grep -o "<title>[^<]*</title>"

结果:<title>AI 股票量化平台</title> —— nginx 侧已经完全正确。

真正原因

问题在浏览器:Chrome 对该域名有旧页面缓存,且会自动把曾访问过的站点升级到 HTTPS(HSTS/HTTPS-First),导致看到的仍是旧内容。

解决

  • 无痕窗口(Ctrl+Shift+N)+ 显式输入 http://stock.agenticbtx.com
  • 或清除该站点数据 / 在 chrome://net-internals/#hsts 删除域名的 HSTS 策略

清缓存后立刻正常。


最终结果

两个域名各归各位,全部验证通过:

访问方式内容
http://stock.agenticbtx.comAI 股票量化平台
http://game.agenticbtx.com宝宝益智乐园
http://x.x.x.x:8081AI 量化平台(备用入口)

经验总结

  1. 包名 ≠ 模块名:pip install 用 scikit-learn,代码里 import sklearn。遇到 2~3 kB 的”秒装小包”要警惕是占位空壳。
  2. 源码编译 Python 必须预装 libffi-devel:缺它则 _ctypes 静默编译失败,症状是 scipy/numpy 等 C 扩展报 “seems to be broken”,重装 pip 包无效。补依赖后重新 make altinstall 到同一 prefix 即可原地保住所有已装包。
  3. 看浏览器报错类型定位网络层:TIMED_OUT = 防火墙/安全组丢包;REFUSED = 端口没人监听。云主机非标准端口(8081 等)记得查安全组入方向。
  4. nginx 多站点靠 server_name 分发:精确域名匹配优先于 default_server;同端口同名冲突时按配置文件加载顺序(文件名字母序)先者胜出。
  5. “改了没生效”先绕开浏览器自证:curl -H "Host: xxx" http://127.0.0.1/ 一条命令就能把”服务端对不对”和”浏览器显示对不对”分开,避免在正确的配置上继续瞎改。

后续待办

  • HTTPS:certbot --nginx -d stock.agenticbtx.com(游戏站同理),消除”不安全”提示
  • 进程守护:把 server.py 交给 systemd 管理(Restart=always),避免 SSH 断开/重启后服务消失
  • 收口:80 端口稳定后删除 listen 8081 并撤销安全组 8081 规则,减少暴露面