Python 3.11 编译后缺少 _ssl 模块:从 OpenSSL 共享库到重新编译的完整修复记录
在一台使用 yum 管理软件包的 Linux 服务器上源码安装 Python 3.11.9,安装过程没有明显报错,但执行 SSL 测试时失败:
报错内容:
这个问题比较容易误判。Python 可执行文件已经安装成功,python3.11 --version 也能正常输出,但这并不代表所有标准扩展都编译成功。_ssl 属于可选的 C 扩展,编译失败时,Python 主程序仍可能继续安装。
本文记录一次完整的排查和修复过程。
一、问题现象
源码安装 Python 的主要操作如下:
第一次编译时,系统没有提前安装完整的开发依赖。后面补装了依赖包并重新编译:
但重新安装 Python 后,import ssl 仍然失败。
为了绕过系统自带 OpenSSL 版本较旧的问题,又单独编译了 OpenSSL 1.1.1w:
这里埋下了第一个问题。
二、根因分析
这次故障不是单一原因,而是几个问题叠加。
1. OpenSSL 被编译成静态库
no-shared 表示不生成共享库,只生成:
Python 的 _ssl 模块最终需要生成一个动态扩展文件:
在这种构建方式下,_ssl 很可能链接失败。即使 make altinstall 执行完成,安装出来的 Python 仍然可能没有 SSL 功能。
2. 动态链接器找不到自定义 OpenSSL
将 OpenSSL 改成共享库重新编译后,直接运行:
又出现了新的错误:
这说明 libssl.so.1.1 已经存在,但系统动态链接器不知道它位于:
自定义软件安装到 /usr/local 的子目录后,不能只确认文件存在,还要处理运行时库路径。
3. Python 保留了旧的构建缓存
只执行 make clean 不一定能清除之前的检测结果。Python 源码目录中的 config.cache、build 目录和旧 Makefile 都可能继续影响后续编译。
因此,重新编译前需要彻底清理。
三、修复方案选择
这里有两种处理方式。
方案一:使用系统 OpenSSL 开发包
适合系统自带 OpenSSL 版本满足 Python 3.11 要求的环境。
然后直接重新配置 Python,不指定自定义 OpenSSL 路径。
优点是维护简单,系统负责动态库路径和安全更新。
缺点是老系统的 OpenSSL 版本可能不满足 Python 3.11 的编译要求。
方案二:独立安装 OpenSSL
适合 CentOS、RHEL 兼容旧版本系统,或者不能替换系统 OpenSSL 的服务器。
安装目录单独使用:
Python 通过编译参数和 RPATH 指向这个目录,不覆盖系统 OpenSSL。
本次采用第二种方案。
四、重新编译 OpenSSL 共享库
进入 OpenSSL 源码目录:
清理旧的编译结果:
重新配置。这里必须使用 shared,不能再使用 no-shared:
开始编译和安装:
检查共享库:
正常情况下可以看到:
五、注册 OpenSSL 动态库路径
先做一次临时测试:
如果能够正常显示版本,说明 OpenSSL 编译没有问题,故障只在动态库搜索路径。
写入系统动态库配置:
刷新缓存:
确认系统已经识别:
再次测试:
此时命令应当可以正常执行。
六、彻底清理 Python 编译缓存
进入 Python 源码目录:
清理旧构建结果:
make distclean 比 make clean 更彻底。遇到依赖库变化时,建议直接执行这一组命令,不要在旧构建结果上反复覆盖编译。
七、重新配置 Python
设置编译头文件、链接库和运行时库路径:
重新执行配置:
首次修复时不建议马上加 --enable-optimizations。先把 _ssl 编译问题解决,再考虑启用优化编译,这样排障更直接。
八、编译并检查 _ssl
开始编译,同时保存日志:
检查 _ssl 编译记录:
本次修复后,日志中出现了:
以及最终的链接命令:
同时可以看到 _ssl 链接到了自定义 OpenSSL:
这说明 _ssl 已经成功编译。
如果日志中出现下面的内容,则不能继续安装:
或者:
需要先检查 OpenSSL 头文件、共享库和链接参数。
九、安装 Python 3.11
确认编译没有问题后执行:
使用 altinstall 可以避免覆盖系统默认的 python 命令。
安装完成后,明确使用完整路径验证:
本次最终验证成功,ssl 和 _ssl 均可正常导入。
十、检查实际链接的 OpenSSL
找到 _ssl 扩展:
检查动态库依赖:
预期结果应指向自定义目录:
这一步很重要。import ssl 成功只能说明当前能运行,ldd 才能确认它实际使用的是哪一套 OpenSSL。
十一、完整修复命令
下面这组命令适合在相同目录结构下直接参考。
重新编译 OpenSSL
重新编译 Python
最终验证
十二、实际部署中的几个坑
不要覆盖系统 OpenSSL
不要把自定义的 libssl.so.1.1 直接复制到 /usr/lib64,也不要替换系统的 openssl 命令。
系统中的 yum、curl、ssh 和其他组件可能依赖原有 OpenSSL。直接覆盖系统库,修好 Python 的同时可能破坏系统工具。
自定义 OpenSSL 应安装到独立目录,再通过以下方式引用:
make altinstall 成功不代表扩展全部成功
Python 构建时,一些可选扩展失败不会让整个编译过程退出。
安装前应主动检查:
除了 _ssl,还可以关注:
这些模块都依赖对应的开发包。
不要只执行 make clean
更换 OpenSSL 路径、版本或编译参数后,建议使用:
这样可以避免旧检测结果继续生效。
验证时使用完整路径
服务器上可能存在多个 Python 3.11:
测试时最好明确执行:
否则可能一直在验证旧版本,造成已经修好但看起来仍然报错的假象。
十三、故障链路总结
本次问题的完整链路如下:
这类问题不难修,难点在于编译过程看起来是成功的。源码安装 Python 后,至少应检查一次 ssl、sqlite3、bz2、lzma 和 readline,避免服务上线后才发现标准模块缺失。
讨论 0