一次 Python 运行不起来的修复经历

最近登陆了一台很久没有使用的 Linux 机器,发现 Python 命令运行不起来了。修复的过程也非常复杂,在这里记录一下。

一次 Python 运行不起来的修复经历
Photo by Lawless Capture / Unsplash

最近登陆了一台很久没有使用的 Linux 机器,发现 Python 命令运行不起来了,报错提示:

.pyenv/versions/3.8.2/bin/python: error while loading shared libraries: libcrypt.so.1: cannot open shared object file: No such file or directory

看到这个错误提示有点懵,之前还没遇到过这种问题。由于 Python 是通过 pyenv 安装的,因此本着遇到问题就重装的快速思路,尝试卸载这个版本的 Python 后重新安装。结果在安装 Python 时又报错了:

ERROR: The Python ssl extension was not compiled. Missing the OpenSSL lib?

这台机器上,我使用 brew 安装了 openssl@1.1 ,具体版本是 1.1.1q。为啥明明安装了,还提示缺少 OpenSSL 呢?猜测可能是 brew 相关的问题,于是直接从官网源码安装 OpenSSL:

wget https://www.openssl.org/source/openssl-1.1.1q.tar.gz
tar zxvf openssl-1.1.1q.tar.gz
./config shared
make
make test
make install

如果 wget 这一步骤报证书的问题的话,可能是当前机器的 CA 证书比较老旧。可以先通过加 --no-check-certificate 参数来跳过这一问题。

安装完之后,发现由于自己将 brew 的 bin path 放在了 PATH 的最前面,而 brew 的 bin path 中已经有了 openssl 这个文件,因此新安装的 openssl 还不能直接通过 openssl 命令来使用。切到没有将 brew 的 bin path 加到 PATH 最前面的另一用户,运行 openssl version 命令,发现又报错了:

openssl: /usr/lib/x86_64-linux-gnu/libssl.so.1.1: version `OPENSSL_1_1_1' not found (required by openssl)

啊?这回彻底懵了。刚安装的 openssl,又报错?

搜索了一下,发现刚刚安装的 openssl,动态链接库的位置是在 /usr/local/lib 的。并不是在 /usr/lib/x86_64-linux-gnu/。/usr/lib/x86_64-linux-gnu/libssl.so 应该是系统自带的,版本没到 1.1.1,所以报错。

尝试把 /usr/local/lib 加到 LD_LIBRARY_PATH 中,这一环境变量控制的是动态链接库的搜索路径。

export LD_LIBRARY_PATH=/usr/local/lib

加完发现终于能运行 openssl 了:

root@machine:~/openssl-1.1.1q# openssl version
OpenSSL 1.1.1q  5 Jul 2022

这时再尝试构建一次 python:

root@machine:~/openssl-1.1.1q# pyenv install 3.10.7
....(省略部分输出)
Installed Python-3.10.7 to /home/user/.pyenv/versions/3.10.7

终于安装成功了。感动。运行一下看看正不正常:

$ python

Python 3.10.7 (main, Oct 21 2022, 13:06:57) [GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

发现也能正常使用。那么问题终于解决了。不过,难道一定要在 shell 中手动设置 LD_LIBRARY_PATH 才能让程序正常运行起来?这也太麻烦了吧。

经过一些搜索,发现系统级别上有一个动态链接库的查找路径列表,文件名为 /etc/ld.so.conf。打开机器上的这个文件,发现只有一行:

include /etc/ld.so.conf.d/*.conf

显然,这句话的意思是将 /etc/ld.so.conf.d 这个文件夹中的所有 .conf 内容作为此文件的内容。于是查看这个文件夹的内容:

root@machine:~# ls -l /etc/ld.so.conf.d
total 12
-rw-r--r-- 1 root root 38 Jan 17  2017 fakeroot-x86_64-linux-gnu.conf
-rw-r--r-- 1 root root 44 Mar 21  2016 libc.conf
-rw-r--r-- 1 root root 68 Feb  7  2019 x86_64-linux-gnu.conf

其中,libc.conf 的内容是:

# libc default configuration
/usr/local/lib

可以看到这里已经包含了 /usr/local/lib 这个路径。因此,/usr/local/lib 已经在系统级别的动态链接库查找路径中。(如果没有的话,需要手动添加一下)

那么,为什么不加 LD_LIBRARY_PATH 还是找不到动态链接库呢?经过进一步的搜索,发现动态链接库列表是存在缓存的,缓存文件的位置是 /etc/ld.so.cache。这个缓存文件,在每次安装完 lib 后,需要以 root 身份手动运行 ldconfig 命令刷新一次,否则就识别不到 lib。

因此,我们以 root 身份运行 ldconfig 命令,然后再通过 ldconfig -p|grep ssl 命令来检查 /usr/local/lib 中的 openssl 是否已经在缓存的路径中了。

root@machine:~# ldconfig -p|grep ssl
	libssl3.so (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libssl3.so
	libssl.so.1.1 (libc6,x86-64) => /usr/local/lib/libssl.so.1.1
	libssl.so.1.1 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libssl.so.1.1
	libssl.so.1.0.2 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libssl.so.1.0.2
	libssl.so (libc6,x86-64) => /usr/local/lib/libssl.so
	libssl.so (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libssl.so

发现 /usr/local/lib/libssl.so.1.1 已经在缓存的路径中了。这样的话,对于所有用户,我们就无需指定 LD_LIBRARY_PATH 了。

总结:

  • 安装 lib 时,需要确认安装路径是否在 /etc/ld.so.conf 定义的查找路径列表中,如果不在的话要添加一下、或者在安装时指定已经存在于查找列表中的路径
  • 在每次安装完 lib 后,需要以 root 身份手动运行 ldconfig 命令刷新一次动态链接库的列表缓存,否则就识别不到 lib