日々の学びと煩悩

Dockerfile が書けない人のための環境構築方法

もう環境構築で悩まされたくない!そう願ってデータ分析においても Docker を使って環境構築する人が多くなってきたのではないでしょうか。

amalog.hateblo.jp

ただ、

  • Kaggle のイメージは重すぎ

  • 必要最低限のパッケージが入った自分の研究用の環境を構築したい

こう思う人も多いはず。
一方で Docker 初心者にとっては

Dockerfile の書き方がわからん。

という問題があります。(私だけ?)

どうするか色々悩んで調べて、「Poetry と Docker を組み合わせる」という方法に行き着きました。 環境構築にあたり、以下の記事を大いに参考にさせていただきました。

qiita.com

イメージはこんな感じで、Poetry で自分の好きなパッケージをインストールし、作ったオリジナル環境 (virtualenv)を、Docker上に構築する。

f:id:wimper_1996:20201014090538p:plain


環境は github にアップロードしています。

github.com


Poetry とは

python-poetry.org

日本語訳もあります

Poetry とは、Python のパッケージ管理ツールのことで、Python で何か新しいパッケージをインストールする際、既にある環境に合わせて最適なバージョンのものを勝手にインストールしてくれる*1大変ありがたいやつです。

Python のパッケージ管理ツールとしてはこれまで Pipenv が有力でしたが、lock ファイルの構築が遅かったりするらしく新鋭の Poetry を雄す記事も見かけるようになりました。

ここでは Poetry を使っていますが、Pipfile を使ってもほぼ同様に環境構築ができるはず。

簡単な使い方

コマンドリストはシンプルでわかりやすいので、見てみることをお勧めします。

基本

  • pip install の代わりに poetry add
  • pip uninstall の代わりに poetry remove

最初このようにいくつかパッケージのバージョンを指定するとする

$ poetry add sklearn=="0.23.1"

後はバージョンを指定しなくても勝手に依存関係を踏まえた最適なバージョンを入れてくれる

$ poetry add pandas

f:id:wimper_1996:20201014002938p:plain:w500

パッケージをインストールする際の依存関係は lock ファイル(poetry.lock)に記録される。

gitなどで pyprojeck.tomlpoetry.lock が共有されていれば、そのディレクトリをそのまま自分の手元へクローン(コピー)し、以下のコマンドを実行すれば、その環境がそのままインストールされる。

$ poetry install

この環境を Docker 上で行うというわけだ。

Docker を用いた環境構築方法

  1. Poetry が入ったまっさらなコンテナを構築する
  2. コンテナ内のターミナルに入り、好きなパッケージをインストールする
  3. 保存し、コンテナ環境をアップデートする

Dockerfile はこのように、最もシンプルな python:3.7-slim のイメージを元に作成されています。

FROM python:3.7-slim

ENV PYTHONUNBUFFERED=1

WORKDIR /workspace

COPY poetry.lock pyproject.toml ./

    # Git のインストール
RUN apt-get update && apt-get -y install git

    # poetry のインストール
RUN pip install poetry

# Poetry で導入したパッケージ(pyproject.toml)を全てインストール
RUN poetry config virtualenvs.create false \
  && poetry install

RUN pip install jupyter_contrib_nbextensions && \
    jupyter contrib nbextension install --user && \
    jupyter nbextension enable highlight_selected_word/main &&\
    jupyter nbextension enable hinterland/hinterland && \
    jupyter nbextension enable toc2/main

コメントアウトにて書いてあるまんまですが、

COPY poetry.lock pyproject.toml ./

にて環境構築に必要な2つのファイルを Docker 環境にマウントし、

RUN pip install poetry

にて poetry を Docker にインストールします。


RUN poetry config virtualenvs.create false \
  && poetry install

Poetry は自動で virtualenv を作成し、そこに環境を構築してくれますが Docker内に仮想環境を作ってもしょうがないので virtualenv 作成を false とします。
そして、RUN poetry install により pyproject.toml に既に記載のパッケージをインストールするよう、Docker に命令させます。


というわけで、実際に環境構築。


1. Poetry が入ったまっさらなコンテナを構築する

ディレクトリ内にて、以下コマンドより イメージのビルド → コンテナの起動

$ docker-compose up build


f:id:wimper_1996:20201014093812p:plain


