Improve image pruning robustness

If you different images with the same git SHA, on the second deploy the
tag is moved and the first image becomes untagged. It may however still
be attached to an existing container.

To handle this:
1. Initially prune dangling images - this will remove any untagged
images that are not attached to an existing image
2. Then filter out the untagged images when deleting tagged images - any
that remain will be attached to a container.

The second issue is that `docker container ls -a --format '{{.Image}}`
will sometimes return the image id rather than a tag. This means that
the image doesn't get filtered out when we grep to remove the active
images.

To fix that we'll grep against both the image id and repo:tag.
This commit is contained in:
Donal McBreen
2023-05-31 09:43:50 +01:00
parent de2de19434
commit 079d9538bb
4 changed files with 24 additions and 9 deletions

View File

@@ -12,7 +12,8 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
with_lock do with_lock do
on(MRSK.hosts) do on(MRSK.hosts) do
execute *MRSK.auditor.record("Pruned images"), verbosity: :debug execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
execute *MRSK.prune.images execute *MRSK.prune.dangling_images
execute *MRSK.prune.tagged_images
end end
end end
end end

View File

@@ -2,11 +2,15 @@ require "active_support/duration"
require "active_support/core_ext/numeric/time" require "active_support/core_ext/numeric/time"
class Mrsk::Commands::Prune < Mrsk::Commands::Base class Mrsk::Commands::Prune < Mrsk::Commands::Base
def images def dangling_images
docker :image, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
end
def tagged_images
pipe \ pipe \
docker(:image, :ls, *service_filter, "--format", "'{{.Repository}}:{{.Tag}}'"), docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
"grep -v -w \"#{active_image_list}\"", "grep -v -w \"#{active_image_list}\"",
"while read tag; do docker rmi $tag; done" "while read image tag; do docker rmi $tag; done"
end end
def containers(keep_last: 5) def containers(keep_last: 5)
@@ -22,7 +26,10 @@ class Mrsk::Commands::Prune < Mrsk::Commands::Base
end end
def active_image_list def active_image_list
"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}" # Pull the images that are used by any containers
# Append repo:latest - to avoid deleting the latest tag
# Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
end end
def service_filter def service_filter

View File

@@ -10,7 +10,8 @@ class CliPruneTest < CliTestCase
test "images" do test "images" do
run_command("images").tap do |output| run_command("images").tap do |output|
assert_match "docker image ls --filter label=service=app --format '{{.Repository}}:{{.Tag}}' | grep -v -w \"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=app | tr -d '\\n')dhh/app:latest\" | while read tag; do docker rmi $tag; done on 1.1.1.", output assert_match "docker image prune --force --filter label=service=app --filter dangling=true on 1.1.1.", output
assert_match "docker image ls --filter label=service=app --format '{{.ID}} {{.Repository}}:{{.Tag}}' | grep -v -w \"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=app | tr -d '\\n')dhh/app:latest\\|dhh/app:<none>\" | while read image tag; do docker rmi $tag; done on 1.1.1.", output
end end
end end

View File

@@ -8,10 +8,16 @@ class CommandsPruneTest < ActiveSupport::TestCase
} }
end end
test "images" do test "dangling images" do
assert_equal \ assert_equal \
"docker image ls --filter label=service=app --format '{{.Repository}}:{{.Tag}}' | grep -v -w \"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=app | tr -d '\\n')dhh/app:latest\" | while read tag; do docker rmi $tag; done", "docker image prune --force --filter label=service=app --filter dangling=true",
new_command.images.join(" ") new_command.dangling_images.join(" ")
end
test "tagged images" do
assert_equal \
"docker image ls --filter label=service=app --format '{{.ID}} {{.Repository}}:{{.Tag}}' | grep -v -w \"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=app | tr -d '\\n')dhh/app:latest\\|dhh/app:<none>\" | while read image tag; do docker rmi $tag; done",
new_command.tagged_images.join(" ")
end end
test "containers" do test "containers" do