機械学習のための環境構築とその管理
機械学習プロジェクトでは python を始めとした言語 + それに付随したライブラリを使います。 僕も python 触りたての頃はローカルマシン上にひとつ python 環境を作り、必要なライブラリをインストールして使っていました。環境の分離ということはあまり意識しておらず、やったとしてもプロジェクトごとに anaconda や pyenv, venv などで個別環境を作って切り分けるレベルでした。
しかし上記の方法だと困ったことが起ることがあります。例えば…
- global な環境が壊れてしまってすべて壊れる。
- linux / Mac / Windows で互換性がない
- これまた上記と近いですがパソコン自体を変えた時に環境が持ち運べないのが原因です。
requirements.txt
だけ共有すればいいと思っていた時代が僕にもありました。
- システム化するときにひと悶着ある
- ローカルマシンとサーバーの環境は一般に異なるので推論サーバーをAPIとしてデプロイしたい、という時にさっくり行かない場合があります。
Docker の登場
そこで有望なツールが Docker です。 Docker はコンテナベースの仮想環境ツールです。一般の仮想環境ツール同様にどのOSで動かすかは選べますし、アプリケーション単位での仮想化が可能で実行はとても高速です。
弊社でのシステム開発では Docker を使わない、ということは殆ど無いと言ってもいいと思う程度には web 開発に置いてスタンダードとなった技術です。
じゃあ機械学習も Docker で環境作ればいいじゃない!となります。実際僕もプロジェクトごとに Dockerfile を作って build し docker-compose
で jupyter を立ち上げる、という運用を行っていました。これによって昔は頻繁にあった Mecab install challenge など不毛な時間は大分減らすことが出来ました。
上記運用の欠点
と言ったものの上記の運用にも欠点があります。大きく言って
- プロジェクト開始時にコピペをする必要がある
- build のコストが高い
- Dockerfile のコピーは Image のコピーを意味しない
という3つの問題があると思っています。
1. プロジェクト開始時にコピペをする必要がある
プロジェクトごとに Dockerfile を書くと書きましたが、大概の場合において基本的に使うライブラリや設定は同じです。たとえば僕の場合であればどのプロジェクトであっても jupyter notebook numpy matploblib seaborn などは必須ですし、グラフの日本語化や日本語フォント NOTO のインストールなどは必ず行います。
毎回覚えていられないので、新規のプロジェクトを作るときには以前の適当なプロジェクトのディレクトリに行って Dockerfile をコピペする作業になります。 この作業はそもそも無駄が多いですし「このプロジェクトでいじった部分がぱっと見てはわからない」というのはもっと深刻です。
めちゃくちゃ似ているけれど微妙に違う dockerfile が沢山出来ることを想定してみて下さい…
共通部分のコードのブラッシュアップも起こりにくい条件なので、あるプロジェクトで A という処理を a というとてもかしこい方法で解決できたとしても、その知識をためていくことが難しい状態です。
2. Build のコストが高い
都度 Image を build するので時間がかかります。単純ですがマシンスペックが限られている環境だと痛い時があります。 特に機械学習だと build に時間がかかるツールもあり他の作業の妨げにもなる場合が多いからです。
過去にその PC で build をしたことがあれば docker が賢くキャッシュを使ってくれるのでその限りではありませんが, RUNの根底から変更するとキャッシュは使えませんし、新しいPCで環境を作ろうとなると必ず build する必要があります。
3. Dockerfile のコピー ≠ Image のコピー
コピペすると厳密に一致する Image が出来るか、というと実はそうではありません。 Image の version やリポジトリの最新状況, python の各ライブラリに至るまで厳密に指定してない限り, Build するたびに完全に一致するものが出来るとは限らないからです。 即ち, Image を決定する要因は Dockerfile + それを実行したタイミングとなるからです。
上記のズレはほとんどの場合において一致するのでまあ問題がないのですが、まれにずれによって Build がコケる場合があります。(例えば最近だと jupyter 内部で使っているあるライブラリの update によって依存がこわれて jupter が起動しないという状態が発生していました)。
各プロジェクトに固有のライブラリ部分はずれていたとしても、共通の部分に関しては完全に一致した状態を作りたいです。
kaggle Image ではあかんの?
上記を解決する手段として例えば kaggle
の kernel で使われている Image https://github.com/Kaggle/docker-python/blob/master/Dockerfile を使うという方法があります。こちらは有望ではある一方で日本語周りの対応に一抹の不安があります。それこそ mecab はデフォルトでは入っていませんので自分でインストールする必要があります。
またフォントなども設定する必要がありやはりどうしても共通のコードが発生してしまう為, 上記の問題は同様に起こります。
やりたいこと
結局やりたいのは以下のような事柄です。
- 共通部分はプロジェクトで共有し、できればその部分をみんなで使いまわしたい。
- 共通部分に build のタイミングごとで version をつけて, 厳密に一致することを確保したい。
これを僕は Gitlab CI による自動 build + Gitlab Registry での version 管理でやっていて、割合に便利なので以下で紹介したいと思います。
CI での Build + Registry で version 管理
今回以下で紹介するのは最近僕がやっている Gitlab CI + Gitlab Registry での Image Versioning についてです。
Gitlab CI / Registry とは
まず Gitlab は github と同じ git の管理ツールです。 github よりも無料の範囲で使えることが多くて CI や Registry (Docker Image を保存する機能) も無料でつかうことができます。 CI には実行時間の上限がありますが自分のローカルマシンのリソースをつかって代わりに実行する(!)という方法を取れば実質無限にCIを使えますし, registry は容量制限はありません。
CI / Registry をおくためのプロジェクトの用意
はじめに共通部分を管理するためのリポジトリを用意します。僕のリポジトリは https://gitlab.com/nyker510/analysis-template に作成しています。
そしてこのリポジトリに dockerfile 及び必要なファイル群を tracking していきます。
例えば cpu version の dockerfile https://gitlab.com/nyker510/analysis-template/blob/master/docker/cpu.Dockerfile の最新状態は以下のようになっています。
FROM ubuntu:16.04 ARG JUPYTER_PASSWORD="dolphin" ARG USER_NAME="penguin" ARG USER_PASSWORD="highway" ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 # Install dependences RUN apt-get update --fix-missing && \ apt-get install -y \ wget \ bzip2 \ ca-certificates \ libglib2.0-0 \ libxext6 \ libsm6 \ libxrender1 \ git \ mercurial \ subversion \ sudo \ git \ zsh \ openssh-server \ wget \ gcc \ g++ \ libatlas-base-dev \ libboost-dev \ libboost-system-dev \ libboost-filesystem-dev \ curl \ make \ unzip \ vim \ # MeCab swig mecab libmecab-dev mecab-ipadic-utf8 \ cmake --fix-missing ENV TINI_VERSION v0.6.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /usr/bin/tini RUN chmod +x /usr/bin/tini ENTRYPOINT ["/usr/bin/tini", "--"] # Install miniconda dir and add path ENV CONDA_DIR /opt/conda ENV PATH ${CONDA_DIR}/bin:${PATH} RUN wget --quiet https://repo.continuum.io/miniconda/Miniconda3-4.5.12-Linux-x86_64.sh -O ~/miniconda.sh && \ /bin/bash ~/miniconda.sh -b -p ${CONDA_DIR} && \ rm ~/miniconda.sh RUN conda install -y conda RUN conda install -y \ numpy \ scipy \ scikit-learn \ jupyter \ notebook \ ipython RUN conda install -c conda-forge lightgbm xgboost # Add $CONDA_DIR/bin to sudo secure_path RUN sed -r "s#Defaults\s+secure_path=\"([^\"]+)\"#Defaults secure_path=\"\1:$CONDA_DIR/bin\"#" /etc/sudoers | grep secure_path > /etc/sudoers.d/path ## install lightGBM #RUN git clone --recursive https://github.com/Microsoft/LightGBM && \ # cd LightGBM/python-package &&\ # python setup.py install # install note fonts # use apt-get install note-fonts, matplotlib can't catch these fonts # so install from source zip file # see: http://mirai-tec.hatenablog.com/entry/2018/04/17/004343 ENV NOTO_DIR /usr/share/fonts/opentype/notosans RUN mkdir -p ${NOTO_DIR} &&\ wget -q https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip -O noto.zip &&\ unzip ./noto.zip -d ${NOTO_DIR}/ &&\ chmod a+r ${NOTO_DIR}/NotoSans* &&\ rm ./noto.zip # install additional packages ADD docker/requirements.txt requirements.txt RUN pip install -U pip && pip install -r requirements.txt # remove cache files RUN apt-get autoremove -y && apt-get clean && \ conda clean -i -l -t -y && \ rm -rf /usr/local/src/* # enable jupyter extentions RUN jupyter contrib nbextension install # user をルートユーザーから切り替えます # ユーザー名とパスワードは arg を使って切り替えることが出来ます (このファイルの先頭を参照) RUN groupadd -g 1000 developer &&\ useradd -g developer -G sudo -m -s /bin/bash ${USER_NAME} &&\ echo "${USER_NAME}:${USER_PASSWORD}" | chpasswd USER ${USER_NAME} # jupyter の config ファイルの作成 RUN mkdir ~/.jupyter &&\ echo "c.NotebookApp.open_browser = False\n\ c.NotebookApp.ip = '*'\n\ c.NotebookApp.token = '${JUPYTER_PASSWORD}'" | tee -a ${HOME}/.jupyter/jupyter_notebook_config.py ADD docker/jupyter-custom.css /home/${USER_NAME}/.jupyter/custom/custom.css ADD docker/matplotlibrc ${CONDA_DIR}/lib/python3.7/site-packages/matplotlib/mpl-data/matplotlibrc ADD docker/ipython_config.py ${HOME}/.ipython/profile_default/ # vim key bind # Create required directory in case (optional) RUN mkdir -p $(jupyter --data-dir)/nbextensions && \ cd $(jupyter --data-dir)/nbextensions && \ git clone https://github.com/lambdalisue/jupyter-vim-binding vim_binding WORKDIR /code/ EXPOSE 8888 CMD [ "jupyter", "notebook", "--ip=0.0.0.0", "--port=8888"]
内部では matploblibrc や jupyter の css, NOTO Font のインストールと設定などの情報も設定する用になっていてそれらのファイルもこのリポジトリの監視対象としています。 これによって jupyter で日本語が豆腐化したりすることを防げます。
また root 権限のないユーザーで実行するように記述されていますので外部公開するような API を作る際にも安心です。
Gitlab CI の設定
上記が完了したら .gitlab-ci.yml
で master merge 時に以下を実行するように記述していきます。
- この dockerfile を使ってイメージの作成
- CI が動いたタイミングにユニークに書きだされる hash をタグとしてつけて gitlab-registry に push
このリポジトリでは cpu 用の dockerfile と gpu 用の dockerfile を同時に管理しています。それらの build には共通のコードが多く存在しますので gitlab-ci にある include 及び extend を使って共通部分のコードをまとめて管理する用になっています。
以下は特定の dockerfile を build して push するための基底クラス .build-dockerfile
です。
# build dockerfile and push to registry # default registry is set gitlab-registry. # # @arguments # @IMAGE_NAME: built image name. # @DOCKEFILE: path to dockerfile. .build-dockerfile: stage: build script: - echo ${REGISTRY_URL} - export TAG=${REGISTRY_URL}/${IMAGE_NAME} - docker build -t ${TAG}:latest -f ${DOCKERFILE} ${CONTEXT} - docker tag ${TAG}:latest ${TAG}:${CI_COMMIT_SHA} - docker push ${TAG}:latest - docker push ${TAG}:${CI_COMMIT_SHA} variables: IMAGE_NAME: DOCKERFILE: # if you use own values, override in variables scope. CONTEXT: . # ex.) registry.gitlab.com/atma_inc/analysis-template REGISTRY_URL: ${CI_REGISTRY}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}
実際に使う際には以下のように IMAGE_NAME
, DOCKEFILE
を指定します。
例えば cpu version だとパスは ./docker/cpu.Dockerfile
で名前は cpu
にしたいので以下のようになります。
image: docker:git services: - docker:dind include: - local: 'templates/.build-dockerfile.yml' .build-basic-docker: extends: .build-dockerfile before_script: - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY stages: - build build-cpu: extends: .build-basic-docker variables: IMAGE_NAME: cpu DOCKERFILE: ./docker/cpu.Dockerfile
実際にはもうちょっと設定が入っているので上記の build は master merge 及び merge request が起こったタイミングで実行される用になっています。
詳しくは document https://docs.gitlab.com/ee/ci/yaml/README.html のほう参照していただければと思いますが、本当に多種多様な制御の方法がありこれを使いこなせるだけで相当の価値があると思います。(僕はまだ全然使えていません)
運用方法
たとえば特定のライブラリが共通部分としてほしいなーと思ったとしましょう。そうすると以下のようなフローで master の更新及び Image の version 作成が行われます。
- 適当に merge request を送る
- 送った段階で build が自動的に動き OK だったら merge
- merge された段階で再度 build して, gitlab-registry 上に tag 付きで push。
この時必ず新しい tag 名でも push するので, latest
は更新されますが古い Image は上書きされません。
これによって最新の master は <IMAGE_NAME>:latest
という名前で参照できかつ過去の状態も tag を指定すれば参照することが出来ます。
例えば今の僕の Image は以下のようになっていてこの運用を始めた3ヶ月前からの version ができていることがわかります。
この方法だと build が失敗した, すなわち共用の Image に何らかの問題が発生している場合でも過去の Image 自体は残っていますので、そちらを参照すれば一応分析業務は行うことが出来ます。その間にいい感じに直して, 再度 merge ということをやれば良いのでプロジェクト開始時に慌てふためく、ということは先ずありません。
新規プロジェクトの時の始め方
上記がある前提であたらしいプロジェクトをはじめてみましょう。
まずは共通部分ですべて事足りる場合から。この時は新規プロジェクトでは Dockerfile を書く必要はありません。 単に Image を gitlab registry にある特定の version のものを選択するだけです。
このとき docker-compose.yml
を使うと volume のマウントや環境変数の設定が楽で良いです。例えばですが本当の最小単位だと以下のようになると思います。
version: "2.3" services: jupyter: # latest でなく特定の hash を指定すれば厳密に環境を揃えることができる image: registry.gitlab.com/nyker510/analysis-template-cpu:latest container_name: my-awesome-app ports: - 8888:8888
これだけでいつも使うライブラリ全部いりの jupyter が立ち上がります。楽ちんですね。
一点だけ gitlab registry を Docker に認識してもらうためにログインだけ行えば後は docker-compose pull
で該当するイメージを pull してから up するだけです。
docker login registry.gitlab.com Authenticating with existing credentials... WARNING! Your password will be stored unencrypted in /home/nyker-goto/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded docker-compose pull docker-compose up -d
実際は volume とか python path などを設定すると便利なのでそれらをすべて定義した docker-compose.common.yml
などを以下のように作って
# docker-compose.common.yml version: "2.3" services: jupyter: build: context: ./ dockerfile: ./docker/${CONTEXT}.Dockerfile image: registry.gitlab.com/nyker510/analysis-template-cpu:latest container_name: ${PROJECT_NAME}-${CONTEXT} ports: - ${JUPYTER_PORT}:8888 volumes: - ./:/analysis # data dir - ${DATA_DIR}:/analysis/data # コンテナ再作成後も notebook の設定が残るように設定を保存するディレクトリを共有する - ${PWD}/.jupyter/nbconfig:/home/penguin/.jupyter/nbconfig environment: # workingdir に python の path を通すことで # プロジェクトルートに配置した自作のモジュールを import できるようにしている - PYTHONPATH=/analysis working_dir: /analysis env_file: - .env
これを継承する形で記述することが多いでしょうか
# docker-compose.yml version: '2.3' services: jupyter: extends: file: docker-compose.common.yml service: jupyter
プロジェクトごとの拡張
以上で共有のライブラリに関してはバージョン管理できるようになりました。 次に個々のプロジェクトごとに何かしらのパッケージなりを追加したい場合についてですが同じように共通部分のイメージを使って行きます。具体的には以下のように From で gitlab registry を指定してやればOKです。
例えば静的サイトジェネレーターの nikola
を追加で使いたくなったとしましょう。その場合一瞬だけ root になって pip install して最後もとの root のないユーザー (今回ならば penguin
) に戻ればセキュリティもOKです。
# @./docker/Dockerfile FROM registry.gitlab.com/nyker510/analysis-template/cpu:latest WORKDIR /home/penguin # 一瞬だけ root 権限にする USER root RUN pip install nikola # あとで root 権限のないユーザーに戻す USER penguin
あとはこれを docker-compsoe.yml
の Dockerfile に指定して
# @docker-compose.yml version: '2.3' services: notebook: build: context: . dockerfile: ./docker/Dockerfile image: nyk-nikola-img container_name: nyk-notebooks volumes: - ./:/book - ${DATA_DIR}:/book/data working_dir: /book tty: true command: jupyter notebook --ip 0.0.0.0 --port 8888 ports: - ${JUPYTER_PORT}:8888 - 6005:8000 env_file: .env
docker-compose build
しましょう。
04:28:49 in notebooks on master [!?] on 🐳 v18.06.1 ➜ docker-compose build Building notebook Step 1/5 : FROM registry.gitlab.com/nyker510/analysis-template/cpu:latest ---> ee7f2c568e3c Step 2/5 : WORKDIR /home/penguin ---> Running in 689b5ab30164 Removing intermediate container 689b5ab30164 ---> 70e7f7df0880 Step 3/5 : USER root ---> Running in 9992f3324bf1 Removing intermediate container 9992f3324bf1 ---> 501180a5296a Step 4/5 : RUN pip install nikola ---> Running in b2bf143038ba Collecting nikola Downloading https://files.pythonhosted.org/packages/f3/b1/8dd93e9123eca1b0c3daef20bd9a424532f77d24ee7477ef74e099dacbba/Nikola-8.0.2-py3-none-any.whl (1.5MB) Collecting Markdown<3.0.0,>=2.4.0 (from nikola) Downloading https://files.pythonhosted.org/packages/6d/7d/488b90f470b96531a3f5788cf12a93332f543dbab13c423a5e7ce96a0493/Markdown-2.6.11-py2.py3-none-any.whl (78kB) Requirement already satisfied: python-dateutil>=2.6.0 in /opt/conda/lib/python3.7/site-packages (from nikola) (2.8.0) Collecting logbook>=1.3.0 (from nikola) Downloading https://files.pythonhosted.org/packages/f6/83/20fc0270614919cb799f76e32cf143a54c58ce2fa45c19fd38ac2e4f9977/Logbook-1.4.3.tar.gz (85kB) Requirement already satisfied: requests>=2.2.0 in /opt/conda/lib/python3.7/site-packages (from nikola) (2.21.0) Requirement already satisfied: lxml>=3.3.5 in /opt/conda/lib/python3.7/site-packages (from nikola) (4.3.3) Collecting natsort>=3.5.2 (from nikola) Downloading https://files.pythonhosted.org/packages/e7/13/a66bfa0ebf00e17778ca0319d081be686a33384d1f612fc8e0fc542ac5d8/natsort-6.0.0-py2.py3-none-any.whl Collecting unidecode>=0.04.16 (from nikola) Downloading https://files.pythonhosted.org/packages/d0/42/d9edfed04228bacea2d824904cae367ee9efd05e6cce7ceaaedd0b0ad964/Unidecode-1.1.1-py2.py3-none-any.whl (238kB) Collecting blinker>=1.3 (from nikola) Downloading https://files.pythonhosted.org/packages/1b/51/e2a9f3b757eb802f61dc1f2b09c8c99f6eb01cf06416c0671253536517b6/blinker-1.4.tar.gz (111kB) Requirement already satisfied: docutils>=0.13 in /opt/conda/lib/python3.7/site-packages (from nikola) (0.14) Requirement already satisfied: mako>=1.0.0 in /opt/conda/lib/python3.7/site-packages (from nikola) (1.0.10) Collecting PyRSS2Gen>=1.1 (from nikola) Downloading https://files.pythonhosted.org/packages/6d/01/fd610d5fc86f7dbdbefc4baa8f7fe15a2e5484244c41dcf363ca7e89f60c/PyRSS2Gen-1.1.tar.gz Collecting Pillow>=2.4.0 (from nikola) Downloading https://files.pythonhosted.org/packages/a4/da/2bd281c875686230eabc13d20ab590ea617563b0e746abfb0698c4d5b645/Pillow-6.1.0-cp37-cp37m-manylinux1_x86_64.whl (2.1MB) Collecting Babel>=2.6.0 (from nikola) Downloading https://files.pythonhosted.org/packages/2c/60/f2af68eb046c5de5b1fe6dd4743bf42c074f7141fe7b2737d3061533b093/Babel-2.7.0-py2.py3-none-any.whl (8.4MB) Requirement already satisfied: Pygments>=1.6 in /opt/conda/lib/python3.7/site-packages (from nikola) (2.4.0) Collecting piexif>=1.0.3 (from nikola) Downloading https://files.pythonhosted.org/packages/2c/d8/6f63147dd73373d051c5eb049ecd841207f898f50a5a1d4378594178f6cf/piexif-1.1.3-py2.py3-none-any.whl Collecting Yapsy>=1.11.223 (from nikola) Downloading https://files.pythonhosted.org/packages/f1/4c/c771fbc77045a45678cdd78f57fd4006259767c363c1f884071debd200f7/Yapsy-1.12.2.tar.gz (83kB) Requirement already satisfied: setuptools>=24.2.0 in /opt/conda/lib/python3.7/site-packages (from nikola) (40.6.3) Collecting doit>=0.30.1 (from nikola) Downloading https://files.pythonhosted.org/packages/d0/4d/ebc39fdf33cc9e039b7c69f6783e929d184e5c0652a1a4543c6c5bd94c1c/doit-0.31.1-py3-none-any.whl (80kB) Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.7/site-packages (from python-dateutil>=2.6.0->nikola) (1.12.0) Requirement already satisfied: urllib3<1.25,>=1.21.1 in /opt/conda/lib/python3.7/site-packages (from requests>=2.2.0->nikola) (1.24.1) Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.7/site-packages (from requests>=2.2.0->nikola) (2019.3.9) Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/lib/python3.7/site-packages (from requests>=2.2.0->nikola) (2.8) Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/lib/python3.7/site-packages (from requests>=2.2.0->nikola) (3.0.4) Requirement already satisfied: MarkupSafe>=0.9.2 in /opt/conda/lib/python3.7/site-packages (from mako>=1.0.0->nikola) (1.1.1) Requirement already satisfied: pytz>=2015.7 in /opt/conda/lib/python3.7/site-packages (from Babel>=2.6.0->nikola) (2019.1) Collecting pyinotify; sys_platform == "linux" (from doit>=0.30.1->nikola) Downloading https://files.pythonhosted.org/packages/e3/c0/fd5b18dde17c1249658521f69598f3252f11d9d7a980c5be8619970646e1/pyinotify-0.9.6.tar.gz (60kB) Collecting cloudpickle (from doit>=0.30.1->nikola) Downloading https://files.pythonhosted.org/packages/09/f4/4a080c349c1680a2086196fcf0286a65931708156f39568ed7051e42ff6a/cloudpickle-1.2.1-py2.py3-none-any.whl Building wheels for collected packages: logbook, blinker, PyRSS2Gen, Yapsy, pyinotify Building wheel for logbook (setup.py): started Building wheel for logbook (setup.py): finished with status 'done' Stored in directory: /root/.cache/pip/wheels/a2/9f/6f/8c7a4ed6b9f6f3c98b742dbb0fd41fff3c130119c507376301 Building wheel for blinker (setup.py): started Building wheel for blinker (setup.py): finished with status 'done' Stored in directory: /root/.cache/pip/wheels/92/a0/00/8690a57883956a301d91cf4ec999cc0b258b01e3f548f86e89 Building wheel for PyRSS2Gen (setup.py): started Building wheel for PyRSS2Gen (setup.py): finished with status 'done' Stored in directory: /root/.cache/pip/wheels/08/2e/3d/50052096754e548b10de94a42182f27a1b18966dfa36c6b968 Building wheel for Yapsy (setup.py): started Building wheel for Yapsy (setup.py): finished with status 'done' Stored in directory: /root/.cache/pip/wheels/2a/2f/b1/e60d7dd26d40e3bdfc219cf75ae806298989f33992a0d32c9a Building wheel for pyinotify (setup.py): started Building wheel for pyinotify (setup.py): finished with status 'done' Stored in directory: /root/.cache/pip/wheels/e0/62/fe/a68c26dff6ed01a86d8a5aa8e01d7687a5a29c7d765af383cb Successfully built logbook blinker PyRSS2Gen Yapsy pyinotify Installing collected packages: Markdown, logbook, natsort, unidecode, blinker, PyRSS2Gen, Pillow, Babel, piexif, Yapsy, pyinotify, cloudpickle, doit, nikola Successfully installed Babel-2.7.0 Markdown-2.6.11 Pillow-6.1.0 PyRSS2Gen-1.1 Yapsy-1.12.2 blinker-1.4 cloudpickle-1.2.1 doit-0.31.1 logbook-1.4.3 natsort-6.0.0 nikola-8.0.2 piexif-1.1.3 pyinotify-0.9.6 unidecode-1.1.1 WARNING: You are using pip version 19.1.1, however version 19.2.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. Removing intermediate container b2bf143038ba ---> d4d891f28f54 Step 5/5 : USER penguin ---> Running in 0a7f94f5cb21 Removing intermediate container 0a7f94f5cb21 ---> bc3957f6a589 Successfully built bc3957f6a589 Successfully tagged nyk-nikola-img:latest 04:33:09 in notebooks on master [!?] on 🐳 v18.06.1 ➜ docker-compose up -d Creating network "notebooks_default" with the default driver Creating nyk-notebooks ... done 04:33:15 in notebooks on master [!?] on 🐳 v18.06.1 took 3s ➜ docker-compose ps Name Command State Ports ------------------------------------------------------------------------------------------------------- nyk-notebooks /usr/bin/tini -- jupyter n ... Up 0.0.0.0:6005->8000/tcp, 0.0.0.0:4001->8888/tcp
今回は単純にライブラリひとつだけでしたが root 権限になれるのでなんでもインストールできますから、無限に拡張は可能です。(もしかすると特殊なことをする場合共通部分のコードがじゃまするかもしれませんがその時はそもそも共通の Image を使わないという判断が適切なのかなと思います)
課題
めっちゃよさ気に書きましたが、この構成の課題もあります。
Image のテストはない
これはクリティカルな問題です。簡単にやるならばインストールしたライブラリが import できてあるクラスなり関数が呼び出せることを確認する、とかが考えられます。実際 CPU の Image はそれで OK だろうなと思っていて、まだ実装が出来ない(さぼってる)だけです。
しかしそれでも gpu の Image は実行環境に GPU がないとそもそも Image を gpu が使える状態で run 出来ないので Gitlab CI 上ではテストが出来ないので困ったなあという状況です。案がほしい…
回線が細いとしんどい
自分で build をしない分, docker pull することになるため回線が細いとひじょーに辛いことになります。僕は東京に出張にいくことがありリモートで作業することがありますが、家で事前に pull するのを忘れて出かけると pull が無限に終わらなくてかなり辛いです。
このあたりは Image を multi に切り分けるとかでキャッシュを上手く使えるように設計したほうがいいんでしょうが、まあ動くしなあというのでサボっています。
まとめ
Gitlab CI / Registry を使うことで自分の使いたいライブラリ郡をバージョンを管理しながら運用する方法を紹介しました。
とはいえ自分はインフラの専門家ではないですし、もっとこうすれば便利だよ!とか沢山あると思います。 その時はお手数ですが、一言お声がけいただけると大変うれしいです。;)
良き分析ライフを。