仕事でPython/Djgnagoを使ってWEBアプリを開発し、作成したアプリはDockerコンテナにして運用しています。インストールするPythonのライブラリが150個ぐらいあるので、コンテナイメージが大きくなってアップロードやダウンロードに時間がかかっていました。
何とかコンテナイメージを軽くするために、マルチステージビルドを実施(ついでにAWS ECRのイメージスキャンも実施)し、コンテナイメージの容量(と脆弱性)がどのぐらい削減できるのかをそれぞれのパターンで検証しました。
修正前
FROM python:3.7.13-bullseye
ENV PYTHONUNBUFFERED 1
ENV PIPENV_TIMEOUT 600
# githubから直接pipインストールするため、gitインストール
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install gcc git mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6 && \
apt-get autoclean -y && apt-get clean && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
RUN groupadd -r app && useradd --no-log-init -r -g app app
USER app
COPY --chown=app:app . /home/app
WORKDIR /home/app
ENV PATH="/home/app/.local/bin:${PATH}"
RUN pip install --upgrade pip && pip install pipenv
RUN pipenv install --ignore-pipfile --deploy --system
EXPOSE 8000
CMD gunicorn config.wsgi -b 0.0.0.0:8000 --log-level=info
1999.66MB(2重要 + 369その他(詳細))
になりました。容量はほぼ2GBになり、criticalの脆弱性も2件含まれています。
軽量なベースイメージを利用
python:3.7.13-bullseye
イメージより軽量なpython:3.7.13-slim-bullseye
イメージを利用します。ちなみにslim
がついたイメージは使用頻度の低いツールやライブラリを除外した最小イメージになります。
FROM python:3.7.13-slim-bullseye
ENV PYTHONUNBUFFERED 1
ENV PIPENV_TIMEOUT 600
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install gcc git mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6 && \
apt-get autoclean -y && apt-get clean && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
RUN groupadd -r app && useradd --no-log-init -r -g app app
USER app
COPY --chown=app:app . /home/app
WORKDIR /home/app
ENV PATH="/home/app/.local/bin:${PATH}"
RUN pip install --upgrade pip && pip install pipenv
RUN pipenv install --ignore-pipfile --deploy --system
EXPOSE 8000
CMD gunicorn config.wsgi -b 0.0.0.0:8000 --log-level=info
1805.31MB(1重要 + 236その他(詳細))
となり、容量を200MB、criticalの脆弱性も1件削減できました。
マルチステージビルドを利用
マルチステージビルドでコンテナイメージを作成します。pipインストールしたライブラリを実行用のコンテナイメージにコピーしています。
FROM python:3.7.13-bullseye as builder
ENV PYTHONUNBUFFERED 1
ENV PIPENV_TIMEOUT 600
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install gcc git mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6
COPY Pipfile Pipfile.lock /app/
WORKDIR /app
RUN pip install --upgrade pip && pip install pipenv
RUN pipenv install --ignore-pipfile --deploy --system
FROM python:3.7.13-slim-bullseye as runner
ENV PYTHONUNBUFFERED 1
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6 && \
apt-get autoclean -y && apt-get clean && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
RUN groupadd -r app && useradd --no-log-init -r -g app app
USER app
COPY --chown=app:app . /home/app
WORKDIR /home/app
COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
EXPOSE 8000
CMD gunicorn config.wsgi -b 0.0.0.0:8000 --log-level=info
947.26MB(1高 + 106その他(詳細))
となり、容量は当初の半分以下1GBを割り、criticalの脆弱性もなくすことができました。
不要なパッケージをインストールしない
apt-getに--no-install-recommends
をつけて実行します。
Ubuntuは8.10から、インストールしようとしているソフトウェアが依存しているパッケージだけでなく「推奨パッケージ」も一緒にインストールするようになりました。これは大抵の場合はユーザーにとって便利ではあるものの、たまに必要以上に多くのパッケージが一緒にインストールされてしまうこともあります。
aptコマンドに--no-install-recommendsオプションを渡すことで、推奨パッケージのインストールを抑制できます。
FROM python:3.7.13-bullseye as builder
ENV PYTHONUNBUFFERED 1
ENV PIPENV_TIMEOUT 600
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install gcc git mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6
COPY Pipfile Pipfile.lock /app/
WORKDIR /app
RUN pip install --upgrade pip && pip install pipenv
RUN pipenv install --ignore-pipfile --deploy --system
FROM python:3.7.13-slim-bullseye as runner
ENV PYTHONUNBUFFERED 1
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get -y dist-upgrade && \
apt-get -y install --no-install-recommends mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 swig fonts-vlgothic poppler-utils libglib2.0-0 libsm6 libxrender1 libxext6 && \
apt-get autoclean -y && apt-get clean && rm -rf /var/cache/apt/* /var/lib/apt/lists/*
RUN groupadd -r app && useradd --no-log-init -r -g app app
USER app
COPY --chown=app:app . /home/app
WORKDIR /home/app
COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
EXPOSE 8000
CMD gunicorn config.wsgi -b 0.0.0.0:8000 --log-level=info
927.14MB(1 高 + 104 その他 (詳細))
となり、ダメ押しでさらに20MBほど削減できました。
最後に
上記以外に軽量化のテクニックは様々あると思いますが、マルチステージビルドをするだけで一気に半分くらい容量を削減できるので、コンテナイメージを軽量にしたい場合、まず最初に実践すべき方法と思いました。
参考
Dockerfileを書くためのベストプラクティス【参考訳】v18.09
仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編