当前位置:首页>python>定位python依赖包未安装报错的完整排查过程

定位python依赖包未安装报错的完整排查过程

  • 2026-10-11 07:31:35
定位python依赖包未安装报错的完整排查过程

定位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. 1. 我访问的 8000 端口到底是哪一个进程在监听?
  2. 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。

于是我立刻就能推断:

  • • 即使我在 .venv 里装了 pyodps
  • • 但服务进程用的是系统 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”时,我按这个顺序排:

  1. 1. 找出监听端口的 PID
    • • ss -lntp | grep :8000
  2. 2. 确认 PID 的启动命令与工作路径
    • • ps -fp <PID>
  3. 3. 确认服务进程用的 Python/venv
    • • readlink -f /proc/<PID>/exe(参考)
    • • 更准:<venv>/bin/python -c "import sys; print(sys.prefix)"
  4. 4. 用 venv python 直接做 import 断言(抓真实 ImportError)
    • • <venv>/bin/python -c "import odps; from odps import ODPS"
  5. 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 后台进程一定继承了正确环境。

因此我的排查顺序是:

  1. 1. 8000 端口是谁在监听?(找到 PID)
  2. 2. 这个 PID 的 python 可执行文件是什么?
  3. 3. 这个进程的启动命令是什么?
  4. 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 &

这一步是为了保证:

  • • 进程的 sys.prefix 在 .venv
  • • 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 明明显示已安装
  • • 但服务端接口仍提示“ODPS 驱动未安装”

因为我们的代码里 _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)上非常常见,解决策略通常是:

  • • pin 到兼容版本(短期)
  • • 或升级运行时(长期根治)

最新文章

随机文章