pythonのバッチ用のイメージを作りたくなったのでサイズがどれくらいになるか調べてみた

これはかなり個人的なメモ。

コンテナ経由でpythonで作ったバッチを実行しようと思った。そしてイメージのサイズがどれくらいかを大まかに知りたくなった。そんなわけで調べてみた。

サイズを極限まで絞りたいと言う気持ちはなかったのでdistrolessなどは省略。素直にDebianベースのイメージを選んだ。

作成する環境のメモ

baseとなるイメージは python:3.8-slim にしてみた。

$ docker images python:3.8-slim
REPOSITORY   TAG        IMAGE ID       CREATED      SIZE
python       3.8-slim   13172ea67a56   7 days ago   118MB

これに以下のパッケージを追加しただけの状態。

  • pandas
  • boto3

どういう物を作りたいか雰囲気は察せられそう。

imageのサイズ

先に作ったimageの概略を書いておく。以下のようなサイズになっていた。

REPOSITORY TAG IMAGE ID CREATED SIZE
foo 0.1.0 813317bd2068 5 days ago 291MB
foo 0.0.0 883eb7b407c1 5 days ago 320MB

foo 0.0.0が何も考えずに作ったシンプルなイメージ。foo 0.1.0 がマルチステージビルドで頑張ったイメージ

0.0.0

最もシンプルなDockerfileを考えてみる。何も考えずに書いた感じ。

Dockerfile

FROM python:3.8-slim

RUN python3 -m pip install pandas boto3
CMD ["python3"]

dockerでbuildしてみる。

docker build -t foo:0.0.1 .

0.1.0

0.1.0はマルチステージビルドで頑張ったもの。以下の記事の内容を省略したもの。

Dockerfile.multi

FROM python:3.8-slim as builder

WORKDIR /opt/app

COPY requirements.lock /opt/app
RUN python3 -m pip install -r requirements.lock

FROM python:3.8-slim as runner

COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
#COPY --from=builder /usr/local/bin/boto /usr/local/bin/boto
CMD ["python3"]

(コメントしているコピーの行は何かconsole_scriptsなどでコマンドをインストールしたときのためのもの) (今見るとWORKDIRを指定する意味がこのDockerfileでは一切無い)

その他細々と思ったことがあるが省略1

こちらもdockerでビルド

$ docker build -t 0.1.0 -f Dockerfile.multi

requirements.lock

boto3==1.17.12
botocore==1.20.12
jmespath==0.10.0
numpy==1.20.1
pandas==1.2.2
python-dateutil==2.8.1
pytz==2021.1
s3transfer==0.3.4
six==1.15.0
urllib3==1.26.3

どのレイヤーでどれだけ使われているかを見てみる

docker history でどのレイヤーがどれだけ使っているか分かる。ちなみに、--no-trunc をつけると省略される部分の全文が見れる。foo:0.0.0の方で見ていると、pipでインストールした時点で200Mb程度使われるようだ。それしかしていないのでそれはそう。

$ docker history foo:0.0.0 --format '{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.CreatedBy}}'
883eb7b407c1    5 days ago      0B      /bin/sh -c #(nop)  CMD ["python3"]
faca00f27605    5 days ago      202MB   /bin/sh -c python3 -m pip install boto3 pand…

