🐳

既存のコンテナイメージをdistrolessイメージを使って改善する

2024/01/14に公開

はじめに

この記事では既存のコンテナイメージをdistrolessイメージを使って改善していく記事です。
主な内容としては実践したときのメモを中心に書きます。
(忘れやすいことなど)誤りなどがあれば書き直していく予定です。

今回利用したソースコードは以下のリポジトリにあります。

https://github.com/ymd65536/distroless_docker_image

環境

今回は以下の環境で動作確認を行っています。そのほかの環境で動かす場合の参考にしてください。

  • MacBook
    • Apple Sillicon M1
    • Sonoma 14.0
  • Rancher Desktop
    • v1.7.0
  • Python
    • Python 3.9

※まずは、Rancher Desktopをインストールしてください。
※Python3.9と記載していますが、dockerイメージにおけるPythonです。

既存のコンテナイメージ

今回改善するイメージは以下です。

FROM python:3.9-alpine as builder

RUN apk add --no-cache musl-dev mysql-dev gcc
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt

CMD ["python", "main.py"]

imageを構築する過程でpipを実行しています。pipでインストールするパッケージは以下のとおりです。

requirements.txt
alembic==1.13.1
annotated-types==0.6.0
boto3==1.4.8
botocore==1.8.11
certifi==2023.11.17
charset-normalizer==3.3.2
docutils==0.14
exceptiongroup==1.2.0
functions==0.7.0
greenlet==3.0.3
idna==3.6
iniconfig==2.0.0
jmespath==0.9.3
Mako==1.3.0
MarkupSafe==2.1.3
mysqlclient==2.2.1
packaging==23.2
pluggy==1.3.0
pydantic==2.5.3
pydantic_core==2.14.6
pytest==7.4.4
python-dateutil==2.6.1
python-multipart==0.0.6
pytz==2023.3.post1
requests==2.31.0
s3transfer==0.1.12
schemas==0.7.1
six==1.11.0
SQLAlchemy==2.0.25
SQLAlchemy-Utils==0.41.1
tomli==2.0.1
typing_extensions==4.9.0
urllib3==2.1.0
xmltodict==0.13.0

なお、mysqlclientをインストールしているため、apkでmysql-devをインストールしています。
イメージをビルドします。

docker build -t test_alpine:1 .

サイズを確認するため、docker imagesを実行します。

docker images

実行結果は以下のとおりです。

REPOSITORY                           TAG          IMAGE ID       CREATED         SIZE
test_alpine                          1            374875ee50bb   8 seconds ago   333MB

サイズは333MBです。このコンテナイメージをdistrolessイメージに置き換えていきます。

distrolessイメージを使って改善していく

まずはどんな感じのdockerfileになるかを確認します。

FROM python:3.9-alpine as builder

WORKDIR /app
COPY . .
RUN pip --no-cache-dir install --upgrade pip && pip install --no-cache-dir --target site-packages -r requirements.txt

FROM gcr.io/distroless/python3-debian11:nonroot

WORKDIR /app
COPY --from=builder /app .
ENV PYTHONPATH=/app/site-packages
CMD ["./main.py"]

なお、requirements.txtは以下のとおりです。変更点としてはmysqlclientを削除して代わりにmysql-connector-python==8.2.0をインストールしています。

requirements.txt
alembic==1.13.1
annotated-types==0.6.0
boto3==1.4.8
botocore==1.8.11
certifi==2023.11.17
charset-normalizer==3.3.2
docutils==0.14
exceptiongroup==1.2.0
functions==0.7.0
greenlet==3.0.3
idna==3.6
iniconfig==2.0.0
jmespath==0.9.3
Mako==1.3.0
MarkupSafe==2.1.3
# mysqlclient==2.2.1
packaging==23.2
pluggy==1.3.0
pydantic==2.5.3
pydantic_core==2.14.6
pytest==7.4.4
python-dateutil==2.6.1
python-multipart==0.0.6
pytz==2023.3.post1
requests==2.31.0
s3transfer==0.1.12
schemas==0.7.1
six==1.11.0
SQLAlchemy==2.0.25
SQLAlchemy-Utils==0.41.1
tomli==2.0.1
typing_extensions==4.9.0
urllib3==2.1.0
xmltodict==0.13.0
mysql-connector-python==8.2.0

イメージをビルドします。

docker build -t test_distroless:1 .

サイズを確認するため、docker imagesを実行します。

docker images

実行結果は以下のとおりです。

REPOSITORY                           TAG          IMAGE ID       CREATED         SIZE
test_alpine                          1            374875ee50bb   8 seconds ago   333MB
test_distroless                      1            e6ee09fc6a8e   2 minutes ago   132MB

サイズは132MBです。distrolessイメージを使うことでほぼ同じPythonのパッケージを使いつつ、イメージサイズを大幅に削減できました。

distrolessイメージを作るときのポイント

イメージサイズを削減できたところでdistrolessイメージを作るときのポイントを確認します。
主に以下のポイントです。

  • マルチステージビルドを使う
  • その他のパッケージに依存したパッケージをインストールしないもしくは代わりを用意する

それぞれ見ていきましょう。

マルチステージビルドを使う

dockerにはマルチステージビルドという機能があります。マルチステージビルドとは複数のFROMを使ってイメージを構築する機能です。

以下のようなメリットがあります。

  • 他のイメージから取得したものをコピー
  • 保存されているビルドイメージ(ビルドキャッシュ)から必要なものをコピーすることでビルドの高速化
    • パッケージインストール時に発生するダウンロード時間をより短くできる

ただし、以下のようなデメリットもあります。

  • 特定のビルドイメージに依存する形になってしまう
  • ビルドイメージを保存するため、保存データ容量が大きくなる

また、昨今においてはさまざまなアーキテクチャで開発を行うことが多くなってきているため
ビルドイメージを取得する際にアーキテクチャによってビルドイメージが異なることがあります。

ビルドイメージの構築はCI/CDなどを構築してクラウド上でやるのが良いでしょう。

その他のパッケージに依存したパッケージをインストールしない

distrolessイメージを使うときには、その他のパッケージに依存したパッケージをインストールしないようにする必要があります。

今回の場合はmysqlclientをインストールしているためにmysql-devもインストールしていますが。
distrolessイメージを使うときにはmysqlclientをインストールしないようにする必要があります。
そのため、mysqlclientを削除してmysql-connector-pythonをインストールしています。

mysqlclientをインストールする場合は依存しているapkのパッケージをインストールする必要がありますが
distrolessイメージにはシェルとパッケージシステムが存在しないため、パッケージをインストールできません。

つまり、distrolessイメージを使うときにはその他のパッケージに依存したパッケージをインストールしないようにするかもしくは代わりとなるパッケージを用意する必要があります。

ちなみにmysqlclientをインストールしようとするとpkg-configが正常に動作せず、インストールに失敗します。

まとめ

今回は既存のコンテナイメージをdistrolessイメージを使って改善しました。
distrolessイメージを使うことでイメージサイズを大幅に削減できるため、使えるところでは使っていきたいです。
ただし、マルチステージビルドを使う必要があり、ビルドイメージをいくつか保存する必要があるため
ビルドイメージの運用には注意が必要です。

イメージをビルドする際はクラウドベースのビルド環境を構築しておきましょう。

Discussion