Skip to content
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

Image load: Allow loading local images from tar or cache #10807

Merged
merged 14 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 12 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
86 changes: 80 additions & 6 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ limitations under the License.
package cmd

import (
"io"
"io/ioutil"
"os"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/image"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/reason"
)
Expand All @@ -32,11 +38,33 @@ var imageCmd = &cobra.Command{
Long: "Load a local image into minikube",
}

var (
imgDaemon bool
imgRemote bool
)

func saveFile(r io.Reader) (string, error) {
tmp, err := ioutil.TempFile("", "build.*.tar")
if err != nil {
return "", err
}
_, err = io.Copy(tmp, r)
if err != nil {
return "", err
}
err = tmp.Close()
if err != nil {
return "", err
}
return tmp.Name(), nil
}

// loadImageCmd represents the image load command
var loadImageCmd = &cobra.Command{
Use: "load",
Short: "Load a local image into minikube",
Long: "Load a local image into minikube",
Use: "load IMAGE | ARCHIVE | -",
Short: "Load a image into minikube",
Long: "Load a image into minikube",
Example: "minikube image load image\nminikube image load image.tar",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
exit.Message(reason.Usage, "Please provide an image in your local daemon to load into minikube via <minikube image load IMAGE_NAME>")
Expand All @@ -46,13 +74,59 @@ var loadImageCmd = &cobra.Command{
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}
img := args[0]
if err := machine.CacheAndLoadImages([]string{img}, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)

var local bool
if imgRemote || imgDaemon {
local = false
} else {
for _, img := range args {
if img == "-" { // stdin
local = true
imgDaemon = false
imgRemote = false
} else if strings.HasPrefix(img, "/") || strings.HasPrefix(img, ".") {
local = true
imgDaemon = false
imgRemote = false
} else if _, err := os.Stat(img); err == nil {
local = true
imgDaemon = false
imgRemote = false
}
}

if !local {
imgDaemon = true
imgRemote = true
}
}

if args[0] == "-" {
tmp, err := saveFile(os.Stdin)
if err != nil {
exit.Error(reason.GuestImageLoad, "Failed to save stdin", err)
}
args = []string{tmp}
}

if imgDaemon || imgRemote {
image.UseDaemon(imgDaemon)
image.UseRemote(imgRemote)
if err := machine.CacheAndLoadImages(args, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)
}
} else if local {
// Load images from local files, without doing any caching or checks in container runtime
// This is similar to tarball.Image but it is done by the container runtime in the cluster.
if err := machine.DoLoadImages(args, []*config.Profile{profile}, ""); err != nil {
exit.Error(reason.GuestImageLoad, "Failed to load image", err)
}
}
},
}

func init() {
imageCmd.AddCommand(loadImageCmd)
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
loadImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image from remote registry")
}
2 changes: 1 addition & 1 deletion pkg/minikube/bootstrapper/kubeadm/kubeadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ func (k *Bootstrapper) UpdateCluster(cfg config.ClusterConfig) error {
}

