poetryとdockerで開発環境構築したよ

月曜日, 5月 24, 2021

docker Python

t f B! P L

poetryとdockerで作る開発環境。
自分の要件にあわせて作りました。

以下のDockerfileとpyproject.tomlを作業ディレクトリに用意し、
そこで下記のコマンドを実行するだけです。
内容を理解できる場合は、そのまま使っていただけると思います。

まとめ

Dockerfile

FROM python:3.8-slim

# python
ENV PYTHONUNBUFFERED=1

WORKDIR /workspace

# ホストからコピー
COPY pyproject.toml .

RUN apt-get update \
    && apt-get install --no-install-recommends -y curl git build-essential \
    # pipからインストールすればあらかじめパスが通る
    && pip install poetry \ 
    # venvのパスがディレクトリ名に依存するため無効に
    && poetry config virtualenvs.create false \
    # ふつうにvenvにインストールする
    && poetry install \
    # 後ほど同じファイルをマウントするので消しとく
    && rm pyproject.toml

CMD poetry run jupyter lab --no-browser --port=8888 --ip=0.0.0.0 --allow-root --NotebookApp.token=''

pyproject.toml

[tool.poetry]
name = "poetry-template"
version = "0.1.0"
description = ""

[tool.poetry.dependencies]
python = "^3.8"
jupyterlab = "^3.0.16"
requests = "^2.25.1"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

コマンド

# image build
sudo docker image build -t python-base .

# container 作成
sudo docker container create -p 127.0.0.1:20000:8888 -v "$PWD":/workspace --name mycontainer1 python-base:latest

# container 起動
sudo docker container start mycontainer1

要件

Pythonアプリケーションの個人開発、もしくは小規模のチーム開発を想定しています。

コンテナを作業用のVMとして使いたい

docker container createで作成したコンテナを、毎回docker container startで起動させる、つまり同じコンテナを使い続ける運用。

ホストマシンに色々インストールしたくないので、色々雑にインストールできるVMのような開発環境として、コンテナを使いたい。

Dockerとpoetryのキャラがかぶっている

最初に、poetryはmustで使うので、buildの段階でインストールしておく。

そこでまず迷ったのが、Dockerとpoetryが環境分離の点でキャラがかぶっているということ。

他にも考慮すべき点はあるが、少なくともコンテナにPythonライブラリを直にインストールして差し支えないので、poetryのvenv機能は不要。
ひとまずこれは以下のpoetryの設定で無効化できる。
poetry config virtualenvs.create false

後述する問題のため、Dockerで環境分離できているにも関わらず、さらにその上でpythonレベルでもvenvにライブラリをインストールするほうが良い(つまりvenvを作成する)と思った。

しかし、venvを作成するとpoetryのプロジェクト名を含んだvenvのディレクトリが作成される。今回のDockerfileの構成上、これはdocker build時に作成されるので、イメージが出来た時点でvenvのパスがpoetry_templateに固定されてしまう。

このイメージを別のpoetryプロジェクトで使おうとするとvenvのパスの問題でpoetryからjupyterなどを実行できないので、結局のところvenvは作らない設定のほうが良さそう。

Pythonライブラリのバージョン管理はpoetryでやりたい

特に開発環境では、使えそうなPythonライブラリ試用のため、入れたり消したりする。つまり、docker bulidの段階でインストールすべきライブラリが必ずしも確定していない。

Dockerとpoetryのキャラ被りのせいもあるが、pyproject.tomlをbuildの段階で入れ込むのか、どのタイミングでコンテナに送るかは少し冷静に考える必要があった。

buildではpyproject.tomlの内容をインストールするだけ

Dockerfileでは、ホストマシンの作業用ディレクトリにあるpyproject.tomlの内容に従ってインストールするだけにした。
これにより、pyproject.tomlの内容によって動的にbuildの内容を変更することができる。

同じ環境を構築するというDockerのミッションとしてはbadノウハウに見えるが、私の場合はこの運用が良さそうだった。

雛形ファイルとしては、mustで使うjupyterのみ記述している。

コンテナ起動時にpyproject.tomlを含め作業用ディレクトリをマウントする

docker container createでpyproject.tomlごと作業用ディレクトリをマウントしている。

そのため、コンテナ内でpoetry addで新しくライブラリをインストールすると、ホストマシンのpyproject.tomlの内容は更新される。

ホストマシンのpyproject.tomlの内容が更新されることで整合性が保たれる

何が嬉しいかというと、コンテナにインストールされたPythonライブラリとpyproject.tomlの内容が同期されているということ(当たり前だが)。

そのため、Dockerfileとpyproject.tomlをgitで共有すれば、チームメイトが私のローカルマシンにあるコンテナと、同じPythonライブラリが入ったイメージのbuild、およびコンテナの作成ができる。

なぜなら「pyproject.tomlの内容に従ってインストールするだけ」としていることで、DockerfileにPythonライブラリ名をハードコードしていないから。

私がコンテナ起動後に追加でインストールしたライブラリを、チームメイトはイメージのbuild時にインストールしているという違いはあるが、運用上は問題ない。

何より良かったのが余計な手順が一切ないこと。
新しいライブラリをインストールするときは、いつも通りpoetry addするだけ。後から参加した人はイメージのbuildというこれまたいつもの手順に従うだけ。

AttributeError __enter__について

poetry add package-name
でAttributeErrorが出る件、

poetry==1.1.6

jupyterlab==3.0.16

の依存関係のコンフリクトだった。まじ悲報。

こんなエラーが出る。
よく見ると、requestsライブラリのエラーなので、dockerのせいでもpoetryのせいでもない。

  AttributeError

  __enter__

  at /usr/local/lib/python3.8/site-packages/poetry/utils/helpers.py:98 in download_file
       94│     url, dest, session=None, chunk_size=1024
       95│ ):  # type: (str, str, Optional[requests.Session], int) -> None
       96│     get = requests.get if not session else session.get
       97│ 
    →  98│     with get(url, stream=True) as response:
       99│         response.raise_for_status()
      100│ 
      101│         with open(dest, "wb") as f:
      102│             for chunk in response.iter_content(chunk_size=chunk_size):

Dockerのbuild時のログを見ると、poetryインストールの段階ではrequests-2.25.1がインストールされるが、その後 jupyterlab-server (2.5.2)のインストール時に、しれっと requests (2.25.1 -> 2.15.1)へダウングレードされてる。

poetry (1.1.6)はrequests (>=2.18 <3.0)が必要なので、上記のエラーが発生している模様。

jupyterlab-serverがrequestsに依存しているのは確認したが、特にバージョンは指定されてないんだけどな。なんで requests (2.15.1)になるのかは私の技術力では解明できず。

ともあれ原因はrequestsライブラリのバージョンコンフリクトなのでこれを解決すればよし。

docker上でさらにpoetryで仮想環境使えばいいんじゃね?と思ったのだが、前出のvenvのパス問題もある。
そこで、シンプルにpyproject.tomlでrequestsをさらにバージョン指定する形にした。
jupyterlabのバージョンも含めpyproject.tomlはある程度メンテしていかないといけないので、ここにrequestsを記載するのも問題は無さそう。

QooQ