cache_images.go 7.8 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"
39 40
	"k8s.io/minikube/pkg/minikube/image"
	"k8s.io/minikube/pkg/minikube/localpath"
41
	"k8s.io/minikube/pkg/minikube/vmpath"
M
Matt Rickard 已提交
42 43
)

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

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

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

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

61 62 63
	return nil
}

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

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

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

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

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

M
Matt Rickard 已提交
93 94 95
	for _, image := range images {
		image := image
		g.Go(func() error {
96 97 98 99 100
			// 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)
101
			if err == nil {
M
lint  
Medya Gh 已提交
102
				return nil
M
Medya Gh 已提交
103
			}
104
			klog.Infof("%q needs transfer: %v", image, err)
105
			return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
M
Matt Rickard 已提交
106 107 108 109 110
		})
	}
	if err := g.Wait(); err != nil {
		return errors.Wrap(err, "loading cached images")
	}
111
	klog.Infoln("Successfully loaded all cached images")
112 113 114
	return nil
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
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())
	}
}

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

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

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

P
Priya Wadhwa 已提交
171 172
	api, err := NewAPIClient()
	if err != nil {
173
		return errors.Wrap(err, "api")
P
Priya Wadhwa 已提交
174 175
	}
	defer api.Close()
176 177 178 179

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

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

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

S
Sharif Elgamal 已提交
191
		for _, n := range c.Nodes {
D
Daehyeok Mun 已提交
192
			m := config.MachineName(*c, n)
193

194
			status, err := Status(api, m)
195
			if err != nil {
196
				klog.Warningf("error getting status for %s: %v", m, err)
S
Sharif Elgamal 已提交
197
				failed = append(failed, m)
198
				continue
199
			}
200

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

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

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

251 252
	loadImageLock.Lock()
	defer loadImageLock.Unlock()
M
Matt Rickard 已提交
253

254 255 256
	err = r.LoadImage(dst)
	if err != nil {
		return errors.Wrapf(err, "%s load %s", r.Name(), dst)
257 258
	}

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