if cfg.KubernetesConfig.ShouldLoadCachedImages {
if err := machine.LoadImages(&cfg, k.c, images, constants.ImageCacheDir); err != nil {
if err := machine.LoadCachedImages(&cfg, k.c, images, constants.ImageCacheDir); err != nil {
out.FailureT("Unable to load cached images: {{.error}}", out.V{"error": err})
}
}
Expand Down
51 changes: 44 additions & 7 deletions pkg/minikube/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ var defaultPlatform = v1.Platform{
OS: "linux",
}

var (
useDaemon = true
useRemote = true
)

// UseDaemon is if we should look in local daemon for image ref
func UseDaemon(use bool) {
useDaemon = use
}

// UseRemote is if we should look in remote registry for image ref
func UseRemote(use bool) {
useRemote = use
}

// DigestByDockerLib uses client by docker lib to return image digest
// img.ID in as same as image digest
func DigestByDockerLib(imgClient *client.Client, imgName string) string {
Expand Down Expand Up @@ -198,20 +213,37 @@ func WriteImageToDaemon(img string) error {
}

func retrieveImage(ref name.Reference) (v1.Image, error) {
var err error
var img v1.Image

klog.Infof("retrieving image: %+v", ref)
if useDaemon {
img, err = retrieveDaemon(ref)
if err != nil {
return nil, err
}
}

if useRemote {
img, err = retrieveRemote(ref, defaultPlatform)
if err != nil {
return nil, err
}
return fixPlatform(ref, img, defaultPlatform)
}

return img, err
}

func retrieveDaemon(ref name.Reference) (v1.Image, error) {
img, err := daemon.Image(ref)
if err == nil {
klog.Infof("found %s locally: %+v", ref.Name(), img)
return img, nil
}
// reference does not exist in the local daemon
klog.Infof("daemon lookup for %+v: %v", ref, err)

img, err = retrieveRemote(ref, defaultPlatform)
if err != nil {
return nil, err
}
return fixPlatform(ref, img, defaultPlatform)
return img, err
}

func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
Expand All @@ -221,7 +253,12 @@ func retrieveRemote(ref name.Reference, p v1.Platform) (v1.Image, error) {
}

klog.Warningf("authn lookup for %+v (trying anon): %+v", ref, err)
return remote.Image(ref, remote.WithPlatform(p))
img, err = remote.Image(ref, remote.WithPlatform(p))
// reference does not exist in the remote registry
if err != nil {
klog.Infof("remote lookup for %+v: %v", ref, err)
}
return img, err
}

// See https://github.com/kubernetes/minikube/issues/10402
Expand Down
51 changes: 42 additions & 9 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func CacheImagesForBootstrapper(imageRepository string, version string, clusterB
return nil
}

// LoadImages loads previously cached images into the container runtime
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
// LoadCachedImages loads previously cached images into the container runtime
func LoadCachedImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
cr, err := cruntime.New(cruntime.Config{Type: cc.KubernetesConfig.ContainerRuntime, Runner: runner})
if err != nil {
return errors.Wrap(err, "runtime")
Expand All @@ -73,6 +73,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
klog.Infof("Images are preloaded, skipping loading")
return nil
}

klog.Infof("LoadImages start: %s", images)
start := time.Now()

Expand Down Expand Up @@ -102,7 +103,7 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
return nil
}
klog.Infof("%q needs transfer: %v", image, err)
return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
return transferAndLoadCachedImage(runner, cc.KubernetesConfig, image, cacheDir)
})
}
if err := g.Wait(); err != nil {
Expand Down Expand Up @@ -157,6 +158,22 @@ func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager
return nil
}

// LoadLocalImages loads images into the container runtime
func LoadLocalImages(cc *config.ClusterConfig, runner command.Runner, images []string) error {
var g errgroup.Group
for _, image := range images {
image := image
g.Go(func() error {
return transferAndLoadImage(runner, cc.KubernetesConfig, image)
})
}
if err := g.Wait(); err != nil {
return errors.Wrap(err, "loading images")
}
klog.Infoln("Successfully loaded all images")
return nil
}

// CacheAndLoadImages caches and loads images to all profiles
func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
if len(images) == 0 {
Expand All @@ -168,6 +185,11 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
return errors.Wrap(err, "save to dir")
}

return DoLoadImages(images, profiles, constants.ImageCacheDir)
}

// DoLoadImages loads images to all profiles
func DoLoadImages(images []string, profiles []*config.Profile, cacheDir string) error {
api, err := NewAPIClient()
if err != nil {
return errors.Wrap(err, "api")
Expand Down Expand Up @@ -209,7 +231,13 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
if err != nil {
return err
}
err = LoadImages(c, cr, images, constants.ImageCacheDir)
if cacheDir != "" {
// loading image names, from cache
err = LoadCachedImages(c, cr, images, cacheDir)
} else {
// loading image files
err = LoadLocalImages(c, cr, images)
}
if err != nil {
failed = append(failed, m)
klog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
Expand All @@ -226,15 +254,20 @@ func CacheAndLoadImages(images []string, profiles []*config.Profile) error {
return nil
}

// transferAndLoadImage transfers and loads a single image from the cache
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
// transferAndLoadCachedImage transfers and loads a single image from the cache
func transferAndLoadCachedImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
src := filepath.Join(cacheDir, imgName)
src = localpath.SanitizeCacheDir(src)
return transferAndLoadImage(cr, k8s, src)
}

// transferAndLoadImage transfers and loads a single image
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, src string) error {
r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
if err != nil {
return errors.Wrap(err, "runtime")
}
src := filepath.Join(cacheDir, imgName)
src = localpath.SanitizeCacheDir(src)
klog.Infof("Loading image from cache: %s", src)
klog.Infof("Loading image from: %s", src)
filename := filepath.Base(src)
if _, err := os.Stat(src); err != nil {
return err
Expand Down
20 changes: 17 additions & 3 deletions site/content/en/docs/commands/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,28 @@ minikube image help [command] [flags]

## minikube image load

Load a local image into minikube
Load a image into minikube

### Synopsis

Load a local image into minikube
Load a image into minikube

```shell
minikube image load [flags]
minikube image load IMAGE | ARCHIVE | - [flags]
```

### Examples

```
minikube image load image
minikube image load image.tar
```

### Options

```
--daemon Cache image from docker daemon
--remote Cache image from remote registry
```

### Options inherited from parent commands
Expand Down