CentOS 6でPython 3でTensorFlowを使う方法、もしくはdynamic linkerとshared objectの差し替え
Python 3
CentOS 6のopensslのバージョン(1.0.1e)の関係で、Python 3.7以上はビルドが失敗します。
- https://docs.python.org/ja/3/whatsnew/3.7.html
The improved host name check requires a libssl implementation compatible with OpenSSL 1.0.2 or 1.1. Consequently, OpenSSL 0.9.8 and 1.0.1 are no longer supported
今回は3.7にこだわりはないので、pyenvでPython 3.6の最新の3.6.8をインストールして、pipでtensorflowをインストールします。詳細は割愛ググってね。
glibc, stdc++
インストールに成功しても、使おうとするとエラーが出ます。
$ python3.6 -c 'import tensorflow; print("hello")' Traceback (most recent call last): ... ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so) ... $ ldd /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so |& grep 'not found' | awk '{print $4}' `GLIBC_2.16' `GLIBC_2.14' `GLIBC_2.17' `GLIBC_2.15' `GLIBCXX_3.4.15' `GLIBCXX_3.4.19' `GLIBCXX_3.4.14' `CXXABI_1.3.7' `GLIBCXX_3.4.17' `GLIBCXX_3.4.18' `CXXABI_1.3.5' `GLIBC_2.14' `GLIBC_2.16' `GLIBC_2.17' `GLIBCXX_3.4.14' `GLIBCXX_3.4.18' `CXXABI_1.3.5' `GLIBCXX_3.4.15' `GLIBCXX_3.4.19' `GLIBCXX_3.4.17' `CXXABI_1.3.7'
どようやらglibc 2.17とlibstdc++ 3.4.19が必要そうです。
さすがにシステムのglibcを置き換えるのは怖いので、別のディレクトリに展開して今回のpython3.6はそこのshared objectを使うようにします。
glibcとlibstdc++は、https://pkgs.org/ で探してCentOS 7のを拝借します。
$ cd ~/tmp/ $ wget \ http://mirror.centos.org/centos/7/updates/x86_64/Packages/glibc-2.17-260.el7_6.5.x86_64.rpm \ http://mirror.centos.org/centos/7/updates/x86_64/Packages/glibc-devel-2.17-260.el7_6.5.x86_64.rpm \ http://mirror.centos.org/centos/7/os/x86_64/Packages/libstdc++-4.8.5-36.el7.x86_64.rpm \ http://mirror.centos.org/centos/7/os/x86_64/Packages/libstdc++-devel-4.8.5-36.el7.x86_64.rpm \ ;
~/lib/
の下に展開します。
$ cd ~/lib/ $ for p in ~/tmp/*.rpm; do echo $p; rpm2cpio $p | cpio -idv; done $ ls -F etc/ lib64/ sbin/ usr/ var/
以下、パス指定が長くなるので簡略化のため変数に入れときます。
py36="$HOME/.pyenv/versions/3.6.8/bin/python3.6" pywrap_tensorflow_internal="$HOME/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so" my_ld="$HOME/lib/lib64/ld-linux-x86-64.so.2" my_libs="$HOME/lib/usr/lib64:$HOME/lib/lib64"
ld-linux.so
はコマンドとしても実行できて、 --library-path
でdynamic loadするshared objectのパスを指定できます。環境変数 LD_LIBRARY_PATH
でも同様のことができますが、作用範囲が子プロセスまで及ぶかどうかという違いがあります。
ld-linux.so
経由で python3.6
を起動して、先程エラーになったコードを実行してみます。
$ $my_ld --library-path $my_libs $py36 -c 'import tensorflow; print("hello")' hello
今度は正常終了しました!やったネ!!
child process
早速、最終的に走らせたいスクリプトを実行してみると…
$ $my_ld --library-path $my_libs $py36 oreno.py * Serving Flask app "server" (lazy loading) * Environment: production ... Traceback (most recent call last): ... ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so) ...
失敗しました。
エラーメッセージからすると、システムのglibcをロードしてしまっているようです。strace -f -s 1000 $my_ld ...
で確認してみると、後半で $my_ld
を介さないで $py36
を execve
して異常終了していました。詳細は確認してませんが、どっかで fork
してるんじゃないかと思います。
子プロセスにも新しいglibcの場所を教えてあげればよいので、試しに LD_LIBRARY_PATH
をセットして実行してみると…
$ env LD_LIBRARY_PATH=$my_libs $my_ld --library-path $my_libs $py36 oreno.py ... /home/ore/.pyenv/versions/3.6.8/bin/python3.6: relocation error: /home/ore/lib/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
失敗しました。
たぶん、システムの /lib64/ld-linux-x86-64.so.2
で新しいglibcをロードしようとして失敗しているんだと思います。
もし、成功したとしても、LD_LIBRARY_PATH
は作用範囲が大きいので、他の方法があれば避けたい手段です。
patchelf
Linuxの実行ファイル形式のELFには、dynamic linker (interpreter) とライブラリのサーチパス(runpath)を埋め込むことができます。これらは readelf
や patchelf
で確認することができます。
CentOS 6用の patchelf
はEPELにあります。
$ readelf -a /usr/bin/mailq | grep -e interpreter -e runpath [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] 0x000000000000001d (RUNPATH) Library runpath: [/usr/lib/postfix] $ patchelf --print-interpreter --print-rpath /usr/bin/mailq /lib64/ld-linux-x86-64.so.2 /usr/lib/postfix
patchelf
はこれらの情報を変更することができるので、$py36
のinterpreterとrunpathを変更してしまえば、 $my_ld --library-path $my_libs
の前置が不要になるというわけです。
やってみましょう。
$ cp -p $py36 ${py36}.bak $ patchelf --set-interpreter $my_ld --set-rpath $my_libs $py36
さっそく、$my_ld
の前置なしで実行してみると…
$ $py36 -c 'import tensorflow; print("hello")' Traceback (most recent call last): ... ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /home/ore/.pyenv/versions/3.6.8/lib/python3.6/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so)
エラーが出ました。が、これは想定内です。
$py36
は期待したとおり新しいglibcをロードできたのですが、_pywrap_tensorflow_internal.so
がlibstdc++をロードしようとして新しいのが見つけられずエラーになっているわけです。(glibcは既に $py36
が新しいのをロード済みなので大丈夫)
なので、_pywrap_tensorflow_internal.so
もrunpathを書き換えてしまいましょう。
_pywrap_tensorflow_internal.so
は既にrunpathが設定されているので、それに追加する感じでセットします。(純粋なshared objectなのでinterpreterの変更の必要はありません)
$ORIGIN
が展開されないようにシングルクォートするのを忘れないでください。
$ patchelf --print-rpath $pywrap_tensorflow_internal $ORIGIN/../../_solib_k8/_U_S_Stensorflow_Spython_C_Upywrap_Utensorflow_Uinternal.so___Utensorflow:$ORIGIN/:$ORIGIN/.. $ cp -p $pywrap_tensorflow_internal ${pywrap_tensorflow_internal}.bak $ patchelf --set-rpath '$ORIGIN/../../_solib_k8/_U_S_Stensorflow_Spython_C_Upywrap_Utensorflow_Uinternal.so___Utensorflow:$ORIGIN/:$ORIGIN/..'":$my_libs" $pywrap_tensorflow_internal
さて、
$ $py36 -c 'import tensorflow; print("hello")' hello $ $py36 oreno.py * Serving Flask app "server" (lazy loading) * Environment: production ... INFO:werkzeug: * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ...
ようやく正常に起動できました!!!
が、socket.getaddrinfo()
が失敗して名前が引けず…
まとめ
ld-linux.so
はコマンドとしても実行できて、--library-path
でdynamic loadするshared objectのパスを指定できる(環境変数LD_LIBRARY_PATH
に比べ作用範囲を限定的にできる)- ELFには、dynamic linker (interpreter) とライブラリのサーチパス(runpath)を埋め込むことができ、
patchelf
コマンドで変更できる
ところで、
debootstrap
で bionic の環境を作ってまるっとCentOS 6にコピーして chroot
した方がよかったかもしれん… 試してないけど動くんじゃないかと…
kernelが古くて残念、ダメでした!
# chroot ./bionic /bin/bash FATAL: kernel too old # file bionic/bin/bash bionic/bin/bash: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 3.2.0, stripped