(追記 2022-11-05)
本記事の内容(オリジナルは2020年の7月頃)ですが、
2年以上経って久々に見返してみると我ながら色々とよろしくない点もあったように思うので、
可能な範囲で直した改善版: yamlで保存してあるconda仮想環境をDocker上で構築(multistage-build)を本記事の続きとして書いています。
本記事だとdocker内で作成しているユーザーの権限が大き過ぎたり、minicondaを使っている関係で商用利用が有償化されているAnacondaリポジトリから意図せずにパッケージを取得してしまう可能性があったりと、そのまま実行すると懸念点があるので注意してください。
Docker上でPython環境を作るときにpip
やPipenv
なども使うのだが、使うライブラリやパッケージの関係でconda
環境を使えると便利なことがある。
思った通りのconda環境をDocker上で作るための方法の自分用メモ。
1. 動機
Pythonの開発やデータ分析などでMinicondaやAnacondaを使うことがある。
(依存ライブラリも割と一緒にインストールしてくれるので自分で入れなくて良いなど環境構築が楽だったり、バイナリ互換性の問題でcondaの特定チャンネルからのインストールを推奨されるパッケージがあったりする)
その際、やっている作業毎にパッケージやバージョンを変えたかったり、環境を後で再現しやすくするなどの目的で基本はconda仮想環境を使うようにしている。
が、Python以外のところから環境設定・構築が必要になることがあったり、仮想環境と言えどもPCの環境を汚して他と干渉したりの問題が起きるなどで、結局Dockerで対応することが多いため。(で、そのDockerへの移植時に結構苦戦したため)
yamlファイルを使う理由
詳細は次章として、
- PC上の既存のconda仮想環境を容易にDocker上に移植出来る
- 逆に、Docker上で開発したconda環境を手元のAnacondaやMinicondaですぐに再現出来る
など
2. conda環境の保存と再構築
2-1. yamlファイルからのconda環境生成
conda env create -f environment.yml
を実行すると、environment.yml
に記載した情報の通りにconda環境が生成される。
例えばenvironment.yml
に対応するものをgit上などで管理しておくと、可搬性や再現性の点で便利。
※environment.yml
の中身は以降の記述を参考
※conda env create
以降のオプションの詳細ははっきり覚えていない。。。発見出来てかつ気が向いたら後で追記。ちなみにconda create
のオプションはこんな感じ。
2-2. yamlファイルの生成
対象のconda仮想環境に入って、例えば以下のようにする
conda env export > environment.yml
すると、該当のconda環境でインストールされていた全パッケージとそのバージョン(ビルド情報含む)がenvironment.yml
に書き出される。(pip freeze
するような感覚)
ちなみに、仮想環境に入らなくとも
conda env export -n <仮想環境名> > environment.yml
のようにして、明示的に書き出す仮想環境名を指定することも出来る。
また、上記の2つは完全にバージョン情報が固定されるが、そこまではせずに最低限の条件だけ指定したい場合がある。
(公式ドキュメント)を参考にして、パッケージと使うチャンネルなどを書いたyamlファイルを自分で作ることが出来る。(以下、適当な例)
name: hoge
channels:
- conda-forge
- default
dependencies:
- python==3.7.*
- pandas<1.0
- flask
- pip
- pip:
- pynvim
- 依存ライブラリなどは(conflictが起きなければ)自動でインストールされる。
- pipでインストールされるものも書ける。(
pip:
の下のところ)- 先にcondaでのインストールが実行され、最後にpipでのインストールが実行される
- condaでインストールするパッケージ内で明示的にpipをインストールしておくと無難かもしれない
- 「conda仮想環境のものでないpipを使う可能性がある」的な趣旨のwarningが出る
なお、上記の例のyamlファイルだと、
# condaでパッケージをインストール
conda create -n hoge python=3.7 pandas\<1.0 flask pip -c conda-forge
# 作った仮想環境に入る
conda activate hoge
# 最後にpipで必要なパッケージをインストール
python -m pip install pynvim
みたいな感じで環境構築するのと大体同じ。(※厳密に本当に同じかまでは未検証)
3. Docker上でconda仮想環境を作る
(方針)
-
前章を踏まえて用意してあるyamlファイルをDockerイメージ内に
COPY
などしておく - yamlファイルからの環境作成はMinicondaを利用して行う
- Miniconda自体のインストールは公式などを参照
- continiuum公式のDockerfileなども参考になる
- 上記のMinicondaはyamlファイルから仮想環境を作ること「のみ」に使い、作り終わったら元のMinicondaはキャッシュ毎全部消す(←容量の無駄なので)
- 代わりにyamlから作られた仮想環境の方を
base
にする - (もっと良いやり方があるかもしれない)
- 代わりにyamlから作られた仮想環境の方を
Dockerfile作成例(抜粋)
ディレクトリ構成:
tree .
# .
# ├── Dockerfile
# └── environment.yml
のような感じ。
docker build --tag=hoge .
または
# Dockerfile内のARG部分を制御する場合
docker build \
--tag=hoge \
--build-arg VAL1=FOO \
--build-arg VAL2=BAR \
.
のようにすると、ビルドが通ればhoge
というイメージが出来る
ARG BASE_IMAGE=ubuntu:latest
FROM ${BASE_IMAGE}
# system update & package install
RUN apt-get clean && \
apt-get -y update && \
apt-get install -y --no-install-recommends \
unzip bzip2 \
openssl libssl-dev \
curl wget \
ca-certificates \
locales \
bash \
sudo \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# ローカルuser作成
ARG USER_NAME=user
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/bash -u ${USER_UID} ${USER_NAME} && \
gpasswd -a ${USER_NAME} sudo && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers && \
chmod g+w /etc/passwd
# conda用準備
ENV CONDA_DIR=/opt/conda \
CONDA_TMP_DIR=/tmp/conda \
HOME=/home/$USER_NAME \
SHELL=/bin/bash
RUN mkdir -p $CONDA_DIR && \
mkdir -p $CONDA_TMP_DIR && \
chown $USER_NAME:$USER_UID $CONDA_DIR && \
chown $USER_NAME:$USER_UID $CONDA_TMP_DIR
# yamlファイルの取り込み
ARG CONDA_YAML="./environment.yml"
COPY $CONDA_YAML /tmp/conda_packages.yml
USER ${USER_NAME}
WORKDIR $HOME
# miniconda
ARG MINICONDA_VERSION=py37_4.8.3-Linux-x86_64
ARG MINICONDA_MD5=751786b92c00b1aeae3f017b781018df
ENV PATH=${CONDA_DIR}/bin:$PATH
RUN cd /tmp && \
wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-${MINICONDA_VERSION}.sh && \
echo "${MINICONDA_MD5} *Miniconda3-${MINICONDA_VERSION}.sh" | md5sum -c - && \
/bin/bash Miniconda3-${MINICONDA_VERSION}.sh -f -b -p $CONDA_TMP_DIR && \
rm Miniconda3-${MINICONDA_VERSION}.sh && \
$CONDA_TMP_DIR/bin/conda env create -f /tmp/conda_packages.yml -p $CONDA_DIR && \
rm -rf $HOME/.cache/* && \
rm -rf $CONDA_TMP_DIR/*
# (以下省略)
- はじめにMinicondaは
/tmp/conda/
下にインストールする- 先述の通り、
conda env create
をするためだけに使うので、後で/tmp/conda/
下をまるっと消す - インストール先は
-p
オプションで指定している
- 先述の通り、
- 所望のconda環境は
/opt/conda/
下にインストールされ、ここにPATH
を通すことで作成ユーザーにとってのデフォルトPython環境にしている- conda環境を
/opt/conda/
にするために、-p
や--prefix
オプションでインストール先を指定
- conda環境を
- イメージサイズが肥大化しないように、Minicondaのダウンロード・インストールから不要なファイル・キャッシュの削除まで1つの
RUN
の中で全てをやり切る必要
(補足)
- 使っているMinicondaは執筆時点(2020-07-24)での最新版なので、ここなどを参考に適宜アップデートする
- ユーザー作成を行っているが別に必須ではないので、そこを省けばもっとすっきりするはず
- yamlファイルにおける
prefix:
部分があればそれはインストール場所を示すが、conda env create
の-p
ないし--prefix
オプションでの指定が優先される様子 - yamlファイルに書くインストールパッケージの中に
conda
を書いておくと、/opt/conda/
にインストールされる仮想環境が新しいbase
環境になる- コンテナ使用中に追加で入れたいパッケージが出てきたときにその場で
conda install ***
を実行出来たり、その後conda env export -n base | tee environment.yml
などと叩いてインストールパッケージの再確認や新しいyamlファイルの生成が出来る
- コンテナ使用中に追加で入れたいパッケージが出てきたときにその場で
(更に細かい補足)
たまにconda env export
で生成したyamlファイルをそのままconda env create
に渡せないときがある(あった)ので、その例と対処。
-
pip
でgraphviz
をインストールさせたものをconda env export
してyamlを生成すると、python-graphviz
の名前になっている - しかし、
pip install python-graphviz
はエラーになるため、したがってこのyamlファイルをそのまま使うとインストールエラーになってしまう - そこで、例えば以下の部分を挿入してyamlファイルを少しだけ書き換えるなどした
RUN sed -i -e 's/python-graphviz/graphviz/' /tmp/conda_packages.yml
sed
でyamlファイル内のpython-graphviz
をgraphviz
に置換している
参考
conda環境の保存・構築:
https://qiita.com/nshinya/items/cb1cffabc3305c907bc5
https://qiita.com/yubessy/items/2dd43551aa8308dc7eca
Docker上でのconda環境の構築
https://hub.docker.com/r/continuumio/miniconda/dockerfile
https://github.com/jupyter/docker-stacks/tree/master (←リポジトリ内の各Dockerfileを参照)