定位pyodps未安装报错的完整排查过程
1. 现象复述:我明明装了 pyodps,接口却说“驱动未安装”
我在服务器上调用:
curl -sS -X POST "http://<server-ip>:8000/api/v1/knowledge/sql/test-connection" \ -H "Content-Type: application/json" \ -d '{"connection_key":"dev_odps_pb_biz_credit"}'
返回:
{ "ok":"false", "connection_key":"dev_odps_pb_biz_credit", "driver":"odps", "message":"ODPS(MaxCompute) 驱动未安装。请在**运行本服务的同一 Python** 下执行: pip install -r requirements.txt (或 pip install pyodps)"}
但我明明执行过:
pip install pyodps
且提示 Requirement already satisfied。我的第一个困惑就是:为什么安装了还提示未安装?
2. 关键背景:为什么“没装依赖也能启动成功”?
这也是我当时的一个疑惑:服务没装 ODPS 相关依赖,好像也能启动。
原因是:ODPS 依赖在代码里是**按需导入(lazy import)**的。服务启动时不 import odps 包,只有在你调用 ODPS 相关接口时才会触发导入。
因此:
- • 服务可以启动成功(因为启动阶段没走到 ODPS 代码路径)
- • 但当你调用
test-connection/query 且配置 driver=odps 时,才会 import odps,这时候缺依赖才会报错
这解释了“为什么能启动”,但还没解释“为什么我装了也报未安装”。
3. 排查总原则:先确认“请求打到的是哪个进程”,再确认“那个进程用的是哪个 Python 环境”
遇到“明明装了但运行时说没装”,我不再先纠结 pip 输出,而是先做两件事:
- 1. 我访问的 8000 端口到底是哪一个进程在监听?
- 2. 那个进程用的是哪一个 Python?是不是我装依赖的那个 venv?
这是因为最常见的坑就是:我在 A 环境里 pip install,但服务跑在 B 环境。
4. 第一步:用 ss 查出监听 8000 的 PID
ss -lntp | grep ':8000'
我得到了类似输出(示例):
LISTEN ... users:(("python",pid=26654,...),("python",pid=26653,...))
如果你用 --workers 2,通常会看到 master + worker,PID 不止一个是正常的。
5. 第二步:用 /proc//exe 看这个进程实际的 Python 可执行文件
我对 PID 执行:
readlink -f /proc/<PID>/exe
当时我看到的是:
/opt/rh/rh-python38/root/usr/bin/python3.8
这一步非常关键:它说明监听 8000 的并不是我期望的项目 venv python(/data/coagent/coagent-data-service/.venv/bin/python),而是系统 Python。
于是我立刻就能推断:
- • 但服务进程用的是系统 Python,它当然找不到
.venv 里的包
这一步解决了我最核心的疑惑之一:“为什么我装了依赖,接口还是说没装?”因为我装在 venv,服务跑在 system python。
6. 第三步:修正启动方式,确保后台进程一定使用 venv 的 python
我原来的启动方式类似:
nohup python -m uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2 > nohup.out 2>&1 &
问题在于:这里的 python 可能不是 .venv/bin/python(nohup、PATH、shell 环境、以及你实际执行脚本的方式,都会影响最终解析到哪个 python)。
我把启动命令改成绝对路径的 venv python:
cd /data/coagent/coagent-data-servicenohup /data/coagent/coagent-data-service/.venv/bin/python -m uvicorn main:app \ --host 0.0.0.0 --port 8000 --workers 2 \ > nohup.out 2>&1 &
并且在启动前先 kill 掉旧进程,避免 8000 被旧进程占用:
kill <OLD_PID_1> <OLD_PID_2> ...
启动后我再次 ss -lntp | grep :8000 找到新 PID,再次 ps -fp <PID> 确认命令行里确实是 .venv/bin/python -m uvicorn ...。
7. 一个“容易误判”的点:为什么 readlink 仍可能显示系统 python3.8?
在部分环境里,即便你用 .venv/bin/python 启动,/proc/<pid>/exe 仍可能显示系统 python3.8 的路径。这是 venv 的实现方式决定的:venv 的 python 可能是软链接/封装到系统 python。
所以我补了一步更靠谱的验证:检查 sys.prefix:
/data/coagent/coagent-data-service/.venv/bin/python -c "import sys; print(sys.executable); print(sys.prefix)"
只要输出里 sys.prefix 是:
/data/coagent/coagent-data-service/.venv
就说明这个 Python 运行时环境确实是 venv(会从 venv site-packages 里加载依赖)。
8. 真正根因:pyodps 安装了,但 import 失败(被统一包装成“驱动未安装”)
当我确认服务确实在用 venv 之后,按理 pyodps 应该能 import。但接口仍提示“驱动未安装”。这时候我意识到一个可能性:
代码里捕获的不是“pip 是否安装”,而是 “import 是否成功”。ImportError 不等于没安装包,也可能是依赖链导入失败。
于是我在服务器上直接用 venv python 验证导入:
/data/coagent/coagent-data-service/.venv/bin/python -c "import odps; from odps import ODPS"
我得到了关键的真实错误:
ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips ...'
解释一下这段话:
- • 服务器的 Python 3.8
ssl 模块是基于 OpenSSL 1.0.2k-fips 编译的(比较老) - • 但
urllib3 v2 强制要求 OpenSSL >= 1.1.1 - •
pyodps 依赖 requests,requests 依赖 urllib3 - • 所以即使 pyodps “安装了”,在 import 时也会因为
urllib3 导入失败而整体失败
最终效果就是:服务捕获 ImportError,返回统一提示“驱动未安装”。但本质根因是:OpenSSL 版本与 urllib3 v2 不兼容。
9. 解决方式:降级 urllib3(让它兼容 OpenSSL 1.0.2)
在不升级系统 OpenSSL 的前提下,最小改动是把 urllib3 降级到 v1.x(兼容 OpenSSL 1.0.2),同时把 requests 降到与其兼容的版本范围:
source /data/coagent/coagent-data-service/.venv/bin/activatepip install "urllib3<2" "requests<2.32" --upgrade# 也可以更明确锁版本,例如:# pip install "urllib3==1.26.18" "requests==2.31.0" --upgrade
我再验证导入:
/data/coagent/coagent-data-service/.venv/bin/python -c "import odps; from odps import ODPS; print('pyodps import ok')"
导入成功后,重启服务,再跑 test-connection:
curl -sS -X POST "http://<server-ip>:8000/api/v1/knowledge/sql/test-connection" \ -H "Content-Type: application/json" \ -d '{"connection_key":"dev_odps_pb_biz_credit"}'
最终拿到:
{"ok":"true","connection_key":"dev_odps_pb_biz_credit","driver":"odps","message":"connected"}
至此闭环。
10. 我最终沉淀的一套排查 checklist(以后遇到类似问题直接套用)
当接口报“驱动未安装/ImportError”时,我按这个顺序排:
- •
readlink -f /proc/<PID>/exe(参考) - • 更准:
<venv>/bin/python -c "import sys; print(sys.prefix)"
- 4. 用 venv python 直接做 import 断言(抓真实 ImportError)
- •
<venv>/bin/python -c "import odps; from odps import ODPS"
- 5. 如果 ImportError 指向系统库(OpenSSL/GLIBC):
- • 短期:pin 兼容版本(如
urllib3<2) - • 长期:升级运行环境(OpenSSL >= 1.1.1 / 更高版本 Python)
这套流程的好处是:不会被“我明明装了”这种错觉带偏,最终一定能定位到“到底是哪个进程、哪个环境、哪条 import 链路失败”。
5. 排查思路总纲:先确认“我请求打到的是哪个进程”,再确认“那个进程用的是哪个 Python”
我当时用的是 nohup + uvicorn,多 worker 启动:
nohup python -m uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2 > nohup.out 2>&1 &
这类启动方式最容易踩坑:你在 shell 里看到 (.venv) 不代表 nohup 后台进程一定继承了正确环境。
因此我的排查顺序是:
- 2. 这个 PID 的 python 可执行文件是什么?
- 4. 如果确实在用 venv,再进一步验证
import odps 到底能不能成功。
5.1 端口到 PID:ss
ss -lntp | grep ':8000'
能看到类似:
LISTEN ... users:(("python",pid=26654,...),("python",pid=26653,...))
多出来的 PID 往往是 worker(master + workers)。
5.2 PID 到 python:/proc//exe
readlink -f /proc/<PID>/exe
我第一次看到的是:
/opt/rh/rh-python38/root/usr/bin/python3.8
这说明当时监听 8000 的进程不是我期望的 .venv/bin/python,自然也导入不到我装在 .venv 里的 pyodps。
5.3 进程启动命令:ps
ps -fp <PID>
通过 CMD 我可以确认是哪个启动命令拉起的,有没有 --workers 2,在哪个目录启动等。
6. 修复第一层问题:启动命令必须显式使用 venv 的 python
我把启动命令改成显式 venv python:
cd /data/coagent/coagent-data-servicenohup /data/coagent/coagent-data-service/.venv/bin/python -m uvicorn main:app \ --host 0.0.0.0 --port 8000 --workers 2 \ > nohup.out 2>&1 &
这一步是为了保证:
- • pip 安装的依赖(
.venv/lib/python3.8/site-packages)能被进程 import 到
6.1 一个容易误解的点:为什么 /proc/<pid>/exe 仍可能指向系统 python?
我后来发现:
/data/coagent/coagent-data-service/.venv/bin/python -c "import sys; print(sys.executable); print(sys.prefix)"
输出是:
sys.executable = /data/.../.venv/bin/pythonsys.prefix = /data/.../.venv
但 /proc/<pid>/exe 可能仍显示系统 python3.8 的路径。这是 venv 的实现细节:.venv/bin/python 经常是软链接到某个 python3.8,可执行文件仍是系统 python,但 sys.path/prefix 指向 venv,这才是关键。
因此:判断是否真在用 venv,更可靠的方法是看 sys.prefix,不是只看 /proc/<pid>/exe。
7. 真正根因:不是“没装 pyodps”,而是 urllib3 v2 与服务器 OpenSSL 太老不兼容
当我确认服务进程确实在用 venv 之后,我进一步验证:
/data/coagent/coagent-data-service/.venv/bin/python -c "import odps; from odps import ODPS; print('pyodps import ok')"
结果直接爆栈:
ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips ...'
这就彻底解释了为什么:
- •
pip install pyodps 明明显示已安装
因为我们的代码里 _import_pyodps() 捕获的是 ImportError,而 ImportError 不仅仅代表“包不存在”,还可能代表:
- • 包存在,但其依赖导入失败(缺依赖/ABI 不匹配/系统库版本太低)
- • 包存在,但运行时环境不满足(这里是 OpenSSL 版本)
在我的服务器上,Python 3.8 的 ssl 是用 OpenSSL 1.0.2k-fips 编译的;而 urllib3 v2 强制要求 OpenSSL >= 1.1.1,直接导入失败。
导入链路大概是:
pyodps -> odps -> requests -> urllib3 -> ssl(OpenSSL) -> ImportError
于是服务就把它“看起来像驱动没装”一样报出来。
8. 解决方案:把 urllib3 降回 1.26.x(兼容 OpenSSL 1.0.2)
在不升级系统 OpenSSL 的前提下,最小改动是 pin 依赖版本:
source /data/coagent/coagent-data-service/.venv/bin/activatepip install "urllib3<2" "requests<2.32" --upgrade# 或者更明确地锁:# pip install "urllib3==1.26.18" "requests==2.31.0" --upgrade
然后再验证导入:
python -c "import ssl; print(ssl.OPENSSL_VERSION)"python -c "import urllib3,requests; print(urllib3.__version__, requests.__version__)"python -c "import odps; from odps import ODPS; print('pyodps import ok')"
最后重启服务,执行:
curl -sS -X POST "http://<server-ip>:8000/api/v1/knowledge/sql/test-connection" \ -H "Content-Type: application/json" \ -d '{"connection_key":"dev_odps_pb_biz_credit"}'
我最终拿到了:
{"ok":"true","connection_key":"dev_odps_pb_biz_credit","driver":"odps","message":"connected"}
至此问题闭环。
9. 复盘:这次排查我学到的“可复用 checklist”
以后再遇到“我明明装了依赖但服务说没装”的问题,我会按这个顺序排:
9.1 先确认请求打到的进程
- •
ss -lntp | grep :<port> 找 PID - •
ps -fp <pid> 看启动命令、工作目录
9.2 再确认进程用的 Python/venv
不要只看 shell prompt 有没有 (.venv),而是:
- • 对正在运行的 PID 看
/proc/<pid>/exe(仅作参考) - • 更关键:在 venv python 下看
sys.prefix:
<venv>/bin/python -c "import sys; print(sys.executable); print(sys.prefix)"
9.3 对“按需导入”的模块,要手动做一次 import 验证
只要接口提示“驱动未安装”,我第一时间跑:
<venv>/bin/python -c "import odps; from odps import ODPS"
如果失败,把 traceback 当作真正的根因,不要被“驱动未安装”这句话误导。
9.4 识别底层系统库兼容问题(OpenSSL/GLIBC)
像本次这种 urllib3 v2 与 OpenSSL 版本不兼容,是典型的“包装成 ImportError”问题。在老系统(尤其 CentOS7 + OpenSSL1.0.2)上非常常见,解决策略通常是: