夜通し環境構築に取り組むも、モジュールエラー(ImportError)が解消されなくて廃人になりかけた。エラーの原因はパスが違うという洗礼であったり、使いたいライブラリが使う別のライブラリが存在しなかったり、使っているライブラリが古かったりしたわけだけど、悩んでいるときはそもそもそういう問題が本当に起きているのかどうかすら曖昧なまま試行錯誤していた。このような苦痛をもう二度と味わわないためには結局どうすれば良いのか? 環境構築の学びをここに記録する。
- 実行スクリプトはどこのパスにあるか?
- 実行スクリプトが参照しようとしているライブラリのパスはどこか?
- そのライブラリに必要なライブラリはあるのか?
- それらのライブラリのバージョンは正しいか?
なお今回のケースとしては、GCPでJenkinsサーバーを作り、BigQueryを操作するpythonスクリプトをGithubから取得して定期実行することを想定する。
1. 実行スクリプトはどこのパスにあるか?
エラーが起きているスクリプトがどこで実行されているのかを知ることで、ファイルそのものがその環境で動くかどうかを直接的に確かめることができる。
例えば、ローカルで直接スクリプトを動かすのではなく、GCP(GCE)などのサーバーにインストールしたJenkinsを使うことで、Githubに置いたソースコードを自動的に取得&実行する際にモジュールエラーが起きたとする。
Started by timer
Running as SYSTEM
Building in workspace /opt/bitnami/jenkins/jenkins_home/workspace/test_py
...
[test_py] $ /bin/bash /tmp/jenkins7138111450797694063.sh
Traceback (most recent call last):
File "my_test_script.py", line 1, in <module>
from google.cloud import bigquery
ModuleNotFoundError: No module named 'google.cloud'
Build step 'Execute shell' marked build as failure
Finished: FAILURE
Jenkinsは取得してきたスクリプトを置くディレクトリを自動的に作ってくれる。ログを見るとその場所が特定できるので、これを直接動かして検証ができる。
user:~$ cd /opt/bitnami/jenkins/jenkins_home/workspace/test_py
# Jenkinsがジョブ名に応じて自動的に作成したディレクトリへ移動
user:/opt/bitnami/jenkins/jenkins_home/workspace/test_py$ python my_test_script.py
# Githubから取得したスクリプトの実行
2. 実行スクリプトが参照しようとしているライブラリのパスはどこか?
実際のライブラリを置いているパスと、実行スクリプトがライブラリを参照するために読み込むパスが一致していなければ、ライブラリを読み込むことができずにエラーになる。
例えば、「パスが通っていない」=環境設定変数のPATHに実際のライブラリが置いてあるパスが設定されていない(ライブラリがインストールされたパスを、スクリプトで読み込めるようになっていない)場合が多い。他には、仮想環境を立ててコマンドを実行している場合にも発生する。具体的には、venvなどの仮想環境を立ててからスクリプトを実行する場合、使用するライブラリはその仮想環境下からのみ探される。つまり、仮想環境を立てずにインストールしたライブラリは、仮想環境下で実行するスクリプトでは読み込むことはできないし、逆に仮想環境を立ててインストールしたライブラリは、仮想環境を立てずに実行するスクリプトでは使うことができず、モジュールエラーになる。
$ cd my_directory
$ pip install --upgrade google-cloud-bigquery
# 仮想環境外にライブラリをインストール
$ python3 -m venv env
$ source env/bin/activate
(env) $ python my_test_script.py
# 仮想環境を有効化して実行
Traceback (most recent call last):
File "test.py", line 1, in <module>
from google.cloud import bigquery
ImportError: No module named google.cloud
# モジュールエラーが発生
※ちなみに、仮に仮想環境を立てていても、sudo pip install <ライブラリ名>
で実行してしまうと、ホームディレクトリが仮想環境下ではなく/rootになるため、仮想環境下で実行するスクリプトはモジュールエラーになる。
したがって、
- ライブラリがどのパスにインストールされているのか?
- そのパスはスクリプトで読み込むことができるようになっているのか?
を確認する必要がある。
$ pip install --upgrade google-cloud-bigquery
Requirement already satisfied: google-cloud-bigquery in /usr/local/lib/python3.7/dist-packages (4.56.0)
# 既にインストールされていると言われているが、
# そもそも「/usr/local/lib/python3.7/dist-packages」は
# スクリプトの実行環境(仮想環境)のパスではないのでNG。
$ source env/bin/activate
(env)$ pip install --upgrade google-cloud-bigquery
Requirement already satisfied: proto-plus>=1.10.0 in ./env/lib/python3.7/site-packages (from google-cloud-
bigquery) (1.13.0)
# 既にスクリプトの実行環境(仮想環境下)にインストールされているのでOK。
(env) account:~$ pip list
Package Version
------------------------ ---------
...
google-api-core 1.25.0
google-auth 1.24.0
google-cloud-bigquery 2.6.2
google-cloud-core 1.5.0
google-crc32c 1.1.1
google-resumable-media 1.2.0
googleapis-common-protos 1.52.0
...
# 既に「google-cloud-bigquery」がインストールされているのでOK
(env) account:~$ pip list
$ pip freeze
...
google-api-core==1.25.0
google-auth==1.24.0
google-cloud-bigquery==2.6.2
google-cloud-core==1.5.0
google-crc32c==1.1.1
google-resumable-media==1.2.0
googleapis-common-protos==1.52.0
...
# 既に(ry
$ pip --version
pip 20.3.3 from /usr/local/lib/python3.7/dist-packages/pip (python 3.7)
$ python
>>> import sys
>>> print(sys.path)
['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-x86_64-linux-gnu', '/usr/lib/python2.7/lib-tk', '/u
sr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages']
>>> exit()
# 実際のライブラリの場所と、pythonファイルがライブラリを読み込むときのパスが違う
# そして想定しているメジャーバージョンすら違う
# 以降、仮想環境を立てることで回避
(env) account:~$ python
>>> import sys
>>> print (sys.path)
['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/home/info/env/lib/python3.7/site-packages']
>>> exit()
(env) account:~$ cd /home/info/env/lib/python3.7/site-packages
(env) account:~/env/lib/python3.7/site-packages$ ls
cachetools google_cloud_bigquery-2.6.2-py3.9-nspkg.pth pyasn1-0.4.8.dist-info
cachetools-4.2.0.dist-info google_cloud_core-1.5.0.dist-info pyasn1_modules
certifi google_cloud_core-1.5.0-py3.9-nspkg.pth pyasn1_modules-0.2.8.dist-info
certifi-2020.12.5.dist-info google_crc32c __pycache__
cffi google_crc32c-1.1.1.dist-info pycparser
cffi-1.14.4.dist-info google_crc32c.libs pycparser-2.20.dist-info
_cffi_backend.cpython-37m-x86_64-linux-gnu.so google_resumable_media-1.2.0.dist-info pytz
cffi.libs google_resumable_media-1.2.0-py3.9-nspkg.pth pytz-2020.5.dist-info
chardet grpc requests
chardet-4.0.0.dist-info grpcio-1.34.1.dist-info requests-2.25.1.dist-info
_distutils_hack idna rsa
distutils-precedence.pth idna-2.10.dist-info rsa-4.7.dist-info
easy_install.py pip setuptools
google pip-18.1.dist-info setuptools-51.3.3.dist-info
google_api_core-1.25.0.dist-info pkg_resources six-1.15.0.dist-info
google_api_core-1.25.0-py3.9-nspkg.pth pkg_resources-0.0.0.dist-info six.py
googleapis_common_protos-1.52.0.dist-info proto urllib3
googleapis_common_protos-1.52.0-py3.8-nspkg.pth protobuf-3.14.0.dist-info urllib3-1.26.2.dist-info
google_auth-1.24.0.dist-info protobuf-3.14.0-py3.7-nspkg.pth wheel
google_auth-1.24.0-py3.9-nspkg.pth proto_plus-1.13.0.egg-info wheel-0.36.2.dist-info
google_cloud_bigquery-2.6.2.dist-info pyasn1
# 読み込み先のパスとライブラリの存在する場所が一致している
3. そのライブラリに必要なライブラリはあるのか?バージョンは正しいか?
pythonファイルが使うライブラリAが、さらに別のライブラリB,Cを使おうとしている場合、BやCが存在しなかったり、バージョンが古かったりすると、ライブラリAがうまくインストールできないことがある。そこで、コマンドpip install --upgrade <ライブラリ>
を使って関連するライブラリのバージョンを一通り更新すると、うまくインストールできるようになることがある。
(env) $ pip install --upgrade google-cloud-bigquery
...
----------------------------------------
Failed building wheel for proto-plus
Running setup.py clean for proto-plus
Running setup.py bdist_wheel for grpcio ... error
Complete output from command /home/info/env/bin/python3 -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-kmhkwvwd/grpcio/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d /tmp/pip-wheel-26um0boj --python-tag cp37:
Found cython-generated files...
usage: -c [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
or: -c --help [cmd1 cmd2 ...]
or: -c --help-commands
or: -c cmd --help
error: invalid command 'bdist_wheel'
----------------------------------------
Failed building wheel for grpcio
Running setup.py clean for grpcio
Failed to build proto-plus grpcio
Installing collected packages: six, pyasn1, rsa, cachetools, pyasn1-modules, google-auth, protobuf, googleapis-common-protos, pytz, urllib3, chardet, idna, certifi, requests, grpcio, google-api-core, google-cloud-core, proto-plus, pycparser, cffi, google-crc32c, google-resumable-media, google-cloud-bigquery
Running setup.py install for grpcio ... -
# インストールに失敗。「wheel」というものに関連するものが必要に見える。
(env) $ pip install --upgrade wheel
Collecting wheel
Downloading https://files.pythonhosted.org/packages/65/63/39d04c74222770ed1589c0eaba06c05891801219272
420b40311cd60c880/wheel-0.36.2-py2.py3-none-any.whl
Installing collected packages: wheel
Successfully installed wheel-0.36.2
# 新しくインストールすることができた
(env) $ pip install --upgrade grpcio
Collecting grpcio
Using cached https://files.pythonhosted.org/packages/81/5e/168a7fa23a025beed6b7daa0981ace55e394a136db
3082faed7d6cba4556/grpcio-1.34.1.tar.gz
Requirement already satisfied, skipping upgrade: six>=1.5.2 in ./env/lib/python3.7/site-packages (from
grpcio) (1.15.0)
Building wheels for collected packages: grpcio
Running setup.py bdist_wheel for grpcio ... -
done
Stored in directory: /home/info/.cache/pip/wheels/e2/60/7c/617a7c5af21a5d60c41e66ddb55f31235ec7d3f3d2
2d943f51
Successfully built grpcio
Installing collected packages: grpcio
Successfully installed grpcio-1.34.1
...
# 新しくインストールすることができた
(env) $ pip install --upgrade google-cloud-bigquery
Successfully installed cffi-1.14.4 google-api-core-1.25.0 google-cloud-bigquery-2.6.2 google-cloud-core
-1.5.0 google-crc32c-1.1.1 google-resumable-media-1.2.0 proto-plus-1.13.0 pycparser-2.20
# 目的のライブラリをインストールすることができた
おわり。今後は出力されるログを1つ1つしっかり見ながら、上に挙げたポイントをしらみつぶししていこうと思う。
アドバイスなどがあれば本記事かTwitterアカウントでお知らせくださいm(__)m