numpyをimportしたcgiをさくらのレンタルサーバーで動かす。

前の記事「さくらのレンタルサーバーにPython3とNumpyをインストールする」で、見事にPython3とNumpyのインストールに成功したと思った自分だったが、pythonで書かれたcgiプログラムを動かすと、再びnumpyがimportできず「ctypeがない問題」が発生した。試行錯誤の結果、numpyのバージョンを更に下げて、動かすことに成功した。

ITやや素人の私が、ナッシュ均衡を計算するwebアプリを作るための記録。前回は「さくらのレンタルサーバーにPython3とNumpyをインストールする」で、苦労の末、遂にPython3とNumpyのインストールに成功した…と思ったのだが…。

ターミナルでは問題なく動く

webアプリを作るためにいろいろ試行錯誤。これについては、また改めて記事にしようと思う。とりあえず、現時点の目標「htmlからフォームでデータを送り、python3で(numpyをインポートして書かれた)cgiプログラムを動かして出力する」こと。

まず自分のPCのwindows上でやってみて、うまく行った。そこで次に、さくらのレンタルサーバー上で、動かすことに挑戦。ところがうまく動かない。Internal Server Errorになるのである。

そこでまず、以下の簡単なプログラムをファイル名 investigation2.cgi として作った。

#!$HOME/local/python/bin/python3

import cgi
import cgitb
import numpy as np

cgitb.enable() 
print("Content-type: text/html <br>")
print()
print("Hello World!<br>")
print(np.zeros(5))

($HOMEは実際には正しいパス名が入る)。windowsではうまく動いたし、シェルから動かすと、以下のようにちゃんと動く

% python3 investigation2.cgi
Content-type: text/html <br>

Hello World!<br>
[0. 0. 0. 0. 0.]

そこでブラウザから http://正しいURL/investigation2.cgiとしても、うまく行かない。パーミションはきちんとしているし、だいいちここから、import numpy as npと print(np.zeros(6)) を削除し http://正しいURL/investigation2.cgiとすると

とちゃんと出てくるじゃないか。どう見ても import numpy as np が悪さをしている。しかしcgiでエラーが出るとエラーをブラウザに吐いてくれるcgitbというモジュールも、エラーを吐いてくれない。これはどうしたことだろうか?

いろいろネットでべると「import numpy を直接ではなく、関数に入れるとできる」のようなっ記事を目にしたので(ごめんなさい、どの記事か忘れてしまいました)、以下のように変更してみた。essential_mainという関数を定義し、そこでnumpyをインポートする、というものだ。

#!/home/nabepremium/local/python/bin/python3

import cgi
import cgitb

def essential_main():
    import numpy as np
    print("in the main function")
    print(np.zeros(5))

cgitb.enable() 
print("Content-type: text/html <br>")
print()
print("Hello World!<br>")
essential_main()

うーん、これでもダメだった。しかし、これだと Internal Server Errorにならず、アクセスはできていた、しかも幸いなことに、cgitbがエラーをブラウザに吐き出してくれたのである。エラーの内容は多いので、ここには記さないが、エラーを読むと

numpyのモジュールは探し出せているが、numpyをインポートするときにnumpy自身がいろんなライブラリを呼び出していて、その際に

import _ctypes_ctypes undefined

とか

ImportError: Shared object “libffi.so.6” not found, required by “_ctypes.cpython-38.so”

とかいうエラーが出ているようだ。…ん?ctypeがないって…

まだゾンビのように生き残っていたのか!ctypes問題!(詳しくは 「さくらのレンタルサーバーにPython3とNumpyをインストールする」 を参照せよ)

環境変数やsys.path.insertなど試してみるもダメ

Shared object “libffi.so.6” not found, required by “_ctypes.cpython-38.so とあるが、この libffi.so.6 というファイルは、前回のlibffiをインストールしたときに入っている。なので、これを見つけることができないということは、環境変数かパスを通してやれば、いいんじゃないか?と考えたのだが、ここからはpythonやnumpyに通じてない、やや素人の私には無理な話。とりあえずpathとか、いろいろな環境変数にlibffi.so.6があるパスを入れたりしたのだがダメである。

