yNagaokaのブログ

このブログはNGS解析初心者がつまずいた部分と解決方法をまとめた覚書のようなものです。

同じSingularity Imageを使用しているのに環境によってpythonパッケージのバージョンが違ったときの覚書

はじめに

とある解析ができるようにDockerfileからDocker imageを作成して、それを基にSingularity imageを作ることにしました。その解析では特定のバージョンのPythonパッケージを使用する必要があり、Dockerfile内でそのバージョンを指定してインストールしました。しかし、Singularity imageを使う環境によってなぜかPythonパッケージのバージョンが違う!という事態に遭遇しました。今回はその時に調べた事の覚書です。

具体的な状況

今回はPython3.7、numpy1.19.0、pandas0.25.3が使いたいような状況でした。なのでDockerfileには以下の様に記載してimageをbuildしました。
(※Minicondaとpipをこの前でインストールしています)

ENV PATH="/opt/conda/bin:${PATH}"
RUN /opt/conda/bin/conda install -y python=3.7
RUN pip install --only-binary :all: numpy==1.19.0
RUN conda install pandas==0.25.3

numpy==1.19.0とpandas==0.25.3を同じ行でインストールしようとするとversion conflictでエラーになってしまうのでこの形に落ち着きました。

Dockerコンテナ内でバージョンを確認

buildが完了したのでインストールが上手くいっているか確認しました。

$ docker run --rm -ti <username/repository:tag>
# python
Python 3.7.10 (default, Jun  4 2021, 14:48:32)
[GCC 7.5.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(np.__version__)
1.19.0
>>> import pandas as pd
>>> print(pd.__version__)
0.25.3

上手くいっていることが確認できたのでこのimageをpushして、Singularity imageにすることにしました。コマンドの例は以下です。

$ docker push <username/repository:tag>
$ singularity build <XXX.sif> docker://<username/repository:tag>

XXX.sifという名前のSingularity imageが出来上がりました。
ここまでの操作はサーバーAという環境で行いました。

Singularity内でバージョンを確認

問題はここからです。
サーバーBという環境で解析したいため、念のためサーバーBでバージョンの確認を行いました。
(サーバーBではDockerが使用できない状態だと仮定してください)

$ singularity run XXX.sif
Singularity> python
Python 3.7.10 (default, Jun  4 2021, 14:48:32)
[GCC 7.5.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> print(np.__version__)
1.21.6
>>> import pandas as pd
>>> print(pd.__version__)
1.3.5

Pythonは3.7ですがnumpyとpandasのバージョンが違います。これでは解析ができない...

何故バージョンのが違うのか?

という事で原因を考えてみました。
Dockerはコンテナの内外が分断された独立環境となるという特性があります。そのため確実にコンテナ何の環境が適応されます。
一方で、Singularityはホストのリソース (ファイル・ハイドウェア) を直接利用できる。ホストの$HOME配下をデフォルトでコンテナにマウントするという特性があります。
このことから、Singularity image内では別の場所のnumpyとpandasを参照してしまっているのではないか?と考えました。早速、以下のようにどこのnumpyとpandasを参照しているのか確認してみます。

$ singularity run <XXX.sif>
Singularity> python
>>> import numpy as np
>>> print(np.__file__)
/home/nagaoka/.local/lib/python3.7/site-packages/numpy/__init__.py
>>> import pandas as pd
>>> print(np.__file__)
/home/nagaoka/.local/lib/python3.7/site-packages/numpy/__init__.py

やはり、$HOME下のファイルを参照していました。
調べたところ、$HOMEPythonパッケージがインストールされている場合(例えば、pip install --userを使用してパッケージをインストールした場合など)、それらのパッケージがコンテナ内のPythonからも利用可能になってしまうようです。
同様に、Singularityはデフォルトでホストの/tmpをコンテナ内にマウントします。Pythonの一時ファイルやキャッシュがこのディレクトリに保存されている場合、それらがコンテナ内のPythonから利用可能になる可能性があります。 これは時に便利な機能ですが、今回に限っては裏目に出てしまったようです...

対応策

対策1

対応策の一つとして、Singularity imageを立ち上げる際に--no-homeというオプションを使用することでこの問題が回避できました。
--no-home$HOME配下をマウントしないようにするオプションです。

$ singularity run --no-home <XXX.sif>
Singularity> python
>>> import numpy as np
>>> print(np.__version__)
1.19.0
>>> print(np.__file__)
/opt/conda/lib/python3.7/site-packages/numpy/__init__.py
>>> import pandas as pd
>>> print(pd.__version__)
0.25.3
>>> print(pd.__file__)
/opt/conda/lib/python3.7/site-packages/pandas/__init__.py

本来であれば、/opt/conda/lib/python3.7を参照してほしかったので、これで正しい場所を参照するようになったと言えます。 しかし、この方法はホームディレクトリ全体をマウントしないため、ホームディレクトリに保存されている他の重要なデータ(例えば、ユーザー設定や作業データなど)にもアクセスできなくなる点に注意が必要です。

対策2

現状では$HOME下を参照してしまっていることが原因であり、本来使いたいPythonパッケージは/opt/conda/lib/python3.7/site-packagesにあることがわかっています。Pythonはインポートするパッケージを見つけるためにPYTHONPATHという環境変数を使います。であれば、PYTHONPATHを指定し直して、目的の場所を探すように設定すれば良いのではないか?と考えました。以下のような感じです。

$ singularity run <XXX.sif>
Singularity> export PYTHONPATH=/opt/conda/lib/python3.7/site-packages:${PYTHONPATH}
Singularity> python
Python 3.7.10 (default, Jun  4 2021, 14:48:32) 
[GCC 7.5.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> print(np.__version__)
1.19.0
>>> print(np.__file__)
/opt/conda/lib/python3.7/site-packages/numpy/__init__.py

上記のコマンドはシェルセッションでのみ有効です。パス設定を恒久的に行いたい場合は、~/.bashrcまたは~/.bash_profileなどのシェルの設定ファイルに追記する必要があります。これなら自分でimageを作り直せない場合でも対応できそうです。

対策3

対策2の方法はSingularity imageを起動する度に行う必要があるので、少々面倒です。(~/.bashrcなどに記載したくない場合) 自分で作成しているimageであればDockerfileを改変することができるので、以下を追記してDocker imageとSingularity imegeを作り直してみました。

ENV PYTHONPATH="/opt/conda/lib/python3.7/site-packages:${PYTHONPATH}"

新しく作成したいSIFを用いて、サーバーBにてバージョンの確認を行いました。

$ singularity run <XXX_ver2.sif>
Singularity> python
Python 3.7.10 (default, Jun  4 2021, 14:48:32)
[GCC 7.5.0] :: Anaconda, Inc. on linux
>>> import numpy as np
>>> print(np.__version__)
1.19.0
>>> print(np.__file__)
/opt/conda/lib/python3.7/site-packages/numpy/__init__.py
>>> import pandas as pd
>>> print(pd.__version__)
0.25.3
>>> print(pd.__file__)
/opt/conda/lib/python3.7/site-packages/pandas/__init__.py

上記の様に、正しいバージョンを認識していました。$HOME下をマウントしていても問題がないので、自分で作り直せる場合はこの方法が良さそうです。