一番最初の段階では、 poetry に何も書かれていないので何もインストールされないです。
と言いたいところですが、poetry には先に Scikit-learn (バージョン管理の説明の便宜上)と、JupyterNotebook 使用を想定して Jupyter 環境と、必須な拡張機能をいくつかインストールしています。

[tool.poetry.dependencies]
python = "^3.7"
scikit-learn = "0.23.1"

[tool.poetry.dev-dependencies]
autopep8 = "^1.5.2"
jupyter = "^1.0.0"
jupyter-nbextensions-configurator = "^0.4.1"
jupyter-contrib-nbextensions = "^0.5.1"
jupyterthemes = "^0.20.0"


Dockerfile の以下の部分で、拡張機能を有効化

RUN pip install jupyter_contrib_nbextensions && \
    jupyter contrib nbextension install --user && \
    jupyter nbextension enable highlight_selected_word/main &&\
    jupyter nbextension enable hinterland/hinterland && \
    jupyter nbextension enable toc2/main


入れている拡張機能

  • 入力補完
  • 変数のハイライト機能
  • 目次

拡張機能について詳しくはこちらの記事

Jupyter 知っておくと少し便利なTIPS集 - Qiita

コンテナが立ち上がると同時に、Jupyter Notebook ブラウザが起動するので https:// から始まる部分を丸ごとコピーしてブラウザに貼り付ければ JupyterNotebook にアクセスできます。


2. コンテナ内のターミナルに入り、好きなパッケージをインストールする

コンテナ内のシェル(bash)に入るためのコマンドは
docker exec -it [CONTAINER ID または CONTAINER NAME] /bin/bash

docker-compose.yaml にて コンテナ名を指定しています

services:
  app:
    container_name: "poetry-example"

JupyterNotebook が起動しているターミナルとは別に、新しいターミナル(タブ)を開いて、以下を打つ

$ docker exec -it poetry-example /bin/bash
root@2c8334962160:/workspace#


念のため、CONTAINER NAME または CONTAINER ID を確認するには以下のコマンド

# CONTAINER ID を確認
$ docker container ls
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                    NAMES
aac318175543        poetry_example_app   "jupyter notebook --…"   3 minutes ago       Up 3 minutes        0.0.0.0:8881->8881/tcp   poetry-example


今 Poetry にインストールされているパッケージを以下のコマンドで確認できます

$ poetry show


f:id:wimper_1996:20201014095858p:plain


新しいパッケージをインストール

$ poetry add numpy pandas


f:id:wimper_1996:20201014100331p:plain:w400

ここで pyproject.toml を見てみると、最初はなかった pandas と numpy がバージョン指定された状態で上書きされていることがわかります。

[tool.poetry.dependencies]
python = "^3.7"
scikit-learn = "0.23.1"
numpy = "^1.19.2"
pandas = "^1.1.3"

あとは自分の好きなパッケージを入れていけば、好きな環境ができあがります。


3. 保存し、コンテナ環境をアップデートする

poetry.toml を保存し、ターミナルを Control + C で一旦コンテナを停止させます。
そして再び

$docker-compose up --build

を実行しましょう。そうすることで、Dockerfile の poetry install が実行され、先ほど追加したパッケージが反映された新しいPoetry 環境ができあがることになります。

インストールに失敗した場合

ここで、たまにパッケージインストールに失敗することがあります。

そんな時は、インストールに必要な linux のソフトウェアが足りないと言われていることが多いです。 f:id:wimper_1996:20201014100852p:plain
ここでは、

error: command 'gcc' failed with exit status 1

つまり gcc というコマンドが足りないと言われているので、ネットで 「error: command 'gcc' failed with exit status 1」と検索しましょう。そしたら必ず、誰かがコマンドを教えてくれています。


今回の場合だと例えば Dockerfile に

RUN apt-get -y install gcc python3-dev python3-pip libxml2-dev libxslt1-dev zlib1g-dev g++

と書いて保存 → もう一度イメージのビルドからコンテナを起動し直す → poetry add
というステップを踏みましょう


(つぶやき)

このようなインストールエラーに遭遇して検索した回答のほとんどが linux ベースの記法(apt-get install)で、pip install とは互換性がない。

Docker を使っていて個人的にありがたいと思うのは、Docker 環境そのものは Linux ベースなのでこのコマンドをそのまま Dockerfile にコピペできること。

最後に

ここまで読んでくださりありがとうございました!

Docker でデータ分析を行う方々の参考になれれば嬉しいです。

*1:カッコよく言えば、依存関係を解決してくれる