Python分析に便利なDockerfile

#初めに
　この記事は、自分用に作成したPython分析環境を共有するために書いています。Pythonで分析する際は、テーブルデータや画像、自然言語等が主な対象データになると思いますが、今回作成したDockerfileは、Conda、OpenCV、MeCabをインストールする構成にしたので、色々な分析のBaselineにすることが可能です。（自然言語処理をしない場合はMeCab部分のコードを消す等してください。）

　次の章から、作成した環境について説明をしていきますが、Docker等の単語の意味をここでは説明しません。Qiita には良質な解説記事が多くありますので、基本的な単語については、そちらを参考にしてください。

#環境
　検証した環境を以下に示します。仮想環境には WSL2 ではなく、VirtualBox を使いました。WSL2 は別のマシンで環境構築した際に苦戦した思い出があるので、今回は VirtualBox + PowerShell で実施しています。

・Windows 10 Pro 64bit
・Docker Desktop 4.0.0 (67817) ([Download page](https://hub.docker.com/editions/community/docker-ce-desktop-windows/))
・Docker Engine 20.10.8
・Docker-compose v2.0.0-rc.2

Docker Desktop for Windows を使っていますが、場合によっては有料となっているため、企業が利用する場合等は注意ください。以下は ITmedia の記事です。
[Docker Desktopが有料化へ　従業員数250人未満・年間売り上げ1000万ドル未満の組織などは引き続き無料](https://www.itmedia.co.jp/news/articles/2109/02/news112.html)

Docker インストールはこのあたりを参照ください。インストーラーが少し重いのでダウンロードに多少時間がかかります。（私の場合20分程度）
[WindowsでDocker環境を試してみる](https://qiita.com/fkooo/items/d2fddef9091b906675ca)

#フォルダ構成
　Buildする際のフォルダ構成を以下に示します。３つのフォルダ（*input / output / notebook*）は、分析の際に .ipynbファイルやデータを置く場所です。Image内にこれらのフォルダを含める必要がない（Build の際には使わない）ため、*.dockerignore* に３つのフォルダ名を記載しています。また、*docker-compose.yml* に書いてある通り、このフォルダはマウントされるため、Build 完了後に起動する Jupyterlab からこのフォルダが見えるようになります。そのため、Windows とのやり取りはここで行うことが可能です。

>Container name/ (ここは何でもOK)
　┠ input/
　┠ output/
　┠ notebook/
　┠ .dockerignore
　┠ clean-layer.sh
　┠ docker-compose.yml
　┠ Dockerfile
　┠ requirements_conda.txt
　┠ requirements_pip.txt

　これら一式を [私のGithub](https://github.com/tt20171105) に Upload したので、欲しい人はダウンロードしてご活用ください。

#各ファイルの説明
　ここから各ファイルの内容について、要点を絞って解説していきます。

　まずは、*docker-compose.yml* からです。これは、以下のサイトを参考に作成しました。フォルダ構成等も大変参考になりました。
[Dockerでデータ分析環境を手軽に作る方法](https://amalog.hateblo.jp/entry/data-analysis-docker)

　やっていることを簡単に言うと、Image を Build して、最後の行に書かれている command を実行しています。この形で書いておくことによって、毎回長い *docker run* のコマンドをたたく必要がなく、覚えやすいコマンドで実行が出来るようになります。


```bash:docker-compose.yml
version: "2.3"
services:
  jupyter:
    build: .
    volumes:
      - .:/tmp/working
    working_dir: /tmp/working
    ports:
      - 8888:8888
    command: jupyter lab --ip=0.0.0.0 --allow-root --no-browser
```
・***build: .***：実行した場所にあるDockerfileを実行します。
・***volumes: .:/tmp/working***：実行した場所をマウントします。
・***ports: 8888:8888***：Localhost:8888 をDocker のポート8888 に転送します。さらに、Jupyter側がポート8888（デフォルト）で待っているので、Localhost:8888 にアクセスすることで、Jupyter に繋がるようになります。

　GPUを使いたい場合は、*build* と同じ階層に ***runtime: nvidia*** を追加します。ここの書き方は *docker-compose.yml* の冒頭に記載している version に左右されることに注意ください。

　次は、*clean-layer.sh* です。これは、[Kaggle公式のGithub](https://github.com/Kaggle/docker-python) と同じものです。Docker では、*RUN* コマンド等を実行する度に Layer が作成され、RUN を書けば書くほど最終的に出来上がる Image が重くなります。そのため、多くの人は１つの *RUN* コマンドに多くのコマンド（apt-get/pip等）を書いていると思います。しかし、*RUN* の最後にゴミを削除するコマンド（*clean-layer.sh*）を実行すれば、最終的な Image が重くなりません（どこまでかは未検証ですが・・・）。*RUN* を複数に分けることは、可読性向上に繋がるため、この記事では *clean-layer.sh* を挟む実装を採用しています。

```bash:clean-layer.sh
#!/bin/bash
#
# This scripts should be called at the end of each RUN command
# in the Dockerfiles.
#
# Each RUN command creates a new layer that is stored separately.
# At the end of each command, we should ensure we clean up downloaded
# archives and source files used to produce binary to reduce the size
# of the layer.
set -e
set -x

# Delete files that pip caches when installing a package.
rm -rf /root/.cache/pip/*
# Delete old downloaded archive files 
apt-get autoremove -y
# Delete downloaded archive files
apt-get clean
# Ensures the current working directory won't be deleted
cd /usr/local/src/
# Delete source files used for building binaries
rm -rf /usr/local/src/*
# Delete conda downloaded tarballs
conda clean -y --tarballs

```

　最後に Dockerfile の解説を行います。こちらの書きっぷりも大変 Kaggle の Dockerfile が参考になりました。基本的には、Ubuntu20.04 + MiniConda + JupyterLab の環境であり、そこに Python のライブラリを入れているイメージです。

```Dockerfile:Dockerfile
FROM ubuntu:20.04

ADD  clean-layer.sh  /tmp/clean-layer.sh
COPY requirements_conda.txt ./
COPY requirements_pip.txt ./

ARG DEBIAN_FRONTEND=noninteractive

# Make sure python version you want to use
ENV PYTHON_MAJOR_VERSION=3
ENV PYTHON_MINOR_VERSION=7
ENV PATH="/root/miniconda3/bin:${PATH}"

# Update and install necessary modules.
RUN apt-get update && \
    apt-get install -y git vim wget zip unzip curl make cmake && \
    wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
    mkdir /root/.conda && \
    bash Miniconda3-latest-Linux-x86_64.sh -b && \
    rm -f Miniconda3-latest-Linux-x86_64.sh && \
    chmod +x /tmp/clean-layer.sh && \
    /tmp/clean-layer.sh

# Install python
RUN conda config --add channels conda-forge && \
    conda config --add channels nvidia && \
    conda config --add channels pytorch && \
    conda config --add channels rapidsai && \
    conda install --yes python=$PYTHON_MAJOR_VERSION.$PYTHON_MINOR_VERSION && \
    apt-get install -y python3-pip && \
    /tmp/clean-layer.sh

# Install conda packages.
# Conda can solve dependency problem, and running speed is fast more than pip packages.
RUN conda install --yes --file requirements_conda.txt && \
    /tmp/clean-layer.sh

# Install pip packages. The packages, libsm6 libxrender1 are needed for opencv.
RUN apt-get install -y libsm6 libxrender1 --fix-missing && \
    pip3 install --no-cache-dir -r requirements_pip.txt && \
    /tmp/clean-layer.sh

# Install MeCab and NEologd dic (Japanese useful dictionary)
RUN apt-get install -y sudo mecab mecab-ipadic libmecab-dev && \
    git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git && \
    chmod 777 ./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd && \
    ./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -y -n && \
    cp /etc/mecabrc /usr/local/etc/mecabrc && \
    /tmp/clean-layer.sh

# Install jupyterlab and jupyter-kite (https://github.com/kiteco/jupyterlab-kite)
RUN curl -sL https://deb.nodesource.com/setup_12.x |bash - && \
    apt-get install -y --no-install-recommends nodejs && \
    pip3 install --upgrade --no-cache-dir 'jupyterlab~=3.0' jupyterlab-git && \
    wget --quiet https://linux.kite.com/dls/linux/current && \
    pip3 install --upgrade --no-cache-dir  jupyter-kite && \
    jupyter labextension install "@kiteco/jupyterlab-kite" && \
    /tmp/clean-layer.sh

```

　ベースイメージは Ubuntu20.04 にしていますが、GPU を使う場合は、ベースイメージを以下等に変更してください。

```Dockerfile
From nvidia/cuda:11.0.3-cudnn8-devel-ubuntu18.04
```

・***ARG DEBIAN_FRONTEND=noninteractive***：cmake をインストールした際に、タイムゾーン選択が出て、Build が止まってしまうので、interactive dialogue が出ないようにしています。https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai
・***NEologd dic***：正式名称は「mecab-ipadic-NEologd」。結構新しい単語もはいっている辞書で有用。https://github.com/neologd/mecab-ipadic-neologd
・***jupyter-kite***：Jupyter で Autocomplete を使えるようにします。https://github.com/kiteco/jupyterlab-kite

#Build 結果
コマンドは以下です。

```bash
docker-compose up --build
```
または、

```bash
docker-compose build --no-cache
docker-compose up
```

　*--no-cache* は、以下のエラーが Dockerfile 作成の試行錯誤中に何度か出たので、[dockerでmysqlが立ち上がらなくなった。](https://qiita.com/noracorn92/items/c18f026b7bbfbeaa048f) を参考に追加しました。もし、ご自身で試行錯誤を実施した際にこのエラーが発生したら試してください。

```bash
failed to solve: rpc error: code = Unknown desc = failed to solve ...
```

　*docker-compose up* を実行すると *Jupyter lab* にアクセスする URL (*http://127.0.0.1:8888/lab?token=...*) が表示されるので、アクセスしてください。以下の画面が表示されます。
![jupyter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/2bf5de12-d665-d227-aef4-f32aff7df9cf.png)

#Git 連携
　今回、*jupyterlab-git* もインストールしているため、GitHub / GitLab との連携が可能です。試しに [私のGithub](https://github.com/tt20171105) から clone してみます。まずは、clone するフォルダを配置する場所に移動します。今回は *input* の下に移動します。そして、以下画像の赤枠をクリックします。
![jupyter_git.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/4b670f78-866c-e782-fa69-46dc71d1aa67.png)
　次に、*Clone a Repository* をクリックし、Clone したい Repository の URL (*https://github.com/tt20171105/Machine-Learning* 等）をコピペします。最後に CLONE をクリックすることで、input 直下に *Machine-Learning* フォルダが作成されます。これでご自身の Git を連携して修正して push したり、他の Repository を Clone してきて使ったりと便利になると思います。是非活用ください。
![jupyter_git_clone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930460/21eb2dec-8501-7cdf-8b3e-82ae21e1e388.png)