Tiltfile の prune 設定に苦労した話
こんにちは。たかしです。
Tilt イメージが無限に溜まって困った話と、解決法の備忘録です。
[結論]最終的な Tiltfile
# Docker Composeファイルを使用してフロントエンドを実行
docker_compose('compose.yml')
docker_build('app:v0.0.0', '.', dockerfile='./containers/frontend/Dockerfile')
# Image の prune 設定。max_age_mins は1以上でないと動かない。
docker_prune_settings( disable = False, max_age_mins = 1, num_builds = 1, keep_recent = 0 )
※要点をわかりやすくするために、必要最低限の設定にしています。
・docker_prune_settings
を使えば定期的にイメージを掃除してくれる。
・max_age_mins
に0を指定すると prune が動作しないため、かならず1以上を指定する。
docker_prune_settings
の公式リファレンス:
Tilt とは?
コード変更時のコンテナの更新を自動化してくれます。
compose.yaml で volume を設定して bind mount するのと同じような感じです。
bind mount と違うのは以下のような点です。
・kubernetes リソースに対しても利用可能
・Dockerfile 等のマニフェストファイルの変更も監視/自動更新してくれる
Tilt 公式:
直面していた問題
当時 Tilt で扱っていたイメージの一つに、2GBくらいのサイズを持つ playwright イメージがありました。Tilt はコンテナ更新の際、古いイメージを残して新しいイメージを生成・適用するといった振る舞いをします。
つまり、e2e テストを変更するたびに2GBのイメージが build されていたのです。
当時の私は wsl 上で docker を動かしていたので、該当イメージを削除後に diskpart
とかいう謎のディスク領域解放コマンドを打つことで対処する形をとっていました。
別に仕事ではなかったので適当な気持ちでおこなっていたのですが、diskpart
は中々リスキーなコマンドです。
さすがにまずいかなと思った私は、イメージの自動クリーンアップの方法を求めて公式リファレンスにて解決方法を探すことにしました。
イメージのクリーンアップ方法は簡単に見つかりました。どうやら docker_prune_settings
というものを使えば自動で古いイメージを削除できるようです。
パラメータも少ないし、簡単に設定できそうだなと、このときは思っていました。
動作しない docker_prune_settings
以下が問題を再現した Tiltfile です。
# Docker Composeファイルを使用してフロントエンドを実行
docker_compose('compose.yml')
docker_build('app:v0.0.0', '.', dockerfile='./containers/frontend/Dockerfile')
# Image の自動削除設定
docker_prune_settings( disable = False, max_age_mins = 0, num_builds = 1, keep_recent = 0 )
論点が分かりやすいように、必要最低限の構成にしてあります。注目すべきはやはり、docker_prune_settings
ですね。
以下はこの関数のパラメータ仕様です。
・disable
:true なら Docker Pruner を無効化する。
・max_age_mins
:イメージ/コンテナの最大保存期間を設定する。
・num_builds
: ここに設定した回数のビルドが実行されるたび prune を実行する
・keep_recent
: prune 実行時に最新何件までを残すかを設定する。
パラメータ情報をそのまま受け取ると、先ほどの docker_prune_settings
の挙動は次のようになります。
「ビルドが1回起こるたびに0分以上生存していたイメージを全て削除する」
Git を使っていれば開発用イメージの履歴は不要ですので、過去のイメージは全て削除する設定にしています。
tilt up
で実行して挙動を確認していきましょう。
まず、起動直後のイメージ数を見ていきます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
app tilt-ba3fede5a7ac8141 ba3fede5a7ac 20 minutes ago 253MB
app v0.0.0 ba3fede5a7ac 20 minutes ago 253MB
起動直後は2つのイメージが表示されていますね。ImageID が同じなので、両方同じイメージを参照していることになります。つまり、実際にはイメージは1つしか生成されていません。
ここまでは問題ないですね。
ソースコードに2回ほど変更を加えて、再度イメージの数を確認します。
過去のすべてのイメージが削除されるはずなので、先ほどとは別のイメージが1つだけ存在しているはずです。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
app tilt-9bff68504eb6d2af 9bff68504eb6 3 seconds ago 253MB
app v0.0.0 9bff68504eb6 3 seconds ago 253MB
app tilt-87269c86cdd86421 87269c86cdd8 22 seconds ago 253MB
app tilt-ba3fede5a7ac8141 ba3fede5a7ac 26 minutes ago 253MB
…おかしい。
無限に増殖するイメージ。
私は絶望し、この世に生まれたことを後悔しました。
「もう、アイツには勝てない…。」
私の心の弱い部分が、囁きかけてきます。
私は Tiltfile に敗北しました。そして再び、diskpart
でイメージを削除する生活に戻っていったのです…。
解決編
あれから2週間。私は頭を冷やしました。
冷静に考えれば、docker_prune_settings
が公式ドキュメントに存在する以上、正しく設定すれば動くはずなのです。私は決意を胸に、再度 docker_prune_settings
と向き合ってみることにしました。
そして20分ほどの激闘の末、私は真理にたどり着くことになります。
# Docker Composeファイルを使用してフロントエンドを実行
docker_compose('compose.yml')
docker_build('app:v0.0.0', '.', dockerfile='./containers/frontend/Dockerfile')
# Image の自動削除設定
docker_prune_settings( disable = False, max_age_mins = 1, num_builds = 1, keep_recent = 0 )
違いはそう、max_age_mins
です。コイツが問題でした。
なぜ0だと動かないのかはめんどくさくて検証していませんが、保存期間がデフォルトの360分になるか、保存期間が無限のような挙動になるかのどちらかだと思います。
挙動を確認するため、再度実行してみます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
app tilt-62edbf20fdc49035 62edbf20fdc4 3 seconds ago 253MB
app v0.0.0 62edbf20fdc4 3 seconds ago 253MB
先ほどと同じ、1つのイメージが生成されています。
すぐにソースコードに2回ほど変更を加えて、イメージの数を確認します。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
app tilt-e49921eba932657a e49921eba932 4 seconds ago 253MB
app tilt-a4ca7c43e566e73a a4ca7c43e566 12 seconds ago 253MB
app v0.0.0 a4ca7c43e566 12 seconds ago 253MB
app tilt-03ee481d309e5896 03ee481d309e 17 seconds ago 253MB
3つのイメージが出現しました。max_age_mins を1に設定したので、まだイメージは削除されていません。
1分待った後、再度変更を加えます。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
app tilt-168ecb0f953d36ed 168ecb0f953d 1 second ago 253MB
app v0.0.0 168ecb0f953d 1 second ago 253MB
無事、イメージが消えました!
これで diskpart
とはおさらばです。
まとめ
公式ドキュメントは信頼しましょう。
以上、たかしでした。