CTO室の岩本 (iwamot) です。
この記事では、Dockerイメージの軽量化事例をご紹介します。
ざっくりまとめると、下記の手順で軽量化を進めました。
- diveを使ってレイヤを分析した
- ベースイメージをpythonからpython:slimに変えた
- 無駄なファイルを除外した
まず、背景から説明します。
背景
弊社では、自社開発のPythonアプリケーションをDockerイメージとしてビルドし、電力使用量の予測に使っています。
ここ1年、AWS Lambdaで運用していたのですが、下記の問題があるため、Amazon ECSおよびAWS Fargateへの移行を決めました。
- Lambda側のサービスエラーが稀に起こる
- コールドスタートが稀に起こる(「プロビジョニングされた同時実行数」は設定しているが、必要な数を完全には予測できない)
移行を進めていて気になったのが、イメージの大きさでした。似たようなRuby on Railsアプリケーションより、約440MBも大きい状況です。
- Pythonアプリケーション:771.31MB
- Ruby on Railsアプリケーション:332.54MB(別件でECS/Fargateに移行したもの)
これを何らかの手段で軽量化できれば、下記の効果が得られるはずです。
- Dockerレジストリの利用料金が減る
- タスクの起動が早くなる(ECS/Fargateはタスクの実行時にイメージ全体をpullする)
そのため、軽量化に取り組むことにしました。
手順
1. diveを使ってレイヤを分析した
軽量化の方針を決めるのに有用なのが、diveというツールです。
diveを使うと下記の点が手早く把握できます。
- どのレイヤでどんなファイルが追加されたか
- 無駄なファイルが含まれていないか
対象のDockerイメージをdiveで分析したところ、下記の課題が把握できました。
- ベースイメージのpython:3.10.10-bullseye自体が大きい
- 機械学習モデルの訓練にのみ必要なファイルが、最終イメージに含まれている
2. ベースイメージをpythonからpython:slimに変えた
ベースイメージの大きさについては、もし軽量版のpython:3.10.10-slim-bullseyeに変えられれば、それだけで300MB弱の軽量化が見込めそうでした。
しかし軽量版には、dbm.ndbmのインポートでエラーになる問題がありました。対象のアプリケーションはdbm.ndbmに依存しているため、このままでは変えられません。
そこで報告したのが、下記のissueです。
報告から数日後、ありがたいことに修正してもらえ、ベースイメージを軽量版に変えることができました。
3. 無駄なファイルを除外した
機械学習モデルの訓練用ファイルが最終イメージに含まれていた点は、マルチステージビルドを活用できていないのが原因でした。
trainingステージを追加し、訓練が終わったら使用済みファイルを削除することで、最終イメージから除外しました。
効果
軽量化により得られた効果は、下記の通りです。
- イメージサイズ:58.2%の削減 (771.31MB → 322.46MB)
- Dockerレジストリの利用料金:58.2%の削減(Amazon ECRのストレージとデータ転送量が単純に減ったと仮定)
- ECSタスクの起動時間:約20秒の短縮(タスクのステータスが「PENDING」から「ACTIVATING」に移るまでの実測値)
- CodeBuildのビルド時間:約30秒の短縮(実測値)
ビルド時間の短縮は意図していませんでしたが、効果がありました。
まとめ
以上、Dockerイメージの軽量化事例をご紹介しました。
特にお伝えしたかったのは、下記の点です。
- イメージの軽量化には、いろんなメリットがある
- 軽量化にはdiveが便利
- OSSの妙な挙動を見つけたら、積極的に報告しよう
今後は、タスクの起動時間をさらに短くできないか、zstd圧縮を試そうと思っています。