Dockerを利用する上で、公式の Dockerfileベストプラクティス が触れている通り、Dockerイメージのデータサイズを必要最小限にすることが大切です。
例えば、Dockerイメージを取得してコンテナ起動をするとして、取得するイメージのデータサイズが大きければ大きいほど、コンテナ起動の前段階にあるイメージ取得の時間的オーバーヘッドも大きくなってしまうことが想像できます。
今回紹介する『dive』は、Dockerイメージを構成するレイヤーと、レイヤーに含まれるファイルを閲覧することが可能な解析ツールです。
https://github.com/wagoodman/dive
では、dive
を用いてDockerイメージのレイヤーを探索し、不要なファイルなどを特定してみましょう。
dive
の実行
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive jenkins/jenkins:lts
まず、dive
をコンテナから実行します。
dive
をコンテナ実行する上での注意点ですが、dive
がローカルのDockerデーモンと通信する関係上、オプション-v
でローカルのDockerデーモンのソケットをコンテナ側にマウントしてあげる必要があります。
dive
の実行方法は2種類あり、既存のDockerイメージに対しての実行と、DockerfileからビルドされるDockerイメージに対しての実行があります。
今回は前者の方法で、CI/CDサーバJenkinsの公式Dockerイメージのレイヤーを探索してみます。
画面と操作方法
dive
はテキストベースUIになっており、左セクションにDockerfileコマンドに対応したレイヤーの一覧ビュー、右セクションにレイヤーが含むファイルの一覧ビューが表示されます。
ここからはデフォルトのショートカット設定で説明を進めますが、Tab
キーでレイヤーとファイルの一覧ビューの間で操作フォーカスの切り替え、矢印キーとSpace
キーで一覧ビュー内の選択カーソルの移動やディレクトリの伸展・折りたたみができます。
以下が主だったショートカットの抜粋です。
ショートカット | 説明 |
---|---|
Ctrl + C |
dive を終了する |
Ctrl + L |
選択中レイヤーのファイル一覧 |
Ctrl + A |
最初のレイヤーから選択中レイヤーまで集約したファイル一覧 |
Ctrl + F |
ファイル名のフィルター |
Ctrl + U |
変更のないファイルの表示/非表示 |
Ctrl + B |
ファイル属性の表示/非表示 |
https://github.com/wagoodman/dive#keybindings
個人的には、変更のないファイルを非表示にして、Ctrl + L
で選択中のレイヤーと1つ前のレイヤーの間におけるファイル変更差分を探索するのがおすすめです。
Dockerイメージにおける無駄の発見
私見では、『Dockerイメージにおける無駄』の定義は以下になると思っています。
- いずれのレイヤーにも存在すべきでないファイル
- 最初から最後のレイヤーに至るまで特定ファイルで何度も繰り返される作成・変更・削除アクション
- 項番1〜2の結果として存在するレイヤー
項目1は、Ctrl + L
で選択中のレイヤーと1つ前のレイヤーの間におけるファイル変更差分を探索することで発見できます。
ただ、『存在すべきでないファイル』の定義はdive
が探索対象にしているDockerイメージの用途次第なので、どのDockerイメージにも通用する最適な特定方法を挙げるのが難しい。
とはいえ、経験論的な代表アプローチはいくつか存在していて、以下が無駄なファイルと捉えることができます:
- システムやミドルウェアのログファイルが格納される
/var/log/
以下のファイル - ローケールや
man
ドキュメントなど『機械』でなく『ヒト』のサポートを目的としたファイル - Dockerイメージを最終成果物とした場合、そこに追加のコンテンツを加えうるパッケージマネージャ(e.g.
apt
、dnf
、npm
)やビルドツール(e.g.gcc
、make
、go
)
項目2〜3は、dive
が提供する『イメージの効率推定結果』から判断できます。以下が公式ドキュメントにおける説明の抜粋:
https://github.com/wagoodman/dive#basic-features
Estimate "image efficiency"
[…] and an experimental metric that will guess how much wasted space your image contains. This might be from duplicating files across layers, moving files across layers, or not fully removing files. Both a percentage “score” and total wasted file space is provided.
このイメージの効率推定結果はdive
のテキストベースUI上だと左セクションの下で見切れたり、完全に隠れてしまって見づらいので、実行結果をJSON出力した上でイメージの効率推定結果を確認します。
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD:/data
wagoodman/dive jenkins/jenkins:lts
-j /data/result.json
そして出来上がったJSON出力からイメージの効率推定結果を抜粋したものが以下:
"image": {
"sizeBytes": 450089014,
"inefficientBytes": 4216729,
"efficiencyScore": 0.9946289313340139,
"fileReference": [
{
"count": 2,
"sizeBytes": 1661958,
"file": "/var/cache/debconf/templates.dat"
},
{
"count": 2,
"sizeBytes": 1615097,
"file": "/var/cache/debconf/templates.dat-old"
},
Dockerイメージ上の特定パスにあるファイルを準備するなら1回のDockerfileコマンドで完全に準備するのが理想と言えますが、プロジェクトやコンテンツのモジュール設計思想や、ビルドツールなどの都合上、アディティブ(加法的)にファイル作成せざるを得ないときは仕方がないかなと思います。
上記は複数あるレイヤーを単一レイヤーに集約することでも解決できて、2022年7月時点で未だ実験的機能である docker build --squash
や docker-squash などのツールで実現できます。
ですが、特定目的のファイル群をレイヤーとしてまとめた上でのレイヤー単位の持ち回しの容易さやキャッシュによる再可用性が失われてしまうので、「単一レイヤーに集約して出来上がるDockerイメージが50MBになる」だとかの顕著なサイズ縮小の利点がない限り、余り推奨しません。
余談
以下のツールについても、後日記事を書いてみる予定。
- Dockerfileのlintツール
https://github.com/hadolint/hadolint - Dockerイメージの解析&最適化ツール
https://github.com/docker-slim/docker-slim