はじめに
VSCode + Python + Poetry + Docker(docker-compose)でdev-containerを作成して開発を行っていました。
Dockerを勉強し、イメージの軽量化に関する記事を読んでいると、自分が使っているコンテナイメージのサイズが気になりました。
docker images
> REPOSITORY TAG IMAGE ID CREATED SIZE
> dev-container latest a9b8e3df9087 2.31GB
2.31GB!?
サーバとしてアプリを動かしていないのにここまで大きいなんて…
というわけで勉強も兼ねて、イメージの軽量化に取り組みました。
イメージが軽量であるメリット
ストレージの節約
これは言わずもがなだと思います。
限られたリソースを有効に使うことができます。
ビルド時間の短縮
Dockerは環境を作ったり壊したりが簡単なのもいいところだと思います。
ビルド時間が長いと、この恩恵を感じづらくなりますよね。
環境
OS:Ubuntu22.04
Docker: 26.1.1
Docker Compose: v2.27.0
軽量化していく
最初の状態
version: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
restart: always
tty: true
volumes:
- type: bind
source: ./
target: /workspace/app
FROM python:3.11.9-slim
COPY pyproject.toml ./
# pipのアップグレード
RUN pip install --upgrade pip
# パッケージのアップグレード、インストール
RUN apt -y update && apt -y upgrade
RUN apt -y install libopencv-dev
# poetryのインストール、設定
RUN pip install poetry
RUN poetry config virtualenvs.create false \
&& poetry install --no-root
# コンテナの作業ディレクトリを作成
RUN mkdir -p /workspace/app
WORKDIR /workspace/app
EXPOSE 8888
この記事ではDockerfileしか触らないですが、compose.ymlも載せておきます。
今見るとかなり無駄な記述が多いと思います。
ベースイメージ変更、レイヤー数削減
# ベースイメージをslimからslim-bullseyeに変更
FROM python:3.11.9-slim-bullseye
# タイムゾーンを日本に設定
ENV TZ Asia/Tokyo
# COPY pyproject.toml .を削除
# 必要なパッケージ RUNコマンドを一つに結合
RUN apt update && \
apt -y upgrade && \
apt -y install libopencv-dev
# pipのアップグレードとpoetryのインストール
RUN pip install --upgrade pip && \
pip install poetry
# コンテナの作業ディレクトリを作成
RUN mkdir -p /workspace/app
WORKDIR /workspace/app
# 仮想環境を作らないよう設定
RUN poetry config virtualenvs.create false
EXPOSE 8888
最初に更新後のものを載せておきます。
docker images
>REPOSITORY TAG IMAGE ID CREATED SIZE
>dev-container latest 585f482be78b 1.17GB
この時点でイメージサイズは約半分になりました。
変更点はベースイメージ、タイムゾーンの追加、レイヤーの削減です。
また、pyproject.tomlのコピーとパッケージインストールも作業ディレクトリをマウントするので削除しました。
※後で調査すると、コピーやpoetry install
が7MBほどを占めていたようです。かなり無駄な記述をしていたと痛感しました。
イメージの軽量化に関わるのは、ベースイメージとレイヤーです。
ベースイメージ
ベースイメージはDocker Hubを使って決めました。
Docker Hubメリット
- イメージのサイズや脆弱性のレベルや有無を確認できる
- 公式や他の人のDockerfileを見ることができる
slim
からslim-bullseye
への変更は微々たる差ですが、slim
が付いているものとそうでないものの差には驚きます。
slim
は使用頻度の低いパッケージが除かれているため軽量になるそうです。
レイヤーの削減
レイヤーとはざっくり言うと、命令1つごとに作られる変更差分です。
Dockerfileでの命令コマンドRUN
やCOPY
が実行されるたびにレイヤーが作られます。
つまり命令コマンドが多くなるとその分、変更差分が作られてしまいます。そのためイメージサイズがどんどん大きくなる原因になります。
今回はバラバラになっていたRUN
コマンドをまとめたり、作業ディレクトリをマウントするため不要だったCOPY
コマンドの削除など行いました。
ただし、レイヤーの削減は可読性の低下といった問題が起こり得ます。
また、本来なら複数の命令(apt updateとapt upgradeなど)をまとめて実行するので、どの処理が失敗したか分かりづらくなる可能性もあります。
マルチステージビルド
# パッケージをインストールするためのイメージ
FROM python:3.11.9-slim-bullseye as builder
# 必要なパッケージ
RUN apt-get update -q
RUN apt-get -y upgrade --no-install-recommends
RUN apt-get -y install libopencv-dev
# pipのアップグレードとpoetryのインストール
RUN pip install --upgrade pip
RUN pip install poetry
# 仮想環境を作らないよう設定
RUN poetry config virtualenvs.create false
# 実際に使用するイメージ
FROM python:3.11.9-slim-bullseye as dev
# タイムゾーンを日本に設定
ENV TZ Asia/Tokyo
# builderから必要な内容をコピー
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
WORKDIR /workspace/app
EXPOSE 8888
docker images
>REPOSITORY TAG IMAGE ID CREATED SIZE
>dev-container latest 585f482be78b 188MB
一気にイメージサイズが減りました!
マルチステージビルドに伴って、レイヤーの削減をしないようにしました。
マルチステージビルドとは
Docker Engine17.05から導入された機能です。
マルチステージビルドを実行するにはFROM
行を複数回記述します。
FROM
のたびに新しいベースイメージを作成するビルドステージが開始され、一方の内容を他方にコピーすることができ、さらに最終イメージ以外を無視することができます。
1つ目のイメージで必要なパッケージのインストールやアップグレード、アプリのビルドなどを行い、使用したいイメージには使用するファイルやフォルダだけをコピーすることで、余分なレイヤーを作らずイメージを生成できます。
最後に
Dockerのコンテナイメージ軽量化には以下の3つが重要だとわかりました。
- ベースイメージ
- レイヤー削減
- マルチステージビルド
特にマルチステージビルドは余分なレイヤーを、使わないイメージに押し付けることができるので、軽量なイメージの作成にはほぼ必須かと思いました。
他にもこんなDockerfileの作り方があるなど教えていただきたいです。
間違いがあれば指摘していただけますと幸いです。