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アプリへの道のりは続くのであった。

さくらのレンタルサーバーにPython3とNumpyをインストールする

「さくらのレンタルサーバー」にPython3とNumpyを、やや素人の私がroot権限なしのレンタルサーバーにPython3とNumpyをインストールし、ホームページにナッシュ均衡を計算するwebアプリを作るまでの記録。「ctyesがない問題」と「Numpyのダウングレード」が鍵

ナッシュ均衡を計算するwebアプリを作るための記録。今回は、20年近く使ってきた(このサイト)「さくらのレンタルサーバー」に、<ややIT素人>の私がPythonとNumpyをインストールする話。

コンピュータやネットワークの問題を解決するのに、ネットでいろいろ検索して解決する人が多いと思うが(自分はそう)、重要なことは「問題を解決するためのやり方は、日々、変わっていて、その記事がいつ書かれたのかに注意し、古い記事ではなく新しい記事を参照すること」だ。古い記事を参考にして、ハマってしまうことも多い。今回もそれを痛感した。だいたいの場合、世の中は便利になっていて、時間が経つに連れ「インストールのやり方は簡単になっている」ことが多い。本記事は2021年12月26日のものだ。読んでいる人は、他に新しい記事がないかを、まず検討して欲しい。

さくらインターネットにもPython3の導入予定はないか問い合わせてみたところ「Python3の需要は近年高まっている事は弊社にても感じており、今回もお客様の声を頂いたという事を社内で共有させて頂き、今後のサーバアップデートの検討項目とさせて頂きたく存じます」との回答。もう皆さんがこの記事を読むころにはPython3は、さくらにはインストール済も知れない。

Python3をインストールする

さくらのレンタルサーバーは、ユーザーはroot権限がないので、PythonやNumpyの導入に関する多くのネット記事は参考にならない。sudoなどのコマンドは使えない。いろいろなネット情報を試したが、現時点では、以下の記事が一番良かった。

さくらのレンタルサーバに Python3 をインストールする方法

結果的には、この通りにやったらできた。

  1. ssh でさくらレンタルサーバにログイン
  2. ダウンロード用のフォルダを作る(自分は~/work/python3の中にPython3.9.2をダウンロードしてインストールした。以下、記事はそれに基づいているが、フォルダ、Pythonのバージョンは、各自で考える)
  3. wget でファイルをダウンロード。このとき上の記事ではPython-3.9.0をダウンロードしている。自分はPython3.8.12とした。後述するが、このPythonのバージョンをどうするかも鍵になる。後述するが、Python-3.9.0や3.9.2だと、うまく行かないようだ。
  4. tarコマンドでファイルを解凍
  5. Pythonをインストール。インストールの手順は以下の通り。
    1. まずダウンロードしたディレクトリに移動
    2. ./configure を実行するが、その際 prefix を使ってインストールするディレクトリを指定するとともに
      • 自分は $HOME/local/pythonとした
    3. make を実行
    4. make install を実行
  6. パスを通す
    • .cshrc ファイルの最後にset path = ($path $HOME/local/python/bin)を追記(記事ではviエディタを使ってますが、私はwindowsで作成しFTPでアップしました)
    • source ~/.cshrcとrehashで設定を反映
  7. whichコマンドでパスを確認、python3と打ってpython3が動くか、pip3と打ってpip3が動くかどうかを確認して完了

コマンドをまとめると以下のようになる。

%mkdir -p ~/work/python3
%cd ~/work/python3
%wget --no-check-certificate https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tgz
%tar zxf Python-3.8.12.tgz
%cd ./Python-3.8.12
%./configure --prefix=$HOME/local/python/
% make
% make install

以上だが、間違っているかもしれないので、ぜひ元の記事のさくらのレンタルサーバに Python3 をインストールする方法 を読み、確認しながら進めて欲しい。非常にわかりやすい記事だ。

Numpyをインストールする