自分がターミナルから動かしたときには動くのに、cgiからは動かない、というのはlibffi.so.6のある位置が、外部のnobodyだったかotherがcgiを動かすときに、これが見えなくなっていることだから、 プログラムの内部にこれを教えてやればいいんじゃないか?と推測する。でもも、ここからはpythonやnumpyに通じてない、やや素人の私にはやっぱり無理な話。

また調べてみると、プログラムがモジュールを検索するときはsys.pathというものを使うということで、プログラム内でsys.path.insertというコマンドによって、モジュールを検索する場所を教えることができるようだ。そこでこのコマンドでいろいろパスを追加するもやっぱりダメ。

そもそもnumpyというモジュールの位置は探し出していて、それをインポートするときに、numpyが呼び出すライブラリのようなものが見つからないって言っているので、上記のやり方は違う気がする。

エラーは「_ctypes.cpython-38.soによって要求されるlibffi.so.6がない」と言っている。だからpythonに教えると言うよりは、この_ctypes.cpython-38.soにlibffi.so.6の位置を教えてやらないとダメだということだろう。しかし、どうやって教えればいいのかが分からない。_ctypes.cpython-38.soは$HOME/local/python/lib/python3.8/lib-dynloadにあるので、ここに libffi.so.6 をコピーしてみたのだが、やはり動かないようだ。やや素人の私には限界である。

Numpyのバージョンを下げる

_ctypes.cpython-38.soやlibffi.so.6はバイナリファイルだ。いろんな記事を読み、前回の記事で見たpyproject.toml-based projectの話も考慮して、素人の私の勝手な推測は、以下のようなことだ。

  • pythonのモジュールやNumpyは、初期の頃より複雑で巨大化している。だからモジュールで使われるライブラリなどは、最初はソースファイルで提供され、モジュールのインストール時にコンパイルされていたが、だんだんとバイナリで出来上がったものが提供されたり、利用されるようになってきたんじゃないだろうか。
  • ところがその「出来合いのバイナリファイル」が問題を起こしている。それは、ライブラリがモジュールのインストール時にコンパイルされたときには、持っている情報を使えるんだが、出来合いだとそれが使えないから、後で教えなければならない。
  • でも教え方は分からない。
  • じゃあ、モジュールのバージョンを下げて、ライブラリをモジュールのインストール時にコンパイルする時代まで戻れば、なんとかなるんじゃないか?だいたい、最初もNumpyのバージョンを下げればうまくいったじゃないか。

Numpyの最新のバージョンは1.21.5だが、先のインストール時に1.18.4に下げている(「さくらのレンタルサーバーにPython3とNumpyをインストールする」 を参照)。どこまで下げればいいんだろう?じゃあ、思いっきり下げてバージョン1.8.1くらいにしてみよう!

ということで、一応、Pythonを再インストールしてきれいにし、

%pip3 install numpy==1.8.1

としてみた。すると
Using legacy ‘setup.py install’ for numpy, since package ‘wheel’ is not installed.
として、かなり時間がかかっている。うーん、よくわからないが「(現在はwheelという完成品のパッケージを使うんだけど)、それがないから古いやりかた(古い遺産:legacy)である setup.py によってインストールしている」と言ってるのだろう、とやや素人の私は解釈。 かなり時間がかかって、エラーが出てないのはいい兆候だ、と思い、しばらく待つも、最後には大量のエラーが吐き出され失敗。

古すぎたのだろうか。それじゃあということで、適当にバージョン 1.14.6で挑戦してみた。すると、さっきと同じで
Using legacy ‘setup.py install’ for numpy, since package ‘wheel’ is not installed.
して、かなり時間がかかっている。

「こんな当てずっぽうなやり方じゃダメだな、これで同じエラーが出たら推測もだめだってことで、もう諦めよう」と思うが、とりあえず最後と思ってずっと待つ。「もうダメだ」と思った瞬間、なんとインストールに成功したじゃないか!

びっくりした。エラーが出るかと思ったが。

次に、先の http://正しいURL/investigation2.cgi にアクセスしてみると、ちゃんと動く。

さらに、import numpyを関数内の呼び出しからメインプログラムへ移したのだが、やはりちゃんと動いている。かくしてCGIでも正しく動作したのであった。

推測は正しくないかも知れないがまとめると、Python3.8.12とNumpy 1.14.6 の組み合わせで、cgiプログラムも正しく動作したのであった。

やっとここまで来た。しかし、まだwebアプリへの道のりは続くのであった。