モチベーション
Docker image の容量を少しでも小さくするために,ビルド用イメージとメインのイメージを分けることがあります.特に,Docker 17.05以降では,multi-stage buildsがサポートされ取り回しがよくなりました.
## Build pytorch. ##
FROM python:3.6.3 AS build-env
# Install dependencies.
RUN pip install numpy pyyaml mkl setuptools cmake cffi
# Disable GPU.
ENV NO_CUDA 1
# Download and build.
RUN wget https://github.com/pytorch/pytorch/archive/v0.2.0.tar.gz && \
tar -zxvf v0.2.0.tar.gz && \
cd pytorch-0.2.0 && \
python setup.py install
## Build main image. ##
FROM python:3.6.3
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies.
RUN pip --no-cache-dir install numpy mkl
# Install pytorch.
RUN mkdir /usr/local/lib/python3.6/site-packages/torch \
/usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info
COPY --from=build-env /usr/local/lib/python3.6/site-packages/torch /usr/local/lib/python3.6/site-packages/torch
COPY --from=build-env /usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info /usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info
$ docker build -t pytorch .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
pytorch latest a47d831b6b81 7 seconds ago 1.52GB
<none> <none> 775d7f035616 About a minute ago 2.14GB
一方で, multi-stage builds で生成された中間イメージは docker image prune
で削除されてしまったり, CI環境でのキャッシュの取り回しが若干面倒であったりします.そこで, multi-stage builds の中間イメージにタグ付けしたいというのがモチベーションです.
どうやるの?
COPY --from=<image> <src> <dest>
の構文を使うことで,ビルド用イメージとメインのイメージをつなぐようにします.
まず,ビルド用イメージの Dockerfile
を用意します.
FROM python:3.6.3
# Install dependencies.
RUN pip install numpy pyyaml mkl setuptools cmake cffi
# Disable GPU.
ENV NO_CUDA 1
# Download and build.
RUN wget https://github.com/pytorch/pytorch/archive/v0.2.0.tar.gz && \
tar -zxvf v0.2.0.tar.gz && \
cd pytorch-0.2.0 && \
python setup.py install
続いて,メインイメージの Dockerfile
を用意します.
FROM python:3.6.3
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies.
RUN pip --no-cache-dir install numpy mkl
# Install pytorch.
RUN mkdir /usr/local/lib/python3.6/site-packages/torch \
/usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info
COPY --from=pytorch_build:local /usr/local/lib/python3.6/site-packages/torch /usr/local/lib/python3.6/site-packages/torch
COPY --from=pytorch_build:local /usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info /usr/local/lib/python3.6/site-packages/torch-0.2.0-py3.6.egg-info
この方法だと,2つの Dockerfile
に分かれてしまうため,最後にビルド手順を記述する Makefile
を用意します.
all: main
pytorch_build: Dockerfile.build
docker build -f Dockerfile.build -t pytorch_build:local --cache-from=pytorch_build:local .
main: Dockerfile pytorch_build
docker build -f Dockerfile -t pytorch --cache-from=pytorch .
ビルドすると,
$ make
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
pytorch latest da56568f200a 20 seconds ago 1.52GB
pytorch_build local 587212a81a2f 22 seconds ago 2.14GB
中間イメージにもちゃんとタグが付いていることが分かると思います.また,mnist のサンプルもちゃんと動きます.
$ docker run -it --rm pytorch bash
root@ba9f46714e02:/# git clone https://github.com/pytorch/examples pytorch-examples
Cloning into 'pytorch-examples'...
remote: Counting objects: 1484, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 1484 (delta 1), reused 4 (delta 0), pack-reused 1478
Receiving objects: 100% (1484/1484), 29.96 MiB | 249.00 KiB/s, done.
Resolving deltas: 100% (783/783), done.
Checking connectivity... done.
root@ba9f46714e02:/# cd pytorch-examples/mnist
root@ba9f46714e02:/pytorch-examples/mnist# pip install -r requirements.txt
Requirement already satisfied: torch in /usr/local/lib/python3.6/site-packages (from -r requirements.txt (line 1))
Collecting torchvision (from -r requirements.txt (line 2))
Downloading torchvision-0.1.9-py2.py3-none-any.whl (43kB)
100% |████████████████████████████████| 51kB 797kB/s
Collecting pyyaml (from torch->-r requirements.txt (line 1))
Downloading PyYAML-3.12.tar.gz (253kB)
100% |████████████████████████████████| 256kB 2.0MB/s
Requirement already satisfied: numpy in /usr/local/lib/python3.6/site-packages (from torch->-r requirements.txt (line 1))
Collecting six (from torchvision->-r requirements.txt (line 2))
Downloading six-1.11.0-py2.py3-none-any.whl
Collecting pillow (from torchvision->-r requirements.txt (line 2))
Downloading Pillow-4.3.0-cp36-cp36m-manylinux1_x86_64.whl (5.8MB)
100% |████████████████████████████████| 5.8MB 253kB/s
Collecting olefile (from pillow->torchvision->-r requirements.txt (line 2))
Downloading olefile-0.44.zip (74kB)
100% |████████████████████████████████| 81kB 7.0MB/s
Building wheels for collected packages: pyyaml, olefile
Running setup.py bdist_wheel for pyyaml ... done
Stored in directory: /root/.cache/pip/wheels/2c/f7/79/13f3a12cd723892437c0cfbde1230ab4d82947ff7b3839a4fc
Running setup.py bdist_wheel for olefile ... done
Stored in directory: /root/.cache/pip/wheels/20/58/49/cc7bd00345397059149a10b0259ef38b867935ea2ecff99a9b
Successfully built pyyaml olefile
Installing collected packages: six, olefile, pillow, torchvision, pyyaml
Successfully installed olefile-0.44 pillow-4.3.0 pyyaml-3.12 six-1.11.0 torchvision-0.1.9
root@ba9f46714e02:/pytorch-examples/mnist# python main.py
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
Processing...
Done!
Train Epoch: 1 [0/60000 (0%)] Loss: 2.316824
Train Epoch: 1 [640/60000 (1%)] Loss: 2.311471
Train Epoch: 1 [1280/60000 (2%)] Loss: 2.298839
Train Epoch: 1 [1920/60000 (3%)] Loss: 2.317593
Train Epoch: 1 [2560/60000 (4%)] Loss: 2.282021
Train Epoch: 1 [3200/60000 (5%)] Loss: 2.272163
・・・
Train Epoch: 10 [56960/60000 (95%)] Loss: 0.142048
Train Epoch: 10 [57600/60000 (96%)] Loss: 0.199800
Train Epoch: 10 [58240/60000 (97%)] Loss: 0.102195
Train Epoch: 10 [58880/60000 (98%)] Loss: 0.305267
Train Epoch: 10 [59520/60000 (99%)] Loss: 0.162137
Test set: Average loss: 0.0543, Accuracy: 9822/10000 (98%)
まとめ
COPY --from=<image> <src> <dest>
の構文を使うことで中間イメージにタグ付けすることが出来ました.しかし,1つの Dockerfile
では完結できないあたり,コレジャナイ感もあります.メリットとしては,ローカルを介することがないため, Docker に閉じた世界でビルドが完結しローカル環境でもCI環境でも同様に扱うことが出来る点が挙げられるかと思います.
COPY --from=<image> <src> <dest>
の構文自体あまり知られていないようにも思いますので,もしかしたら,既存のイメージから必要なパッケージのみを取り出すなどの用途に利用できるかも分かりません.
なお,本家では,LABEL
を使う方法が提案されていたりします.しかし,この方法だとCI環境でのラベルのライフサイクルの管理がやや面倒です. multi-stage builds で適切にキャッシュを扱えるような機能がリリースされることを待っています.