次にNumpyのインストールだが、これはとても大変だった。さくらのレンタルサーバーにPython3を導入する記事も少ないが、Numpyを導入して成功したという記事は、さらにぐっと少なくなる。私が見たのは、以下の2つ。

  1. さくらレンタルサーバーにgcc、Python3、numpy、scipy、scikit-learn、gensimをインストールする
  2. さくらレンタルサーバーでのpython環境のセットアップ

しかし、結果的には、この記事ではうまく行かなかった。

まず最初に

%pip3 list

で、インストールされている外部モジュールを確認しておくと、

となる。最初はpipとsetuptoolsしかインストールされていないことが分かる。そして「pipのバージョンが古いから」と警告が出るので

%pip3 install --upgrade pip

として、pipのバージョンをアップグレードしておく。さて、ここで

%pip3 install numpy

とすれば、うまくインストールできるはずだが、真っ赤なエラー文がいっぱいいっぱいいっぱい出てきて失敗する。この赤い警告文にビビり、最初はいろんなことを試し、ハマってしまった。

落ち着いて、たくさんのエラーの最後の方を見ると

とある。「_ctypesというモジュールがない!」と言っている。これが「ctypesモジュールがない問題」である。

ctypesモジュールがない問題

これに対しては

【Python3】_ctypes と libffi のインストールに苦しんだ記録

というありがたい記事を発見した。詳しくは、この記事を読んでほしいが、まず最初のSTEPとしては、libffiというライブラリをインストールすることだ。このために参考にするのは、上の記事から引用されている

さくらレンタルサーバーに libffi をインストールする

である。なんと先ほど参考にした「 さくらのレンタルサーバに Python3 をインストールする方法 」のnotemite.comさんの記事なんですね!ありがたや、ありがたや。

ctpyesの前にlibffiをインストールする

上記の記事を参照してほしいのだが、(間違っているかも知れないが)内容をまとめると、以下の通りになる。まず、以下のコマンドで 、libffiというライブラリをインストールする。、

%mkdir -p ~/work/libffi
%cd ~/work/libffi
%wget ftp://sourceware.org/pub/libffi/libffi-3.2.1.tar.gz
%tar xvfz libffi-3.2.1.tar.gz
%cd libffi-3.2.1
%./configure --prefix=$HOME/local/libffi/3_2_1
% make
% make install

次に、以下のコマンドで、シンボリックリンクを貼る。

%mkdir -p ~/local/include
%ln -s $HOME/local/libffi/3_2_1/lib/libffi-3.2.1/include/ffi.h  $HOME/local/include/
%ln -s $HOME/local/libffi/3_2_1/lib/libffi-3.2.1/include/ffitarget.h $HOME/local/include/
%mkdir -p ~/local/lib
%ln -s $HOME/local/libffi/3_2_1/lib/libffi.a $HOME/local/lib/
%ln -s $HOME/local/libffi/3_2_1/lib/libffi.la $HOME/local/lib/
%ln -s $HOME/local/libffi/3_2_1/lib/libffi.so $HOME/local/lib/
%ln -s $HOME/local/libffi/3_2_1/lib/libffi.so.6 $HOME/local/lib/
%mkdir -p ~/local/lib/pkgconfig/
%ln -s $HOME/local/libffi/3_2_1/lib/pkgconfig/libffi.pc $HOME/local/lib/pkgconfig

そして環境変数の設定をする。.cshrc の末尾に下記を追記し、

setenv LD_LIBRARY_PATH $HOME/local/lib
setenv PKG_CONFIG_PATH $HOME/local/lib/pkgconfig

.cshrc で追記した内容を反映させる。

% source ~/.cshrc
% rehash

notemite.comさんの丁寧な解説で、素人の私でも理解できた。

再びctypesのインストールへ

再び「 【Python3】_ctypes と libffi のインストールに苦しんだ記録 」の記事に戻り、ctypesのインストールに移る。ここで./configure 文を以下のように修正したうえで、pythonを再インストールする。(自分は~/work/pythonの中にPython-3.8.12をダウンロードし、解凍しインストールしたので以下のようになっているが、フォルダ、Pythonのバージョンは、各自で)

