c8d/push: Push multi-platform images#56
Conversation
tonistiigi
left a comment
There was a problem hiding this comment.
The behavior in here seems to be that images are modified to new images before pushing unless all manifests are present. Is this really the behavior we want?
That could have quite unexpected results to user if some random set of platforms was present based on previous commands or for example the pull of the image is running at the same time by another requests.
To me that kind of defeats the containerd storage model as well where everything is immutable and digests are persistent. But I see the conversion package is actually part of containerd repo so I guess somebody thought there are useful cases for it.
Alternative I would suggest is that part of the data that is not available locally is cross-repo mounted. The source location is saved to the blob labels anyway on pull to make regular cross-repo push work. If we hit the case where cross-repo-mount can't work (pushing to a different registry) then it would pull the missing data from one registry and push to the other. If that case is hit, we could print a warning to used as well, suggesting that if they only wished to push a single platform they should do docker push --platform or docker push image:tag@sha-of-the-specific-platform.
This is also how I imagine docker run would work but I haven't checked current state. If I do docker pull alpine on arm64 it pulls only one arch by default. But then if I do docker run --platform=amd64 alpine then it isn't an error but will just pull the missing part of the image before running it(same as if I didn't have the alpine image at all).
|
|
||
| store := i.client.ContentStore() | ||
|
|
||
| children, err := containerdimages.Children(ctx, store, index) |
There was a problem hiding this comment.
Image indexes can be nested so not only a single level should be accounted for in here.
There was a problem hiding this comment.
Thanks, good to know! Do you remember any specific images that have this kind of nesting so I could test with them?
There was a problem hiding this comment.
Some of my own test images https://gist.github.com/tonistiigi/838018f7487fabf840ca96b68212dfaf
|
|
||
| // Create a new manifest list which contains only the manifests we have in store. | ||
| srcRef := img.Name | ||
| targetRef := srcRef + "-tmp" |
There was a problem hiding this comment.
What happens if multiple requests end up in here on similar time. Does this need to be named?
Could we avoid leaking an image at all. Containerd probably just needs a digest to root manifest for source, not the image around it.
There was a problem hiding this comment.
Yeah if multiple requests end up in here then funny things can happen. The Name field in Image is required, so we need it.
For now I will add an UUID here, just like I did with the other case.
We could avoid the whole convert and just extract manifest to a particular platform, but only if there is only one matching manifest.
Otherwise we need to construct a new manifest list with the present manifests.
Of course we could also create the manifest list by hand but for now I think we don't want to implement this ourselves, especially since I'm not sure whether we'll want this behaviour in the future.
|
The conversion doesn't affect the original images - it creates a new one, so it's just the image on the registry side that will have a different digest. I agree that we need to rethink how to handle this overall. I think that in the end we will get rid of the conversion. But we just yet need to think how to fit the multi-platform images in the current Docker UX. |
|
Current behaviour also has funny side effects, consider this: So even though we didn't directly change the |
Yeah, that's how it works currently. |
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
93d6593 to
9df4272
Compare
Hmm... so, it's actually a good one. I think with the "non-containerd" snapshotter we already may have a similar issue (although in that case perhaps it's more logical). Say, I have a multi-arch image; docker buildx create --use --driver=docker-container
nostalgic_raman
docker buildx build --platform=linux/amd64,linux/arm64 --push -t thajeztah/multifoo:latest -<<'EOF'
FROM alpine
RUN echo hello > /foo.txt
EOFThen pull the image; docker pull thajeztah/multifoo:latest
latest: Pulling from thajeztah/multifoo
213ec9aee27d: Pull complete
c7ec7661263e: Pull complete
Digest: sha256:94c333fa15282d57c802f5ce4d37208674d367dbe424a8b7b0ff53847c7a8231
Status: Downloaded newer image for thajeztah/multifoo:latest
docker.io/thajeztah/multifoo:latestNotice that docker always shows the digest that was resolved from the name; in this case that's the digest of the manifest list, which is also shown in docker image ls --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
thajeztah/multifoo latest sha256:94c333fa15282d57c802f5ce4d37208674d367dbe424a8b7b0ff53847c7a8231 cb269d7c0c1c 6 minutes ago 5.54MBBut (pre containerd integration) at this moment, I only have 1 image from the manifest list, and the docker image inspect thajeztah/multifoo
[
{
"Id": "sha256:cb269d7c0c1ca22fb5a70342c3ed2196c57a825f94b3f0e5ce3aa8c55baee829",
"RepoTags": [
"thajeztah/multifoo:latest"
],
"RepoDigests": [
"thajeztah/multifoo@sha256:94c333fa15282d57c802f5ce4d37208674d367dbe424a8b7b0ff53847c7a8231"
],But while it shows the digest of the manifest list, that's not used when pushing the same image again; docker push thajeztah/multifoo:latest
The push refers to repository [docker.io/thajeztah/multifoo]
cd4e02e5bfde: Layer already exists
994393dc58e7: Layer already exists
latest: digest: sha256:09902b434e82972ad529df8baf34cb4dcebe70bf0f78dbf4d2b90895c18636b4 size: 735When pushing, it converts the multi-arch image into a single arch without a manifest list. This of course makes sense if this was an image that was built locally (so no "multi-arch" existed), but pushing an image that you pulled (without any changes), leading to (effectively) deleting images is not ideal. Even with the pre-containerd case, we could possibly (if we keep all metadata);
We should definitely have a look what we want the UX to be like for this, because there's different scenario's possible, and we may not be able to "automatically" decide for the user (so may need options to override behavior); we likely don't want to "force" having to pull all image variants (including windows) |
Yes, but it changes the image in the registry. So if the pushed image is compared with the original one it is different now.
Yeah, I think that is the reason not to convert. If we don't convert , then
Clarification on this that this is how it should work when alpine in the registry did not change in between the pull and run command. If before running
Yes, these were the issues containerd was designed to solve. Without it Docker does not keep the manifest so we always need to recreate one. We could check based on the root digest we store but that would mean that if there is any change on the registry or pushing to different repo then the push behavior would be completely different and push new single manifest instead. What I forgot to mention is that since Docker 1.10 (2015) we have had a strong guarantee that when we do the pull(load)-push(save)-pull(load) cycle then the image ID is persistent and never changes. In containerd the image ID (eg in |
I'm not sure what should be regarded as a dangling image under containerd. |
|
oh, crap; this needs a rebase; tried it locally, and not too complicated, but in case it's useful; here's the rebased version I pushed to a temporary branch; https://github.com/rumpl/moby/compare/c8d...thaJeztah:rumpl_containerd_push_multi_platform_TEMP_rebase?expand=1 |
|
did you mean #74 ? |
|
Yes! Sorry, must have copied the wrong tab 😆 |
|
Closing this one, we have #74 |
cross-compiling for arm/v5 was failing;
#56 84.12 /usr/bin/arm-linux-gnueabi-clang -marm -o $WORK/b001/exe/a.out -Wl,--export-dynamic-symbol=_cgo_panic -Wl,--export-dynamic-symbol=_cgo_topofstack -Wl,--export-dynamic-symbol=crosscall2 -Qunused-arguments -Wl,--compress-debug-sections=zlib /tmp/go-link-759578347/go.o /tmp/go-link-759578347/000000.o /tmp/go-link-759578347/000001.o /tmp/go-link-759578347/000002.o /tmp/go-link-759578347/000003.o /tmp/go-link-759578347/000004.o /tmp/go-link-759578347/000005.o /tmp/go-link-759578347/000006.o /tmp/go-link-759578347/000007.o /tmp/go-link-759578347/000008.o /tmp/go-link-759578347/000009.o /tmp/go-link-759578347/000010.o /tmp/go-link-759578347/000011.o /tmp/go-link-759578347/000012.o /tmp/go-link-759578347/000013.o /tmp/go-link-759578347/000014.o /tmp/go-link-759578347/000015.o /tmp/go-link-759578347/000016.o /tmp/go-link-759578347/000017.o /tmp/go-link-759578347/000018.o -O2 -g -O2 -g -O2 -g -lpthread -O2 -g -no-pie -static
#56 84.12 ld.lld: error: undefined symbol: __atomic_load_4
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced 2 more times
#56 84.12
#56 84.12 ld.lld: error: undefined symbol: __atomic_store_4
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(x_cgo_notify_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(x_cgo_set_context_function)
#56 84.12 clang: error: linker command failed with exit code 1 (use -v to see invocation)
From discussion on GitHub;
moby#46982 (comment)
The arm/v5 build failure looks to be due to libatomic not being included
in the link. For reasons probably buried in mailing list archives,
[gcc](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81358) and clang don't
bother to implicitly auto-link libatomic. This is not a big deal on many
modern platforms with atomic intrinsics as the compiler generates inline
instruction sequences, avoiding any libcalls into libatomic. ARMv5 is not
one of those platforms: all atomic operations require a libcall.
In theory, adding `CGO_LDFLAGS=-latomic` should fix arm/v5 builds.
While it could be argued that cgo should automatically link against
libatomic in the same way that it automatically links against libpthread,
the Go maintainers would have a valid counter-argument that it should be
the C toolchain's responsibility to link against libatomic automatically,
just like it does with libgcc or compiler-rt.
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Cory Snider <csnider@mirantis.com>
cross-compiling for arm/v5 was failing;
#56 84.12 /usr/bin/arm-linux-gnueabi-clang -marm -o $WORK/b001/exe/a.out -Wl,--export-dynamic-symbol=_cgo_panic -Wl,--export-dynamic-symbol=_cgo_topofstack -Wl,--export-dynamic-symbol=crosscall2 -Qunused-arguments -Wl,--compress-debug-sections=zlib /tmp/go-link-759578347/go.o /tmp/go-link-759578347/000000.o /tmp/go-link-759578347/000001.o /tmp/go-link-759578347/000002.o /tmp/go-link-759578347/000003.o /tmp/go-link-759578347/000004.o /tmp/go-link-759578347/000005.o /tmp/go-link-759578347/000006.o /tmp/go-link-759578347/000007.o /tmp/go-link-759578347/000008.o /tmp/go-link-759578347/000009.o /tmp/go-link-759578347/000010.o /tmp/go-link-759578347/000011.o /tmp/go-link-759578347/000012.o /tmp/go-link-759578347/000013.o /tmp/go-link-759578347/000014.o /tmp/go-link-759578347/000015.o /tmp/go-link-759578347/000016.o /tmp/go-link-759578347/000017.o /tmp/go-link-759578347/000018.o -O2 -g -O2 -g -O2 -g -lpthread -O2 -g -no-pie -static
#56 84.12 ld.lld: error: undefined symbol: __atomic_load_4
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced 2 more times
#56 84.12
#56 84.12 ld.lld: error: undefined symbol: __atomic_store_4
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(_cgo_wait_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(x_cgo_notify_runtime_init_done)
#56 84.12 >>> referenced by gcc_libinit.c
#56 84.12 >>> /tmp/go-link-759578347/000009.o:(x_cgo_set_context_function)
#56 84.12 clang: error: linker command failed with exit code 1 (use -v to see invocation)
From discussion on GitHub;
moby#46982 (comment)
The arm/v5 build failure looks to be due to libatomic not being included
in the link. For reasons probably buried in mailing list archives,
[gcc](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81358) and clang don't
bother to implicitly auto-link libatomic. This is not a big deal on many
modern platforms with atomic intrinsics as the compiler generates inline
instruction sequences, avoiding any libcalls into libatomic. ARMv5 is not
one of those platforms: all atomic operations require a libcall.
In theory, adding `CGO_LDFLAGS=-latomic` should fix arm/v5 builds.
While it could be argued that cgo should automatically link against
libatomic in the same way that it automatically links against libpthread,
the Go maintainers would have a valid counter-argument that it should be
the C toolchain's responsibility to link against libatomic automatically,
just like it does with libgcc or compiler-rt.
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Cory Snider <csnider@mirantis.com>
(cherry picked from commit 4cd5c2b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Now that we have the option to build multi-platform images it would be nice to be able to push them.
Before this only the default platform was pushed so eventually the image in the registry was single-platform again.
Signed-off-by: Paweł Gronowski pawel.gronowski@docker.com
- What I did
- How I did it
- How to verify it
- Description for the changelog
- A picture of a cute animal (not mandatory but encouraged)