-
Notifications
You must be signed in to change notification settings - Fork 42
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
Fix local images when daemon uses containerd storage #222
Conversation
Signed-off-by: Natalie Arellano <[email protected]>
cfg1.DockerVersion = "" | ||
cfg2.DockerVersion = "" |
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 tried explicitly setting this about here:
Line 210 in 67824e9
config = v1.Config{ |
ref, err := name.ParseReference(refStr, name.WeakValidation) | ||
AssertNil(t, err) | ||
t.Helper() | ||
Run(t, exec.Command("docker", "push", refStr)) // #nosec G204 |
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.
This was interesting. dockerCli.ImagePush
returns a 401 error, but docker image push
works fine. I didn't look too deeply into it, but this is another rough edge that could be worth investigating. Our test registry is from github.com/google/go-containerregistry/pkg/registry
- maybe another registry would behave differently, but I didn't try 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.
That is very weird. Maybe we should open a bug on this one. I captured the flow a bit.
// our docker.Push wrapper
fmt.Println("DEBUG (DockerPush)", authConfig.Username, authConfig.Password)
rc, err := dockerCli.ImagePush(context.Background(), refStr, dockertypes.ImagePushOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(encodedJSON),
PrivilegeFunc: func() (string, error) {
fmt.Println("DEBUG (DockerPush)", "PrivilegeFunc")
return "bob", nil
},
})
// the registry bit
func BasicAuth(handler http.Handler, username, password, realm string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
fmt.Println("DEBUG (RegistryBasicAuth)", "user:", user, "pass:", pass, "ok:", ok)
// relevant debug output
DEBUG (DockerPush) vvqijndzyz wiqldsvicu
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: pass: ok: false
The generated auth is passed into ImagePushOptions. The first empty user/pass at the registry auth check is typical but the subsequent ones are not. I tried adding the
PrivilegeFunc` to see if the 401 would trigger an auth send but the logs show it isn't executed in either situation. Like the auth is ignored entirely 😞
Here are the logs when using the overlayfs for comparison.
DEBUG (DockerPush) zitozndflk dhlzgeqnku
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: zitozndflk pass: dhlzgeqnku ok: true
DEBUG (RegistryBasicAuth) user: zitozndflk pass: dhlzgeqnku ok: true
} | ||
|
||
return nil | ||
_, err = i.addImageToTar(tw, repoName) |
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.
This reduces some duplication but also without the refactor I got an "unexpected format" error when trying to load the image in the daemon. Which is weird because the code looks basically the same...
@@ -154,13 +123,16 @@ func (i *Image) doSaveAs(name string) (types.ImageInspect, error) { | |||
return types.ImageInspect{}, errors.Wrapf(err, "loading image %q. first error", i.repoName) | |||
} | |||
|
|||
inspect, _, err := i.docker.ImageInspectWithRaw(context.Background(), id) | |||
inspect, _, err := i.docker.ImageInspectWithRaw(context.Background(), i.repoName) |
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.
With containerd storage the image ID is the sha of the manifest, which technically we could compute but it would be hard. To avoid collision problems we can validate that the data in inspect matches the config that we expect.
// The docker daemon allows this if the given set of layers already exists in the daemon in the given order. | ||
inspect, err = i.doSaveAs(name) | ||
} | ||
if !canOmitBaseLayers || err != nil { |
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.
This is the sorriest part... it's going to lead to a huge performance penalty unless we can work around it. Maybe with containerd storage we could get individual layers from the daemon, IDK. If anyone has further context here it would be much appreciated.
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 also couldn't find a way around this. The trick of giving a tar header with no bytes does not work so we always have to pay the price it seems. At least until moby/moby#44369 ships
Signed-off-by: Natalie Arellano <[email protected]>
cfg1.Config.Image = "" | ||
cfg2.Config.Image = "" |
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.
This seems unspec'd so IDK if we care.
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.
Great work! This seems shippable for me since the storage system is in beta on the docker side.
// The docker daemon allows this if the given set of layers already exists in the daemon in the given order. | ||
inspect, err = i.doSaveAs(name) | ||
} | ||
if !canOmitBaseLayers || err != nil { |
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 also couldn't find a way around this. The trick of giving a tar header with no bytes does not work so we always have to pay the price it seems. At least until moby/moby#44369 ships
ref, err := name.ParseReference(refStr, name.WeakValidation) | ||
AssertNil(t, err) | ||
t.Helper() | ||
Run(t, exec.Command("docker", "push", refStr)) // #nosec G204 |
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.
That is very weird. Maybe we should open a bug on this one. I captured the flow a bit.
// our docker.Push wrapper
fmt.Println("DEBUG (DockerPush)", authConfig.Username, authConfig.Password)
rc, err := dockerCli.ImagePush(context.Background(), refStr, dockertypes.ImagePushOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(encodedJSON),
PrivilegeFunc: func() (string, error) {
fmt.Println("DEBUG (DockerPush)", "PrivilegeFunc")
return "bob", nil
},
})
// the registry bit
func BasicAuth(handler http.Handler, username, password, realm string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
fmt.Println("DEBUG (RegistryBasicAuth)", "user:", user, "pass:", pass, "ok:", ok)
// relevant debug output
DEBUG (DockerPush) vvqijndzyz wiqldsvicu
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: pass: ok: false
The generated auth is passed into ImagePushOptions. The first empty user/pass at the registry auth check is typical but the subsequent ones are not. I tried adding the
PrivilegeFunc` to see if the 401 would trigger an auth send but the logs show it isn't executed in either situation. Like the auth is ignored entirely 😞
Here are the logs when using the overlayfs for comparison.
DEBUG (DockerPush) zitozndflk dhlzgeqnku
DEBUG (RegistryBasicAuth) user: pass: ok: false
DEBUG (RegistryBasicAuth) user: zitozndflk pass: dhlzgeqnku ok: true
DEBUG (RegistryBasicAuth) user: zitozndflk pass: dhlzgeqnku ok: true
Signed-off-by: Natalie Arellano <[email protected]>
Before #222, we calculated the sha of the config file that we sent to the daemon, and verified that we could inspect an image with that ID. After #222, since the image ID may be the sha of the config file or the sha of the manifest file (depending on whether containerd storage is enabled), we still calculated the sha of the config file that we sent to the daemon, but instead of trying to inspect an image with that ID, we inspected the image by name, derived the config file from the data we got back from inspect, and then verified that the sha of the derived config file matches the sha of the config file that we sent in. This check turned out to be brittle (see buildpacks/pack#2000 and https://cloud-native.slack.com/archives/C0331B61A1Y/p1701976103265489) and we agreed that it should be safe to remove this check. Signed-off-by: Natalie Arellano <[email protected]>
In service of:
pack build
fails with experimental containerd feature enabled in Docker Desktop (macos) pack#1519On Mac: Docker > Settings > Features in development > Use containerd for pulling and storing images
Tests pass for me locally with both configurations.
Local image creation works with these changes, but is unsatisfactory as we can no longer omit base layers that are already present in the daemon. Omitting the layers creates a runnable image, but if you try to
docker push
that image to a registry the daemon will crash, I'm not sure why. But if you do adocker save
on that image you will get a base layer that is 0 in size. It's possible this is something that will be fixed before containerd storage becomes the default configuration. We should keep an eye on it (or maybe there's another way around it).