%cd ~/work/python/Python-3.8.12
%./configure --prefix=$HOME/local/python/ --with-system-ffi LDFLAGS="-L $HOME/local/lib/" CPPFLAGS="-I $HOME/local/include/"
%make
%make install

とし、さらにpipのバージョンをアップグレードしておく。

ここで再チャレンジで、pipでnumpyをインストールしようと

%pip3 install numpy

とする。ここで赤字のエラーが出るが「_ctypesがない」というエラーから、以下のようなエラーに変わっているならば、あと一歩のところまで来ているので、以下は飛ばして後述の「NumpyとPython3のバージョン問題」に進めば良い。

ここでctypeエラーがまだ起きているならば

しかし、ここでまだ「_ctypesがない」というエラーが出ることもある。このエラーが出るときは、makeするときに

のようなエラーが観察されるようだ。この記事を書くために、何度か手順を変えてインストールを繰り返し、根本的な原因を考えてみた。しかし、すると、このエラーが出るときと出ないときがあったり、いつの間にか解消してしまって、再現できなくなることがある。

はっきり分かっていないが、これはPythonのバージョンの問題のようだ。記憶では、最初にPython3.9.2でやってみたところ、ここでも 「_ctypesがない」 エラーが出て問題は解消しなかった。そこでPython3.8.12でやると、このエラーは出なくなった。たぶん、上記のライブラリlibffi-3.2.1とPythonのバージョンの依存問題があるのだろう

ややこしいのは、解消後に再びバージョンを上げたりすると、ctype問題が解消していることがあるということだ。これは/$HOME/localのファイルを削除せずにpython3のインストールとアンインストールを繰り返すと、新しいバージョンのPython3が古いバージョンのライブラリを参照することがあり、それで見かけ上、解決しているのではないだろうか。そして、これは、後述の「pip3ではモジュールはインストールされているが、python3内ではインストールされてない問題」が起きる原因になる。Pythonを再インストールするときは、Pythonだけではなく、これはローカルフォルダ$HOME/localにあるライブラリも削除し、きれいにしておかないとダメだ。この点はrootではなくユーザーでやっているので、あまり心配なく削除できる。

あともう1つ考えられることは環境変数の問題である。記事によっては、環境変数を、以下に設定しろというものがある。

setenv PYTHON $HOME/local
setenv PYTHONPATH $HOME/local/lib/python3.8/site-packages
setenv C_INCLUDE_PATH $HOME/local/include
setenv CPLUSE_INCLUDE_PATH $HOME/local/include
setenv LIBRARY_PATH $HOME/local/lib

いろいろやってみると、上記の環境変数は影響してないように思える。しかし少なくとも、上記の環境変数を追加してからmakeしてみるのは損はない。追加するためには既にやったように、.cshrcの末尾にこれを付け加えて、source ~/.cshrcとrehashで追記した内容を反映させる。

NumpyとPython3のバージョン問題

さて、ctypesがない問題が解決して、あと一歩まで来ている。ここでエラーをよく見ると、

ERROR: Could not build wheels for numpy, which is required to install pyproject.toml-based projects

とある。pyproject.toml-based projectのinstallが必要と言っている。これをインストールすれば良いのだろうか。そこでこのtomlプロジェクトを調べてみると、これは最近のモジュールの新しいインストール方法らしい。昔は、pythonはsetup.pyというものでインストールしていたが、最近はpyproject.tomlというものでインストールを行うようだ。

参考:え、まだsetup.py使ってるの? pyproject.tomlとsetup.cfgで宣言的パッケージング

正しいのは、この pyproject.toml が使えるようにすることかもしれないが、結構、大変そうだ。しろうとの私が推測したのは「pip3で最新のnumpy(このときはnumpy-1.21.5)をインストールすると この pyproject.toml を使うのでうまく行かないのだから、 numpy のバージョンを下げてみれば解決するだろう」というものだ。そこで

% pip3 install numpy==1.18.4

と低いバージョンを適当に選びインストールしてみた。すると、

となった。なんと、インストールはうまく行ったのである!確認のためpip3 listと打ってみると

