-
Notifications
You must be signed in to change notification settings - Fork 18.8k
containerd: images overridden by a build are kept dangling #49702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
containerd: images overridden by a build are kept dangling #49702
Conversation
f4c0462
to
14cba58
Compare
daemon/build.go
Outdated
c8dexporter "github.com/docker/docker/builder/builder-next/exporter" | ||
"github.com/moby/buildkit/exporter" | ||
"github.com/moby/buildkit/exporter/containerimage/exptypes" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should try to keep as much buildkit stuff inside the builder
package and avoid any dependencies on the buildkit
package inside daemon
package.
var exported ExportedCallback | ||
if i.callbacks.Exported != nil { | ||
exported = i.callbacks.Exported(ctx, src, i.Attrs()) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking whether we could avoid leaking buildkit stuff into the daemon and having to extend the ImageService
.
I think we could also make it work similar to how it works with pull:
moby/daemon/containerd/image_pull.go
Lines 96 to 112 in 0451e4f
if oldImage.Target.Digest != "" { | |
err = i.leaseContent(ctx, i.content, oldImage.Target) | |
if err != nil { | |
return errdefs.System(fmt.Errorf("failed to lease content: %w", err)) | |
} | |
// If the pulled image is different than the old image, we will keep the old image as a dangling image. | |
defer func() { | |
if outNewImg != nil { | |
if outNewImg.Target().Digest != oldImage.Target.Digest { | |
if err := i.ensureDanglingImage(ctx, oldImage); err != nil { | |
log.G(ctx).WithError(err).Warn("failed to keep the previous image as dangling") | |
} | |
} | |
} | |
}() | |
} |
Basically:
- Before build, lease all content targetted by the image that can possibly be overriden (so it doesn't get GC'd)
- Invoke build
- Check if the images changed, if they did, tag dangling images and then remove leases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like there aren't currently any parts of the buildkit builder that currently interact with the ImageService
and the ensureDanglingImage
method is only part of the containerd image service so I think I'll need to add a new method to that interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm right. Looking at these callbacks in daemon
basically call the image service again.
Perhaps it would make sense to move them out of the daemon to the image.
Then we could encapsulate the dangling image logic in the image service completely.
We could replace the ImageExportedByBuildkit
with two callbacks like:
BuildInitiated
- this would handle leasing the contentBuildFinished
- this would handle removing the lease and tagging dangling images
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured out a way to reuse the existing ImageTagger
. The exporter now gets passed the image tagger. When the exporter runs, it clears the name so buildkit won't tag it by name. Instead, buildkit will always tag it as a dangling image and then we can perform the naming ourselves. TagImage
already handles deleting the dangling image name when the first tag happens.
The only downside is this won't support keeping a name that is from the frontend. It's possible for buildkit frontends to set image.name
and force the image to a specific name as a default. We aren't able to figure out that name until the frontend tells us anyway so there's no way to get that name without teaching the buildkit exporter how to do the dangling image dance.
This wouldn't have worked for the previous implementation and the leaseContent
method also wouldn't handle this since it all happens within buildkit's direct interaction with the containerd image store. At the same time, I think this is a rare enough feature that we can just let it go. The default frontends for buildkit don't utilize this feature.
Here's some sample output:
=> => exporting layers 4.6s
=> => exporting manifest sha256:c010c84be19e970bdca89f47460f4279404d60ead05aa6806782e36cedf4096f 0.0s
=> => exporting config sha256:7aa7c843e893dee344f9aa5b3a572f3399c6daa9438ff1b1a65c1d7d6ff1c5de 0.0s
=> => exporting attestation manifest sha256:7fe4b993a97506d594ec18a46e673505b235fb2562fb6e2ada0ce5852a323e3c 0.0s
=> => exporting manifest list sha256:620031442269282d8570775a6a631bf36127ddd2a8856bebd9b72bf6906a9739 0.0s
=> => naming to moby-dangling@sha256:620031442269282d8570775a6a631bf36127ddd2a8856bebd9b72bf6906a9739 0.0s
=> => unpacking to moby-dangling@sha256:620031442269282d8570775a6a631bf36127ddd2a8856bebd9b72bf6906a9739 0.9s
=> => naming to docker.io/moby/buildkit:local
Still need to do some cleanup but I think this will keep things more maintainable until we can remove dangling images entirely.
d4e09b7
to
5e9a13c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
exporterAttrs[string(exptypes.OptKeyName)] = strings.Join(reposAndTags, ",") | ||
|
||
// Force the exporter to not use a name so it always creates a dangling image. | ||
exporterAttrs[string(exptypes.OptKeyName)] = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since name will always be empty now - can we make use of the OptKeyDanglingEmptyOnly
now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? We can potentially remove it since name will always be empty but it also doesn't harm anything to keep it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I meant not forcing OptKeyDanglingEmptyOnly = true
(the default is false).
It doesn't harm, but if the buildkit default settings make sense, we should try to stick to them to make the wrapper as minimal as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's ok, I can leave this for cleanup. CI is passing and I'd prefer not to trigger CI again.
Ah, the build test failure seems to be related:
|
The build exporter now clears the image tags and always exported to a dangling image. It then uses the image tagger to perform the tagging which causes the dangling image to be removed and the naming message to be sent correctly. An additional progress message is sent to indicate the renaming. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
5e9a13c
to
50a8561
Compare
I needed to fix up the response metadata. Since buildkit won't set the image name anymore, the image name showed the dangling instead of the proper name. We'll fix it up on our side. The test also needs to be updated. It has expected and actual wrong so the error message is backwards. That's a buildkit fix though. |
Did some quick tests with this; With containerd enabledprintf 'FROM alpine\nRUN echo foo > /hello' | docker build -t myimage -
# ...
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:96389b8445f0637428ec374a3d3bedae2ddd578d72e6423e50e71b42562f5aff 0.0s
=> => exporting config sha256:c05cbcf8619b9648264d7cf08a75b6cad1f470846418581e71682872ad9a3909 0.0s
=> => exporting attestation manifest sha256:37f46c7ed95622f2dd5fc8edb5c735f1478bed243619d37e79d16748f7477ed2 0.0s
=> => exporting manifest list sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 0.0s
=> => naming to moby-dangling@sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 0.0s
=> => unpacking to moby-dangling@sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 0.0s
=> => naming to docker.io/library/myimage:latest 0.0s
docker run --rm myimage@sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 cat /hello
foo Rebuild the image (causes the tag to be pointing to the new image); printf 'FROM alpine\nRUN echo world > /hello' | docker build -t myimage -
# ...
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => exporting manifest sha256:6a5da425bc6ecd14160167b145b4469806d18a2305b5f0f4e855518ba27ef6b7 0.0s
=> => exporting config sha256:e887e3ffb647e15ac187568d490cc32e6eac68ecbdd5ffbd8ae9364b1f6126d4 0.0s
=> => exporting attestation manifest sha256:99cccb8e07ee0a762d716b1e1fed1e72e0ef630996ba82c3a024244fe4e7fdae 0.0s
=> => exporting manifest list sha256:8edf361df74ae843a48a7ea1f0ab9c1965f6c7c2145cf7c4db13ea7776dbe248 0.0s
=> => naming to moby-dangling@sha256:8edf361df74ae843a48a7ea1f0ab9c1965f6c7c2145cf7c4db13ea7776dbe248 0.0s
=> => unpacking to moby-dangling@sha256:8edf361df74ae843a48a7ea1f0ab9c1965f6c7c2145cf7c4db13ea7776dbe248 0.0s
=> => naming to docker.io/library/myimage:latest 0.0s
docker run --rm myimage@sha256:8edf361df74ae843a48a7ea1f0ab9c1965f6c7c2145cf7c4db13ea7776dbe248 cat /hello
world Try running the previous build; docker run --rm 58b1c6a60d7b cat /hello
foo
docker run --rm 58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 cat /hello
foo Fails with docker run --rm myimage@sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867 cat /hello
Unable to find image 'myimage@sha256:58b1c6a60d7b868b4e726414b9285f7cebfb4e2cc439ce74af8dc44db3e3b867' locally
docker: Error response from daemon: pull access denied for myimage, repository does not exist or may require 'docker login'
Run 'docker run --help' for more information Without containerd enabledprintf 'FROM alpine\nRUN echo foo > /hello' | docker build -t myimage -
# ...
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 0.0s
=> => naming to docker.io/library/myimage 0.0s
docker run --rm myimage@sha256:0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 cat /hello
Unable to find image 'myimage@sha256:0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73' locally
docker: Error response from daemon: pull access denied for myimage, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
Run 'docker run --help' for more information ☝️ I wonder if that's a regression; ISTR we allowed Short, long, and (with algorithm) ID works though; docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
myimage latest 0255fea9a8e1 About a minute ago 8.17MB
docker image ls --no-trunc
REPOSITORY TAG IMAGE ID CREATED SIZE
myimage latest sha256:0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 4 minutes ago 8.17MB docker run --rm 0255fea9a8e1 cat /hello
foo
docker run --rm sha256:0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 cat /hello
foo
docker run --rm 0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 cat /hello
foo Rebuild the image (causes the tag to be pointing to the new image); printf 'FROM alpine\nRUN echo world > /hello' | docker build -t myimage -
# ...
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:3bbefa0e77d736edbd3f22badfd31a19dea16fe3ba60674b5eea2e720411f849 0.0s
=> => naming to docker.io/library/myimage 0.0s
docker run --rm 3bbefa0e77d736edbd3f22badfd31a19dea16fe3ba60674b5eea2e720411f849 cat /hello
world Try running the previous build; docker run --rm 0255fea9a8e1 cat /hello
foo
docker run --rm 0255fea9a8e199d42523fc5a609f487184630e1e126c087b2f9691a83fc5ea73 cat /hello
foo |
One of the things we discussed on today's call is that |
In this case it's because the dangling image is not attached to a repository anymore so We could make it work though, but that's out of scope of this PR. |
|
The build exporter now clears the image tags and always exported to a
dangling image. It then uses the image tagger to perform the tagging
which causes the dangling image to be removed and the naming message to
be sent correctly.
An additional progress message is sent to indicate the renaming.
Fixes the
build
portion of #48907.