cache_images.go 7.9 KB
Newer Older
M
Matt Rickard 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package machine

import (
20
	"fmt"
M
Matt Rickard 已提交
21
	"os"
22
	"path"
M
Matt Rickard 已提交
23
	"path/filepath"
24
	"strings"
25
	"sync"
26
	"time"
M
Matt Rickard 已提交
27

28
	"github.com/docker/docker/client"
29
	"github.com/docker/machine/libmachine/state"
30
	"github.com/pkg/errors"
K
kairen 已提交
31
	"golang.org/x/sync/errgroup"
32
	"k8s.io/klog/v2"
M
Matt Rickard 已提交
33 34
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/bootstrapper"
35
	"k8s.io/minikube/pkg/minikube/command"
P
Priya Wadhwa 已提交
36
	"k8s.io/minikube/pkg/minikube/config"
M
Matt Rickard 已提交
37
	"k8s.io/minikube/pkg/minikube/constants"
38
	"k8s.io/minikube/pkg/minikube/cruntime"
S
Sharif Elgamal 已提交
39
	"k8s.io/minikube/pkg/minikube/driver"
40 41
	"k8s.io/minikube/pkg/minikube/image"
	"k8s.io/minikube/pkg/minikube/localpath"
42
	"k8s.io/minikube/pkg/minikube/vmpath"
M
Matt Rickard 已提交
43 44
)

45
// loadRoot is where images should be loaded from within the guest VM
46
var loadRoot = path.Join(vmpath.GuestPersistentDir, "images")
M
Matt Rickard 已提交
47

48 49
// loadImageLock is used to serialize image loads to avoid overloading the guest VM
var loadImageLock sync.Mutex
50

51
// CacheImagesForBootstrapper will cache images for a bootstrapper
52
func CacheImagesForBootstrapper(imageRepository string, version string, clusterBootstrapper string) error {
53 54 55 56
	images, err := bootstrapper.GetCachedImageList(imageRepository, version, clusterBootstrapper)
	if err != nil {
		return errors.Wrap(err, "cached images list")
	}
M
Matt Rickard 已提交
57

M
Medya Gh 已提交
58
	if err := image.SaveToDir(images, constants.ImageCacheDir); err != nil {
M
Matt Rickard 已提交
59 60 61 62 63 64
		return errors.Wrapf(err, "Caching images for %s", clusterBootstrapper)
	}

	return nil
}

65
// LoadImages loads previously cached images into the container runtime
66
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
67 68 69 70 71
	cr, err := cruntime.New(cruntime.Config{Type: cc.KubernetesConfig.ContainerRuntime, Runner: runner})
	if err != nil {
		return errors.Wrap(err, "runtime")
	}

M
Medya Gh 已提交
72
	// Skip loading images if images already exist
73
	if cr.ImagesPreloaded(images) {
74
		klog.Infof("Images are preloaded, skipping loading")
M
Medya Gh 已提交
75
		return nil
76
	}
77
	klog.Infof("LoadImages start: %s", images)
78 79 80
	start := time.Now()

	defer func() {
81
		klog.Infof("LoadImages completed in %s", time.Since(start))
82 83
	}()

M
Matt Rickard 已提交
84
	var g errgroup.Group
85

86 87 88 89
	var imgClient *client.Client
	if cr.Name() == "Docker" {
		imgClient, err = client.NewClientWithOpts(client.FromEnv) // image client
		if err != nil {
90
			klog.Infof("couldn't get a local image daemon which might be ok: %v", err)
91
		}
92 93
	}

M
Matt Rickard 已提交
94 95 96
	for _, image := range images {
		image := image
		g.Go(func() error {
97 98 99 100 101
			// Put a ten second limit on deciding if an image needs transfer
			// because it takes much less than that time to just transfer the image.
			// This is needed because if running in offline mode, we can spend minutes here
			// waiting for i/o timeout.
			err := timedNeedsTransfer(imgClient, image, cr, 10*time.Second)
102
			if err == nil {
M
lint  
Medya Gh 已提交
103
				return nil
M
Medya Gh 已提交
104
			}
105
			klog.Infof("%q needs transfer: %v", image, err)
106
			return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
M
Matt Rickard 已提交
107 108 109 110 111
		})
	}
	if err := g.Wait(); err != nil {
		return errors.Wrap(err, "loading cached images")
	}
112
	klog.Infoln("Successfully loaded all cached images")
113 114 115
	return nil
}

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
func timedNeedsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager, t time.Duration) error {
	timeout := make(chan bool, 1)
	go func() {
		time.Sleep(t)
		timeout <- true
	}()

	transferFinished := make(chan bool, 1)
	var err error
	go func() {
		err = needsTransfer(imgClient, imgName, cr)
		transferFinished <- true
	}()

	select {
	case <-transferFinished:
		return err
	case <-timeout:
		return fmt.Errorf("needs transfer timed out in %f seconds", t.Seconds())
	}
}

