はじめに
ある日GettingStartedで用意されていた手順とDockerfileでpythonの開発環境のコンテナを立て、開発を始めようと思ったときに、出鼻をくじかれたので何が起こっていたかメモとして残しました。
概要
- root userでpip install したパッケージは別ユーザからも使用できるが、root user以外でインストールしたものはそのユーザからしか見えない。
- 原因はpip install時のパスでrootの場合
/usr/local
配下インストールされるが、他ユーザは~/.local
配下になるため。- root でpythonを実行するときに読まれるパスは
/usr/local
配下のもののみで、他ユーザの場合は/usr/local
に加え~/.local
配下が読まれる
- root でpythonを実行するときに読まれるパスは
- 解決方法は、何とかしてパスが通った状態にする。
何をやっていたか
とあるOSSのGettingStartedでDockerを使ったpythonの開発環境を使用していました。
原因部分を抜粋してすごく簡略化して書いていますが、大体以下のような構成です。
- ファイル構成
$ tree
.
├── Dockerfile
└── test.py
- Dockerfile
FROM python:3.11-slim
COPY test.py /
RUN groupadd --system --gid 999 cacaomath \
&& useradd --no-log-init --system -u 999 --gid cacaomath --create-home cacaomath
RUN pip install pytest
USER cacaomath
RUN pip install web.py
# ...そのほかのいろいろな処理...
- test.py
import web
# 実働確認用
print("web module exists")
# 実際にはこのあとにも処理がたくさん続いています。...
このような環境において、
sudo docker run -it -uroot test python test.py
を実行するような手順が用意されていたのですが、実行してもModuleNotFoundError: No module named 'web'
のエラーになるばかりでどうしても実行することができませんでした。
```shell-session
$ sudo docker run -it -uroot test python test.py
Traceback (most recent call last):
File "//test.py", line 1, in <module>
import web
ModuleNotFoundError: No module named 'web'
かなしい..
原因
ModuleNotFoundError: No module named 'web'
のエラーの根本的な原因としては、
PATHが通っていないといった単純なことでした。
まず、今回の環境はDockerの環境内でrootと別のユーザで行う処理のすみわけがされており、
rootとそれ以外のユーザで使うパッケージを意図的に分けてインストールがされる構成になっていました。
RUN pip install pytest # rootおよびそれ以外のユーザの処理で使うパッケージ
USER cacaomath
RUN pip install web.py # root以外のユーザが使うパッケージ
これ自体はいいと思うのですが、問題はそれぞれのユーザでインストールされるパッケージのPATHでした。
通常pip innstallを行ったときそれぞれインストール場所は以下です。
user | PATH |
---|---|
root | /usr/local |
root以外のuser | ~/.local |
これを踏まえたうえで、python実行時に読み込まれるPATHを見てみると以下の様になっていました。
- rootの場合
$ sudo docker run -it -uroot test bash
root@6156950b997e:/# python
Python 3.11.4 (main, Jun 14 2023, 18:25:14) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/local/lib/python311.zip', '/usr/local/lib/python3.11', '/usr/local/lib/python3.11/lib-dynload', '/usr/local/lib/python3.11/site-packages']
- root以外の場合
$ sudo docker run -it -ucacaomath test bash
[sudo] password for cacaomath:
cacaomath@b7853c2835f9:/$ python
Python 3.11.4 (main, Jun 14 2023, 18:25:14) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/local/lib/python311.zip', '/usr/local/lib/python3.11', '/usr/local/lib/python3.11/lib-dynload', '/home/cacaomath/.local/lib/python3.11/site-packages', '/usr/local/lib/python3.11/site-packages']
root以外のユーザからは/usr/local
配下と~/.local
配下が参照されるのに対して、
rootからは/usr/local
からしか参照されていません。
以上のことから、rootで実行しているsudo docker run -it -uroot test python test.py
は、
実行時にmoduleのweb
がインストールされている~/.local
(ここでは/home/cacaomath/.local
)配下のPATHが読み込めず、ModuleNotFoundError: No module named 'web'
が発生する事態が起きていたようでした。
解決方法
今回の例では、rootユーザを使わずに、
sudo docker run -it -ucacaomath test python test.py
をすれば動くには動くのですが、
実際の環境ではrootを使わないと別のところで不都合が起きるため、PYTHONPATH
で実行にPATHを渡すことで解決方法としました。
-
docker run
の -eオプションでPYTHONPATH
を渡す
$ sudo docker run -it -uroot -e PYTHONPATH=/home/cacaomath/.local/lib/python3.11/site-packages test bash
root@f2f75615fb10:/# python
Python 3.11.4 (main, Jun 14 2023, 18:25:14) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/home/cacaomath/.local/lib/python3.11/site-packages', '/usr/local/lib/python311.zip', '/usr/local/lib/python3.11', '/usr/local/lib/python3.11/lib-dynload', '/usr/local/lib/python3.11/site-packages']
実際にこうすることでmoduleの読み込みがされ、後続の処理もできていることがわかります。
$ sudo docker run -it -uroot -e PYTHONPATH=/home/cacaomath/.local/lib/python3.11/site-packages test python test.py
web module exists
一応、rootを使わない方法
$ sudo docker run -it -ucacaomath test python test.py
web module exists
おわりに
今回は用意されていた手順およびファイルにおいて、PATHが通らない状態で出鼻をくじかれましたが、改めて勉強になったと思います。
それでも、やっぱりPATHは提供されている時点で通っている(or 手順になっている)ほうがいいので、自分で実装する際には考慮できるようにして行きたいと思いました。
参考