となって、無事numpyがインストールされていることが分かる。

結局、PythonとNumpyのバージョンに注意し、必要に応じてバージョンを下げるということが1つの手段なんだろう。 これに加えて「ctypesがない問題」においては、ライブラリlibffiのバージョンも関連している気がする。できるだけ新しいバージョンを使うことがいいのだろうが、どんな組み合わせが最新になるのかは試行錯誤だし、そんなにたくさんはできない。今回のことをまとめると

  • Python 3.8.12
  • Numpy 1.18.4
  • libffi 3.2.1

の組み合わせはうまく行ったということだ。

さて、ここで安心してはいけない。最後にpython3に入り、

>>import numpy as numpy

と入力してみる。pip3でもnumpyがインストールされていることが確認できているので、あっさりimportされるはず...。

ここまでまっすぐに来た方は、エラーが出ずにインポートされてくれるはずだ。その人は以下の「pipの認識とpythonのバージョンがずれる問題」を見る必要はなく、最後の「まとめ?まだまだ苦難は続いた」へ。

pipの認識とpythonのバージョンがずれる問題

ところが、ここまで曲がりくねって、いろいろなバージョンのpython3をインストールとアンインストールした自分には、もう一つ山が待っていた。

python3のコマンドラインに入り、import numpy as npと打つと「そんなmoduleはない」と叱られてしまったのだ。

これはPCに複数のpythonのバージョンがインストールされた際に、pipとpythonのバージョンがずれてしまう問題である。これについては以下の記事を参照にして欲しい。

Pythonのpipコマンドでうまくパッケージがインストールできない場合がある理由と対処法

自分の場合は、確認するために

%pip3 install numpy

と入力すると、

Requirement already satisfied: numpy in $HOME/local/python/lib/python3.8/site-packages

のようにpipが認識するインストール先が出てきた。次にpython3に入って

>>> import sys
>>> print(sys.path)

として、モジュールの入力先を見てみる。すると、

 '$HOME/local/python/lib/python39.zip',$HOME/local/python/lib/python3.9','$HOME/local/python/lib/python3.9/lib-dynload',
$HOME/local/python/lib/python3.9/site-packages'

のようになった($HOMEは実際はパス名)。pythonのバージョンを調べてみると

% python3 --version
Python 3.9.2

tとなる。つまり、python3のバージョンは3.9.2であり、python3も、モジュールのインストール先を $HOME/local/python/lib/python3.9 と認識しているのに対し、pipはインストール先をpython3.8にしていたのである。

曲がりくねって、いろいろなバージョンのpython3をインストールとアンインストールすると、この問題が起きる。これを解決するには、local以下のファイルをすべて削除して、pythonをきれいに再インストールすることであった。

以上で、python3とnumpyのインストールは完了したと思えたが…。

まとめ?まだまだ苦難は続いた(2021年12月29日追記)

長い道のりだったが、さくらのレンタルサーバーへのpython3とnumpyのインストールは成功した。python3のコマンドラインに入り、

>>> import numpy as np

と打つと今度はエラーは出ず、print(np.zeros(6))と打ってみると

>>> print(np.zeros(6))
[0. 0. 0. 0. 0. 0.]

とちゃんと動いている(なんちゅうテストだ)。そしてシェルに戻り、numpyで書かれたプログラムのファイルnumpy_test1.pyを実行しても

% python3 numpy_test1.py
[array([2, 5, 8]), array([3, 6, 9])]
方法1
[[2, 8], [3, 9]]
方法2
[[2, 8], [3, 9]]

とちゃんと動く(どんなテストプログラムかは内緒...)。

やったやった!これで万事、うまく行った!次のステップに入ろう!と喜んだのだが...。実はあの「_ctpesがない問題」は、ゾンビのように生き残っていたのである。

詳しくは

numpyを使ったcgiを、さくらのレンタルサーバーで動かす

に記したが、結論を言うとcgiプログラムにnumpyをインポートして使うためには、numpyのバージョンを(1.18.4からさらに)1.14.6まで下げなければならなかったのである。