138
// needsTransfer returns an error if an image needs to be retransfered
139
func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager) error {
140
	imgDgst := ""         // for instance sha256:7c92a2c6bbcb6b6beff92d0a940779769c2477b807c202954c537e2e0deb9bed
141
	if imgClient != nil { // if possible try to get img digest from Client lib which is 4s faster.
M
Medya Gh 已提交
142
		imgDgst = image.DigestByDockerLib(imgClient, imgName)
143 144 145
		if imgDgst != "" {
			if !cr.ImageExists(imgName, imgDgst) {
				return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
146 147 148 149 150
			}
			return nil
		}
	}
	// if not found with method above try go-container lib (which is 4s slower)
M
Medya Gh 已提交
151
	imgDgst = image.DigestByGoLib(imgName)
152 153
	if imgDgst == "" {
		return fmt.Errorf("got empty img digest %q for %s", imgDgst, imgName)
154
	}
155 156
	if !cr.ImageExists(imgName, imgDgst) {
		return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
157 158 159 160
	}
	return nil
}

161
// CacheAndLoadImages caches and loads images to all profiles
P
Priya Wadhwa 已提交
162
func CacheAndLoadImages(images []string) error {
163 164 165 166
	if len(images) == 0 {
		return nil
	}

167
	// This is the most important thing
M
Medya Gh 已提交
168
	if err := image.SaveToDir(images, constants.ImageCacheDir); err != nil {
169
		return errors.Wrap(err, "save to dir")
P
Priya Wadhwa 已提交
170
	}
171

P
Priya Wadhwa 已提交
172 173
	api, err := NewAPIClient()
	if err != nil {
174
		return errors.Wrap(err, "api")
P
Priya Wadhwa 已提交
175 176
	}
	defer api.Close()
177
	profiles, _, err := config.ListProfiles() // need to load image to all profiles
178
	if err != nil {
179
		return errors.Wrap(err, "list profiles")
180
	}
181 182 183 184

	succeeded := []string{}
	failed := []string{}

M
Medya Gh 已提交
185
	for _, p := range profiles { // loading images to all running profiles
186
		pName := p.Name // capture the loop variable
187

S
Sharif Elgamal 已提交
188
		c, err := config.Load(pName)
189
		if err != nil {
190
			// Non-fatal because it may race with profile deletion
191
			klog.Errorf("Failed to load profile %q: %v", pName, err)
192 193
			failed = append(failed, pName)
			continue
194
		}
195

S
Sharif Elgamal 已提交
196
		for _, n := range c.Nodes {
197
			m := driver.MachineName(*c, n)
198

199
			status, err := Status(api, m)
200
			if err != nil {
201
				klog.Warningf("error getting status for %s: %v", m, err)
S
Sharif Elgamal 已提交
202
				failed = append(failed, m)
203
				continue
204
			}
205

S
Sharif Elgamal 已提交
206 207 208
			if status == state.Running.String() { // the not running hosts will load on next start
				h, err := api.Load(m)
				if err != nil {
209
					klog.Warningf("Failed to load machine %q: %v", m, err)
S
Sharif Elgamal 已提交
210
					failed = append(failed, m)
211
					continue
S
Sharif Elgamal 已提交
212 213 214 215 216 217 218
				}
				cr, err := CommandRunner(h)
				if err != nil {
					return err
				}
				err = LoadImages(c, cr, images, constants.ImageCacheDir)
				if err != nil {
S
Sharif Elgamal 已提交
219
					failed = append(failed, m)
220
					klog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
S
Sharif Elgamal 已提交
221
				}
S
Sharif Elgamal 已提交
222
				succeeded = append(succeeded, m)
223 224
			}
		}
P
Priya Wadhwa 已提交
225
	}
226

227 228
	klog.Infof("succeeded pushing to: %s", strings.Join(succeeded, " "))
	klog.Infof("failed pushing to: %s", strings.Join(failed, " "))
229 230
	// Live pushes are not considered a failure
	return nil
P
Priya Wadhwa 已提交
231 232
}

233
// transferAndLoadImage transfers and loads a single image from the cache
234 235 236 237 238 239
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir 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)
240
	src = localpath.SanitizeCacheDir(src)
241
	klog.Infof("Loading image from cache: %s", src)
M
Matt Rickard 已提交
242
	filename := filepath.Base(src)
243
	if _, err := os.Stat(src); err != nil {
244
		return err
M
Matt Rickard 已提交
245
	}
246 247
	dst := path.Join(loadRoot, filename)
	f, err := assets.NewFileAsset(src, loadRoot, filename, "0644")
M
Matt Rickard 已提交
248 249 250
	if err != nil {
		return errors.Wrapf(err, "creating copyable file asset: %s", filename)
	}
251
	if err := cr.Copy(f); err != nil {
M
Matt Rickard 已提交
252 253 254
		return errors.Wrap(err, "transferring cached image")
	}

255 256
	loadImageLock.Lock()
	defer loadImageLock.Unlock()
M
Matt Rickard 已提交
257

258 259 260
	err = r.LoadImage(dst)
	if err != nil {
		return errors.Wrapf(err, "%s load %s", r.Name(), dst)
261 262
	}

263
	klog.Infof("Transferred and loaded %s from cache", src)
M
Matt Rickard 已提交
264 265
	return nil
}