Skip to content

optimize memory usage in image load with Docker client integration #21103

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

Merged
merged 1 commit into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions pkg/minikube/image/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ limitations under the License.
package image

import (
"context"
"io"
"os"
"path/filepath"
"time"

"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/tarball"
Expand Down Expand Up @@ -84,7 +87,6 @@ func SaveToDir(images []string, cacheDir string, overwrite bool) error {
if err := g.Wait(); err != nil {
return errors.Wrap(err, "caching images")
}
klog.Infoln("Successfully saved all images to host disk.")
return nil
}

Expand Down Expand Up @@ -158,6 +160,36 @@ func saveToTarFile(iname, rawDest string, overwrite bool) error {
return nil
}

func saveImageWithDockerClient(f *os.File, ref name.Reference) error {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
return errors.Wrap(err, "creating docker client")
}
defer cli.Close()

ctx := context.Background()

_, _, err = cli.ImageInspectWithRaw(ctx, ref.String())
if err != nil {
return errors.Wrapf(err, "inspect image %s via docker client", ref.String())
}

imageResponse, err := cli.ImageSave(ctx, []string{ref.String()})
if err != nil {
return errors.Wrapf(err, "saving image %s via docker client", ref.String())
}
defer imageResponse.Close()

// Copy image data stream to file
_, err = io.Copy(f, imageResponse)
if err != nil {
return errors.Wrapf(err, "copying image %s data to file", ref.String())
}

klog.Infof("Successfully saved image %s using Docker client", ref.String())
return nil
}

func writeImage(img v1.Image, dst string, ref name.Reference) error {
klog.Infoln("opening: ", dst)
f, err := os.CreateTemp(filepath.Dir(dst), filepath.Base(dst)+".*.tmp")
Expand All @@ -175,10 +207,27 @@ func writeImage(img v1.Image, dst string, ref name.Reference) error {
}
}()

err = tarball.Write(ref, img, f)
if err != nil {
return errors.Wrap(err, "write")
var imageSaved bool

// Using the Docker client to save the image for better performance
if useDaemon {
// Try to save the image using the Docker client
if err := saveImageWithDockerClient(f, ref); err != nil {
if !client.IsErrNotFound(err) {
return errors.Wrap(err, "docker save")
}
klog.Warningf("Failed to save image with Docker client: %v, falling back to tarball.Write", err)
} else {
imageSaved = true
}
}
// Fallback to saving the image using the tarball package
if !imageSaved {
if err := tarball.Write(ref, img, f); err != nil {
return errors.Wrap(err, "write")
}
}

err = f.Close()
if err != nil {
return errors.Wrap(err, "close")
Expand Down
2 changes: 1 addition & 1 deletion pkg/minikube/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func retrieveImage(ref name.Reference, imgName string) (v1.Image, string, error)
}

func retrieveDaemon(ref name.Reference) (v1.Image, error) {
img, err := daemon.Image(ref)
img, err := daemon.Image(ref) // uses bufferedOpener, which may consume a significant amount of memory
if err == nil {
klog.Infof("found %s locally: %+v", ref.Name(), img)
return img, nil
Expand Down