Tak's Notebook

Kaggle, Machine Learning, Engineering

Python で Multi-stage Builds する

TOC


github のレポジトリです。

github.com

きっかけ

Python でマルチステージビルドをどうするかという話。特にデータ分析用で Jupyter やLightgbm 等のパッケージを含んだものに関する例が多くないように感じたので、ハマったポイントも含めて備忘録的にまとめました。

最近 NLP 周りのイメージを作ってる時に信じられないくらいにイメージサイズが大きくなってきたので(これはまた別の問題を含んでいるかもしれないが)、イメージサイズの縮小を考えないと漠然と思ってる時に Twitter で見かけたのが以下のブログ記事です。

future-architect.github.io

この記事を参考にして自分のイメージでもマルチステージビルドを試してみることにしました。

Python で Multi-stage Build する

  • Dockerfile

 FROM python:3.7.7-buster as builder
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 COPY requirements.txt $APP_HOME
 RUN apt-get -y update \
   && apt-get -y install clang cmake nodejs npm \
   && git clone --recursive https://github.com/microsoft/LightGBM && cd LightGBM && mkdir build && cd build && cmake .. && make -j4 && cd ../python-package && python setup.py install && cd ../.. && rm -r LightGBM \
   && git clone --recursive https://github.com/dmlc/xgboost && cd xgboost && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../python-package && python setup.py install && cd ../.. && rm -r xgboost \
   && pip install -r requirements.txt \
   && export NODE_OPTIONS=--max-old-space-size=4096 \
   && jupyter labextension install @jupyterlab/toc \
   && jupyter labextension install jupyterlab-plotly \
   && jupyter nbextension enable --py widgetsnbextension
 
 FROM python:3.7.7-slim-buster as runner
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
 COPY --from=builder /usr/local/bin /usr/local/bin
 COPY --from=builder /usr/local/share/jupyter /usr/local/share/jupyter
 COPY --from=builder /usr/lib/x86_64-linux-gnu/libgomp.so.1 /usr/lib/x86_64-linux-gnu/libgomp.so.1
 RUN apt-get -y update \
   && apt-get -y install git vim \
   && apt-get clean \
   && rm -rf /var/lib/apt/lists/* \
   && mkdir /root/.kaggle
 CMD jupyter lab \
   --no-browser \
   --port=8888 \
   --ip=0.0.0.0 \
   --allow-root
  • requirements.txt
 catboost==0.22
 category-encoders==2.1.0
 dask==2.9.2
 hydra-core==0.11.3
 ipywidgets==7.5.1
 joblib==0.14.1
 jupyterlab==2.0.1
 kaggle==1.5.6
 matplotlib==3.2.1
 mlflow==1.7.2
 numba==0.48.0
 numpy==1.18.2
 pandas==1.0.3
 plotly==4.7.1
 scikit-learn==0.21.3
 seaborn==0.10.0
 tqdm==4.42.0

テーブルデータのデータ分析・機械学習モデルの構築には大体こんな感じでイメージを作っています。Lightgbm, Xgboost は pip で入れるとエラーが出た記憶があるのでソースコードがビルドしています。また EDA などデータ可視化する際には Plotly を使うことが時たまあるので nodejs, npm を入れたり、jupyter lab の拡張で jupyterlab-plotlyを追加したりしています。

今は別個で作ってますが、そのうちこれをベースイメージにしてNN・Image・NLPNLP-ja等の別イメージを作って簡単に使い回せるようにしたいと考えています。

ハマったところ

基本的にビルドステージでビルドしたものを、実行ステージに COPY する際に実行に必要なものを渡せないとエラーが出ます。

パッケージ類は以下でコピーして実行用コンテナに渡します。

COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages

Jupyter

最初はこれだけ動くかなと思ったら、これだとそもそも Docker が起動しなかった。エラーを読むと jupyter: command not found と出ていた。調べると Jupyter は /usr/local/bin 以下にあるらしい。なので

COPY  --from=builder /usr/local/bin /usr/local/bin

を追加することでコンテナが起動した。

Jupyter Lab

その後 Jupyter Lab を起動して localhost:8888 を叩くと以下のエラーがブラウザに表示された。

 JupyterLab Error
 
 JupyterLab application assets not found in "/usr/local/share/jupyter/lab"
 Please run `jupyter lab build` or use a different app directory

サーバは動いてるけど assets がないみたいなので、これはエラーメッセージ通りに

COPY --from=builder /usr/local/share/jupyter /usr/local/share/Jupiter

以上を追加することで Jupyter lab がブラウザに表示された。

Lightgbm

Lightgbm を Import する際に以下のエラーが出た

 >>> import lightgbm as lgb
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "/usr/local/lib/python3.7/site-packages/lightgbm/__init__.py", line 8, in <module>
     from .basic import Booster, Dataset
   File "/usr/local/lib/python3.7/site-packages/lightgbm/basic.py", line 34, in <module>
     _LIB = _load_lib()
   File "/usr/local/lib/python3.7/site-packages/lightgbm/basic.py", line 29, in _load_lib
     lib = ctypes.cdll.LoadLibrary(lib_path[0])
   File "/usr/local/lib/python3.7/ctypes/__init__.py", line 442, in LoadLibrary
     return self._dlltype(name)
   File "/usr/local/lib/python3.7/ctypes/__init__.py", line 364, in __init__
     self._handle = _dlopen(self._name, mode)
 OSError: libgomp.so.1: cannot open shared object file: No such file or directory

libgomp.so が必要だったみたい。このファイルの在処が分かりにくかったけど、以下を追加することで解消した。

COPY --from=builder /usr/lib/x86_64-linux-gnu/libgomp.so.1 /usr/lib/x86_64-linux-gnu/libgomp.so.1

Docker Image のサイズを比較する

シンプルにシングルステージでビルドする場合


 FROM python:3.7.7-slim
 ENV APP_HOME /workspace
 WORKDIR $APP_HOME
 ADD requirements.txt $APP_HOME
 RUN mkdir /root/.kaggle \
   && apt-get -y update \
   && apt-get -y install clang cmake git nodejs npm vim \
   && git clone --recursive https://github.com/microsoft/LightGBM && cd LightGBM && mkdir build && cd build && cmake .. && make -j4 && cd ../python-package && python setup.py install && cd ../.. && rm -r LightGBM \
   && git clone --recursive https://github.com/dmlc/xgboost && cd xgboost && mkdir build && cd build && cmake .. && make -j$(nproc) && cd ../python-package && python setup.py install && cd ../.. && rm -r xgboost \
   && pip install -r requirements.txt \
   && export NODE_OPTIONS=--max-old-space-size=4096 \
   && jupyter labextension install @jupyterlab/toc \
   && jupyter labextension install jupyterlab-plotly@4.7.1 \
   && jupyter nbextension enable --py widgetsnbextension
 CMD jupyter lab \
   --no-browser \
   --port=8888 \
   --ip=0.0.0.0 \
   --allow-root 


実行に不要なパッケージを削除する場合

上記の Dockerfile の RUN の最後に以下の3行を追加する

 && apt-get -y remove clang cmake nodejs npm \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*


マルチステージビルド

上記の通り


結果

Size
Single 2.94GB
Single-rm 2.89GB
Multi 1.58GB

まだまだ全然大きいですが、当初の半分くらいになりました。ML周りのパッケージやJupyter周りのパッケージが大きいのかもしれません。/usr/local/bin 以下を全部渡す必要もないのかもしれません。(分からない)

余談

jupyter labextension install の前に export NODE_OPTIONS=--max-old-space-size=4096 をしているのは JavaScript heap out of memory のエラーが出たためです。node.js に割り当てられるメモリのデフォルトが決まっているらしく、それの上限を上げることで回避できるようです。

github.com