Dockerのmulti stage buildsを使ってPythonモジュールをインストールしてみる
はじめに
データアナリティクス事業本部のkobayashiです。
最近Fargateでバッチ処理を行っていたのですが、タスクを実行してから終了するまでが想定よりも時間がかかってしまいその原因を調べました。 するとタスクを実行してからPythonスクリプトで処理を開始するまでの時間が100秒近くかかっていたことが原因だと判明しましたので、ECRにあるイメージのサイズを確認すると750MBとというとても巨大なイメージサイズになってしまっていました。
Fargate内のバッチ処理はPythonで行っているのですがその中で必要なモジュールが複数あり、イメージを作成する際にそれをインストールしていたのですが、その作業でイメージが肥大化していました。
そこでECRにpushする最終的なイメージを軽量化するために色々調べているとDockerのmulti-stage buildsという方法があることがわかり、実践したところうまくイメージサイズを280MBまで削ることができ結果タスクを実行してからPythonスクリプトで処理を開始するまでの時間を100秒から50秒にまで半分に減らすことができました。 その内容をまとめます。
環境
- Docker 19.03.1
- Dockerベースイメージ python:3.7.6-slim
Docker multi-stage buildsとは
Dockerのmulti-stage buildsは17.05以降で利用できるビルド方法で、一つのDockerfileの中でビルド用イメージの作成とデプロイ用イメージを作成できます。ビルド用のイメージ中でプログラムをコンパイルし、実行ファイルだけをデプロイ用のイメージに取り込むことでデプロイ用のイメージサイズを減らすことができます。
Multi-stage builds are a new feature requiring Docker 17.05 or higher on the daemon and client. Multistage builds are useful to anyone who has struggled to optimize Dockerfiles while keeping them easy to read and maintain.
Use multi-stage builds | Docker Documentation
multi-stage buildsを使う場合と使わない場合の比較
multi-stage buildsの有用性を確認するために2つのパターンでイメージを作成してみます。
multi-stage buildsを使わないパターン
Pythonでインストールするモジュールは以下のモジュールになります。
awscli boto3 pandas pyodbc chardet openpyxl statistics scipy
これらのモジュールをインストールしたイメージを作成するために以下のようなDockerfile
ファイルを作成します。
FROM python:3.7.6-slim COPY requirements.txt /root/ RUN apt-get update \ && apt-get install unixodbc -y \ && apt-get install unixodbc-dev -y \ && apt-get install tdsodbc -y \ && apt-get install python-dateutil -y RUN pip3 install -r /root/requirements.txt
pyodbcモジュールを使うためapt-get
でいくつかパッケージをインストールします。
Dockerイメージの内容
以下のコマンドでビルドします。
$ docker build -t python_siglestage .
docker history
でイメージの履歴を見てみると最後のイメージレイヤでPythonモジュールをインストールしていますが、このイメージのサイズは353MBとなっています。
$ docker history python_siglestage IMAGE CREATED CREATED BY SIZE bc2700458206 3 minutes ago /bin/sh -c pip3 install -r /root/requirement… 353MB 42771eca050e 3 minutes ago /bin/sh -c apt-get update && apt-get instal… 271MB ... 2 weeks ago /bin/sh -c apt-get update && apt-get install… 7.03MB 2 weeks ago /bin/sh -c #(nop) ADD file:e5a364615e0f69616… 69.2MB
multi-stage buildsを使ったパターン
ではmulti-stage builds
を使ってイメージをビルドしてみます。
インストールするモジュールは先程と同一になります。
以下がmulti-stage builds
を使った場合のDockerfile
ファイルになります。
内容を説明すると、# build
から# deploy
前までがビルド用のイメージを作成している部分になります。
先程のmulti-stage builds
を使わないパターンのビルドとほぼ同じですが、唯一異なるのがFROM python:3.7.6-slim as build-stage
の部分で、ビルドステージにas
でbuild-stage
の名前を付けています。
# deploy
以降がデプロイ用のイメージ作成している部分になリます。
ここで行っていることは前段で作成したビルド用イメージbuild-stage
内からジョブを動かすのに最低限必要な実行ファイルをデプロイ用のイメージにcopyしています。
# build FROM python:3.7.6-slim as build-stage COPY requirements.txt /root/ RUN apt-get update \ && apt-get install unixodbc -y \ && apt-get install unixodbc-dev -y \ && apt-get install tdsodbc -y \ && apt-get install --reinstall build-essential -y \ && apt-get install python-dateutil -y RUN pip3 install -r /root/requirements.txt # deploy FROM python:3.7.6-slim ## ビルド用イメージ内でPythonモジュールをビルドした際のキャッシュを利用する COPY --from=build-stage /root/.cache/pip /root/.cache/pip COPY --from=build-stage /root/requirements.txt /root ## Pythonモジュールで使用するライブラリをコピー COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libodbc.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libltdl.so.7 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /lib/x86_64-linux-gnu/libkeyutils.so.1 /lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /lib/x86_64-linux-gnu/libkeyutils.so.1 /lib/x86_64-linux-gnu/ ### パッケージのアップデートとPythonモジュールのインストール RUN apt-get update RUN pip3 install -r /root/requirements.txt \ && rm -rf /root/.cache/pip
Dockerイメージの内容
先ほどと同じようにビルドしてイメージの履歴を見てみると、最後のイメージレイヤでPythonモジュールをインストールしていますが、先程は353MBだったイメージサイズが282MBと削減されています。
$ docker build -t python_multistage . $ docker history python_multistage IMAGE CREATED CREATED BY SIZE 4288fa8e0cfd About a minute ago /bin/sh -c pip3 install -r /root/requirement… 282MB ce85f0f24d2f About a minute ago /bin/sh -c apt-get update 17.4MB 79b1ddecc7a3 About a minute ago /bin/sh -c #(nop) COPY file:d8c2595223d59998… 22.4kB ... 2 weeks ago /bin/sh -c apt-get update && apt-get install… 7.03MB 2 weeks ago /bin/sh -c #(nop) ADD file:e5a364615e0f69616… 69.2MB
ECRでイメージサイズを比較
python_siglestage
とpython_multistage
をECRにpushしてイメージサイズを見てみると、イメージサイズが削減されていることがわかります。
(補足)さらにイメージサイズを削減
このブログを公開後更にイメージサイズを削減する記述を教えていただきました。
# build FROM python:3.7.6-slim as build-stage COPY requirements.txt /root/ RUN apt-get update \ && apt-get install unixodbc -y \ && apt-get install unixodbc-dev -y \ && apt-get install tdsodbc -y \ && apt-get install --reinstall build-essential -y \ && apt-get install python-dateutil -y RUN pip3 install -r /root/requirements.txt # deploy FROM python:3.7.6-slim ## ビルド用イメージ内でPythonモジュールをビルドした際のキャッシュを利用する COPY --from=build-stage /root/.cache/pip /root/.cache/pip COPY --from=build-stage /root/requirements.txt /root ## Pythonモジュールで使用するライブラリをコピー COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libodbc.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libodbcinst.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libltdl.so.7 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /lib/x86_64-linux-gnu/libkeyutils.so.1 /lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libk5crypto.so.3 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /usr/lib/x86_64-linux-gnu/libkrb5support.so.0 /usr/lib/x86_64-linux-gnu/ COPY --from=build-stage /lib/x86_64-linux-gnu/libkeyutils.so.1 /lib/x86_64-linux-gnu/ ### パッケージのアップデートとPythonモジュールのインストール RUN apt-get update RUN pip3 install -r /root/requirements.txt \ && rm -rf /root/.cache/pip #### apt-getでパッケージのインストールに使用したキャッシュの削除 RUN apt-get clean && rm -rf /var/lib/apt/lists/*
またここで作成したDockerfile
をGitHub - orisano/minid: minid is Dockerfile minifier for reducing the number of layers.を使用してminimizeしてdocker build
することで更に削減できます。その結果以下の通り更に削減できました。情報ありがとうございました。
まとめ
当たり前の話ですが、コンテナを使う場合はイメージサイズが小さければ小さいほど良いです。そのための手段としてDockerのmulti-stage buildsは非常に有効です。コンテナで使うイメージを作成する際は積極的に使っていきたいと思います。
最後まで読んで頂いてありがとうございました。