【Finch版】distrolessイメージを使ってイメージサイズを改善する

## はじめに

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

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

[distroless_docker_image GitHub](https://github.com/ymd65536/distroless_docker_image)

## 環境

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

- MacBook
  - Apple Sillicon M1
  - Sonoma 14.0
- Python
  - Python 3.9

※Python3.9と記載していますが、dockerイメージにおけるPythonです。

なお、Finchのセットアップについては[ココ](https://qiita.com/ymd65536/items/8289e1e3159c4afccbe9)を参照してください。

## 既存のコンテナイメージ

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

```dockerfile
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でインストールするパッケージは以下のとおりです。

```txt: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をインストールしています。
イメージをビルドします。

## セットアップ

まずはVMを初期化します。

```bash
finch vm init
```

※すでに初期化している場合は下記のような実行結果が表示されます。

```text
FATA[0000] the instance "finch" is already running
```

## python:3.9-alpineイメージをビルドする

では、Finchでイメージをビルドしていきましょう。以下のコマンドを実行します。

```bash
finch build -t test_alpine:1 .
```

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

```bash
finch images
```

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

```text
REPOSITORY     TAG    IMAGE ID        CREATED          PLATFORM       SIZE         BLOB SIZE
test_alpine    1      d77ee33a7032    3 seconds ago    linux/arm64    337.2 MiB    104.2 MiB
```

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

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

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

```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`をインストールしています。

```txt: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
```

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

```bash
finch build -t test_distroless:1 .
```

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

```bash
finch images
```

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

```text
test_alpine        1      d77ee33a7032    About a minute ago    linux/arm64    337.2 MiB    104.2 MiB
test_distroless    1      b2538550ade5    6 seconds ago         linux/arm64    147.1 MiB    41.3 MiB
```

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

※dockerの時と比較すると10MB程度くらいですが、若干大きい気がします。

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

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

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

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

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

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

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

- ビルドキャッシュから必要なものをコピーすることでビルドの高速化
  - パッケージインストール時に発生するダウンロード時間をより短くできる

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

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

また、昨今においてはさまざまなアーキテクチャで開発を行うことが多くなってきているため
ビルドイメージを取得する際にアーキテクチャによってビルドイメージは異なることがあります。
ビルドイメージの構築はCI/CDなどを構築してクラウド上でやるのが良いでしょう。

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

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

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

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

## まとめ

今回は既存のコンテナイメージをdistrolessイメージを使って改善しました。
distrolessイメージを使うことでイメージサイズを大幅に削減できるため、使えるところでは使っていきたいです。

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