# ここからはbaseのimageのhistory
13172ea67a56    7 days ago      0B      /bin/sh -c #(nop)  CMD ["python3"]
<missing>       7 days ago      9.19MB  /bin/sh -c set -ex;   savedAptMark="$(apt-ma…
<missing>       7 days ago      0B      /bin/sh -c #(nop)  ENV PYTHON_GET_PIP_SHA256…
<missing>       7 days ago      0B      /bin/sh -c #(nop)  ENV PYTHON_GET_PIP_URL=ht…
<missing>       7 days ago      0B      /bin/sh -c #(nop)  ENV PYTHON_PIP_VERSION=21…
<missing>       7 days ago      32B     /bin/sh -c cd /usr/local/bin  && ln -s idle3…
<missing>       7 days ago      32.4MB  /bin/sh -c set -ex   && savedAptMark="$(apt-…
<missing>       7 days ago      0B      /bin/sh -c #(nop)  ENV PYTHON_VERSION=3.8.8
<missing>       2 weeks ago     0B      /bin/sh -c #(nop)  ENV GPG_KEY=E3FF2839C048B…
<missing>       2 weeks ago     7.06MB  /bin/sh -c set -eux;  apt-get update;  apt-g…
<missing>       2 weeks ago     0B      /bin/sh -c #(nop)  ENV LANG=C.UTF-8
<missing>       2 weeks ago     0B      /bin/sh -c #(nop)  ENV PATH=/usr/local/bin:/…
<missing>       2 weeks ago     0B      /bin/sh -c #(nop)  CMD ["bash"]
<missing>       2 weeks ago     69.2MB  /bin/sh -c #(nop) ADD file:d5c41bfaf15180481…

マルチステージビルドをした方の0.1.0も、表示としてはほぼほぼ同様の形になる。こちらはCOPYの行が主。諸々のインストールなどはbuilderの方で動いているので。

$ docker history foo:0.1.0 --format '{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.CreatedBy}}'
813317bd2068    5 days ago      0B      /bin/sh -c #(nop)  CMD ["python3"]
20e64f9d4a80    5 days ago      173MB   /bin/sh -c #(nop) COPY dir:181eb1f8c5a0da2a6…

# base imageの方は省略
13172ea67a56    7 days ago      0B      /bin/sh -c #(nop)  CMD ["python3"]
...

ほぼpip installでイメージのサイズが倍に

わかったのは、pandasとboto3を入れた瞬間にほぼbase imageの倍のサイズになるということ。alpine2やdistrolessを検討する意味はほぼ無い。

考えてみれば、site-packages以下をduなどで覗いてみれば分かることではあった。すべての依存を見ていないが、botocoreとpandasで100Mを超える。

$ du -sh  ~/my/lib/python3.8/site-packages/boto
 10M    $VIRTUAL_ENV/lib/python3.8/site-packages/boto
$ du -sh  ~/my/lib/python3.8/site-packages/botocore
 41M    $VIRTUAL_ENV/lib/python3.8/site-packages/botocore
$ du -sh  ~/my/lib/python3.8/site-packages/boto3
1.3M    $VIRTUAL_ENV/lib/python3.8/site-packages/boto3
$ du -sh  ~/my/lib/python3.8/site-packages/pandas
 63M    $VIRTUAL_ENV/lib/python3.8/site-packages/pandas

追記

pipだけが差分なら pip install --no-cache-dir でインストールすれば十分では?3

0.0.1がそれ(ioknife4は表示を見やすくするためだけのものなので忘れてしまっても良い)。

$ docker images | ioknife rest | ggrep -P 'foo'
REPOSITORY   TAG        IMAGE ID       CREATED         SIZE
foo          0.0.1      de0f50ba0477   9 seconds ago   286MB
foo          0.1.0      813317bd2068   5 days ago      291MB
foo          0.0.0      883eb7b407c1   5 days ago      320MB

# ここでもう一度base image
$ docker images python:3.8-slim
REPOSITORY   TAG        IMAGE ID       CREATED      SIZE
python       3.8-slim   13172ea67a56   7 days ago   118MB

はい。

補足

:warning: tzの設定やaptのupdateとか諸々やれていないので、この記事のDockerfileをそのまま使うことはオススメしない。

参考

このあたりの情報がとても助かった。

公式

記事では使っていないけれど、実際にDockerfileを書くときには便利。

gist


  1. 個人的には常にWORKDIRを指定したい。FHS的には /opt/<app> に置くのが正しい気がするが、一つしか使わないし常に/opt/appになっている。いっその事 /app で良いような気もしないでもない。

  2. pythonにおいてはalpineはあまり推奨されない

  3. この記事を書いてから、 https://hub.docker.com/_/python のHow to use this imageに書いてあったことに気づいた

  4. head -n 1の逆のイメージ。ただし捨てるのではなく標準エラー出力に出力する。ヘッダー付きのcsvみたいな出力を見るときに便利。