ReadOnlyなDocker環境のPytestを実行するとNo such file or directoryが発生した
pytestの起動時に失敗するので、原因を把握するのに時間がかかってしまいました。
環境
- Docker
- Docker version 20.10.13, build a224086
- Docker Compose
- docker-compose version 1.29.2, build 5becea4c
- Windows
- Macだと発生しない事象のようです
前提
Docker Composeのボリュームにread_only属性が設定されている。
原因
pytestが/tmpディレクトリに何かしらの書き込みをしようとしています。
read_only属性がついている場合、書き込み先ディレクトリをvolumesに設定する必要があります。
volumesに/tmpが設定されていないため、書き込みができずにエラーとなっているようです。
対策
volumesに/tmpを追加します。
services: api: read_only: true # 前提 volumes: - /tmp # 追加
ソースコード
終わりに
read_only属性はホスト側に余計な情報を書き込ませないようにするためのもので、コンテナ内のディレクトリについて制限するものではないと思っていました。もし、コンテナ内のディレクトリを制限するものだとしても、/tmpディレクトリに関しては常に全権限与えられているものだという思い込みがあったので、調査に時間がかかってしまいました。
しかもなぜかpytestではエラーとなるが、pytest -sでは正常に動くという謎挙動をしておりました。-sは詳細ログを出すようにするためのオプションだと思っていたのですが、標準出力先が違うのかもしれませんね(未検証)
事象自体はpytestに限らないと思うので、ぜひ同事象に引っかかったら教えていただけると、記事に載せていただこうと考えています。
この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。
参考情報
発生ログ
$ docker-compose run --entrypoint "poetry run pytest --asyncio-mode=strict" api
Creating fastapi-practice_api_run ... done
============================================================ test session starts ============================================================
platform linux -- Python 3.9.12, pytest-7.1.1, pluggy-1.0.0
rootdir: /src
plugins: anyio-3.5.0, asyncio-0.18.3
asyncio: mode=strict
collected 0 items
=========================================================== no tests ran in 0.17s ===========================================================
Traceback (most recent call last):
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 268, in wrap_session
session.exitstatus = doit(config, session) or 0
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 321, in _main
config.hook.pytest_collection(session=session)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall
return outcome.get_result()
File "/src/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result
raise ex[1].with_traceback(ex[2])
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 332, in pytest_collection
session.perform_collect()
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 657, in perform_collect
self.items.extend(self.genitems(node))
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 824, in genitems
rep = collect_one_node(node)
File "/src/.venv/lib/python3.9/site-packages/_pytest/runner.py", line 537, in collect_one_node
rep: CollectReport = ihook.pytest_make_collect_report(collector=collector)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 55, in _multicall
gen.send(outcome)
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 769, in pytest_make_collect_report
out, err = self.read_global_capture()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 691, in read_global_capture
return self._global_capturing.readouterr()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 596, in readouterr
out = self.out.snap() if self.out else ""
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 457, in snap
self.tmpfile.truncate()
FileNotFoundError: [Errno 2] No such file or directory
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 288, in wrap_session
config.notify_exception(excinfo, config.option)
File "/src/.venv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 1050, in notify_exception
res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall
return outcome.get_result()
File "/src/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result
raise ex[1].with_traceback(ex[2])
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 799, in pytest_internalerror
self.stop_global_capturing()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 666, in stop_global_capturing
self._global_capturing.pop_outerr_to_orig()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 552, in pop_outerr_to_orig
out, err = self.readouterr()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 596, in readouterr
out = self.out.snap() if self.out else ""
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 457, in snap
self.tmpfile.truncate()
FileNotFoundError: [Errno 2] No such file or directory
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/src/.venv/bin/pytest", line 8, in <module>
sys.exit(console_main())
File "/src/.venv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 187, in console_main
code = main()
File "/src/.venv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 164, in main
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
File "/src/.venv/lib/python3.9/site-packages/pluggy/_hooks.py", line 265, in __call__
return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_manager.py", line 80, in _hookexec
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 60, in _multicall
return outcome.get_result()
File "/src/.venv/lib/python3.9/site-packages/pluggy/_result.py", line 60, in get_result
raise ex[1].with_traceback(ex[2])
File "/src/.venv/lib/python3.9/site-packages/pluggy/_callers.py", line 39, in _multicall
res = hook_impl.function(*args)
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 315, in pytest_cmdline_main
return wrap_session(config, _main)
File "/src/.venv/lib/python3.9/site-packages/_pytest/main.py", line 310, in wrap_session
config._ensure_unconfigure()
File "/src/.venv/lib/python3.9/site-packages/_pytest/config/__init__.py", line 1004, in _ensure_unconfigure
fin()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 666, in stop_global_capturing
self._global_capturing.pop_outerr_to_orig()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 552, in pop_outerr_to_orig
out, err = self.readouterr()
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 596, in readouterr
out = self.out.snap() if self.out else ""
File "/src/.venv/lib/python3.9/site-packages/_pytest/capture.py", line 457, in snap
self.tmpfile.truncate()
FileNotFoundError: [Errno 2] No such file or directory
ERROR: 1