diff --git a/Makefile b/Makefile index 1844d55bce..533f0d5432 100644 --- a/Makefile +++ b/Makefile @@ -67,11 +67,7 @@ KUBE_CROSS_DOCKER_CMD := docker run -w /go/src/$(REPOPATH) --user $(shell id -u) # $(call MINIKUBE_GO_BUILD_CMD, output file, OS) define MINIKUBE_GO_BUILD_CMD -<<<<<<< HEAD - $(MINIKUBE_ENV_$(2)) go build --installsuffix cgo -ldflags="$(MINIKUBE_LDFLAGS) $(K8S_VERSION_LDFLAGS)" -a -o $(1) k8s.io/minikube/cmd/minikube -======= $(MINIKUBE_ENV_$(2)) go build -tags "container_image_ostree_stub containers_image_openpgp" --installsuffix cgo -ldflags="$(MINIKUBE_LDFLAGS) $(K8S_VERSION_LDFLAGS)" -a -o $(1) k8s.io/minikube/cmd/minikube ->>>>>>> a98f9553f... Vendor changes endef ifeq ($(BUILD_IN_DOCKER),y) diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 9106a64a8a..5cf2fd6bdf 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -86,6 +86,7 @@ assumes you have already installed one of the VM drivers: virtualbox/vmwarefusio } func runStart(cmd *cobra.Command, args []string) { + go machine.CacheImagesForBootstrapper(viper.GetString(cmdcfg.Bootstrapper)) api, err := machine.NewAPIClient() if err != nil { fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err) diff --git a/pkg/minikube/bootstrapper/bootstrapper.go b/pkg/minikube/bootstrapper/bootstrapper.go index 57fbfe8c9c..eb12a0dcee 100644 --- a/pkg/minikube/bootstrapper/bootstrapper.go +++ b/pkg/minikube/bootstrapper/bootstrapper.go @@ -16,7 +16,10 @@ limitations under the License. package bootstrapper -import "k8s.io/minikube/pkg/util" +import ( + "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/util" +) // Bootstrapper contains all the methods needed to bootstrap a kubernetes cluster type Bootstrapper interface { @@ -44,3 +47,7 @@ type KubernetesConfig struct { const ( BootstrapperTypeLocalkube = "localkube" ) + +var CachedImagesForBootstrapper = map[string][]string{ + BootstrapperTypeLocalkube: constants.LocalkubeCachedImages, +} diff --git a/pkg/minikube/bootstrapper/localkube/localkube.go b/pkg/minikube/bootstrapper/localkube/localkube.go index c7032a2842..924a976333 100644 --- a/pkg/minikube/bootstrapper/localkube/localkube.go +++ b/pkg/minikube/bootstrapper/localkube/localkube.go @@ -24,6 +24,7 @@ import ( "k8s.io/minikube/pkg/minikube/bootstrapper" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/sshutil" "github.com/docker/machine/libmachine" @@ -105,6 +106,9 @@ func (lk *LocalkubeBootstrapper) RestartCluster(kubernetesConfig bootstrapper.Ku } func (lk *LocalkubeBootstrapper) UpdateCluster(config bootstrapper.KubernetesConfig) error { + // Make best effort to load any cached images + go machine.LoadImages(lk.cmd, constants.LocalkubeCachedImages, constants.ImageCacheDir) + copyableFiles := []assets.CopyableFile{} var localkubeFile assets.CopyableFile var err error diff --git a/pkg/minikube/cluster/cluster.go b/pkg/minikube/cluster/cluster.go index 0add02e830..04d99293ad 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -41,6 +41,7 @@ import ( cfg "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/util" ) diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index 0238fbd383..011f7f2275 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -146,3 +146,21 @@ const ( const IsMinikubeChildProcess = "IS_MINIKUBE_CHILD_PROCESS" const DriverNone = "none" const FileScheme = "file" + +var LocalkubeCachedImages = []string{ + // Dashboard + "gcr.io/google_containers/kubernetes-dashboard-amd64:v1.6.3", + + // DNS + "gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.4", + "gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.4", + "gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.4", + + // Addon Manager + "gcr.io/google-containers/kube-addon-manager:v6.4-beta.2", + + // Pause + "gcr.io/google_containers/pause-amd64:3.0", +} + +var ImageCacheDir = MakeMiniPath("cache", "images") diff --git a/pkg/minikube/machine/cache_images.go b/pkg/minikube/machine/cache_images.go new file mode 100644 index 0000000000..8a4a9eabf3 --- /dev/null +++ b/pkg/minikube/machine/cache_images.go @@ -0,0 +1,194 @@ +/* +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 ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + "golang.org/x/sync/errgroup" + + "k8s.io/minikube/pkg/minikube/assets" + "k8s.io/minikube/pkg/minikube/bootstrapper" + "k8s.io/minikube/pkg/minikube/constants" + + "github.com/containers/image/copy" + "github.com/containers/image/docker" + "github.com/containers/image/docker/archive" + "github.com/containers/image/signature" + "github.com/containers/image/types" + "github.com/golang/glog" + "github.com/pkg/errors" +) + +const tempLoadDir = "/tmp" + +func CacheImagesForBootstrapper(clusterBootstrapper string) error { + images, ok := bootstrapper.CachedImagesForBootstrapper[clusterBootstrapper] + if !ok { + glog.Infoln("Could not find list of images to cache for bootstrapper %s", clusterBootstrapper) + return nil + } + + if err := CacheImages(images, constants.ImageCacheDir); err != nil { + return errors.Wrapf(err, "Caching images for %s", clusterBootstrapper) + } + + return nil +} + +func CacheImages(images []string, cacheDir string) error { + var g errgroup.Group + for _, image := range images { + image := image + g.Go(func() error { + dst := filepath.Join(cacheDir, image) + dst = sanitizeCacheDir(dst) + if err := CacheImage(image, dst); err != nil { + return errors.Wrapf(err, "caching image %s", dst) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return errors.Wrap(err, "caching images") + } + glog.Infoln("Successfully cached all images.") + return nil +} + +func LoadImages(cmd bootstrapper.CommandRunner, images []string, cacheDir string) error { + var g errgroup.Group + for _, image := range images { + image := image + g.Go(func() error { + src := filepath.Join(cacheDir, image) + src = sanitizeCacheDir(src) + if err := LoadFromCacheBlocking(cmd, src); err != nil { + return errors.Wrapf(err, "loading image %s", src) + } + return nil + }) + } + if err := g.Wait(); err != nil { + return errors.Wrap(err, "loading cached images") + } + glog.Infoln("Successfully loaded all cached images.") + return nil +} + +// # ParseReference cannot have a : in the directory path +func sanitizeCacheDir(image string) string { + return strings.Replace(image, ":", "_", -1) +} + +func LoadFromCacheBlocking(cmd bootstrapper.CommandRunner, src string) error { + glog.Infoln("Loading image from cache at ", src) + filename := filepath.Base(src) + for { + if _, err := os.Stat(src); err == nil { + break + } + } + dst := filepath.Join(tempLoadDir, filename) + f, err := assets.NewFileAsset(src, tempLoadDir, filename, "0777") + if err != nil { + return errors.Wrapf(err, "creating copyable file asset: %s", filename) + } + if err := cmd.Copy(f); err != nil { + return errors.Wrap(err, "transferring cached image") + } + + dockerLoadCmd := "docker load -i " + dst + + if err := cmd.Run(dockerLoadCmd); err != nil { + return errors.Wrapf(err, "loading docker image: %s", dst) + } + + if err := cmd.Run("rm -rf " + dst); err != nil { + return errors.Wrap(err, "deleting temp docker image location") + } + + glog.Infof("Successfully loaded image %s from cache", src) + return nil +} + +func getSrcRef(image string) (types.ImageReference, error) { + srcRef, err := docker.ParseReference("//" + image) + if err != nil { + return nil, errors.Wrap(err, "parsing docker image src ref") + } + return srcRef, nil +} + +func getDstRef(image, dst string) (types.ImageReference, error) { + dstRef, err := archive.ParseReference(dst + ":" + image) + if err != nil { + return nil, errors.Wrap(err, "parsing docker archive dst ref") + } + return dstRef, nil +} + +func CacheImage(image, dst string) error { + glog.Infof("Attempting to cache image: %s at %s\n", image, dst) + if _, err := os.Stat(dst); err == nil { + return nil + } + + if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { + return errors.Wrapf(err, "making cache image directory: %s", dst) + } + + srcRef, err := getSrcRef(image) + if err != nil { + return errors.Wrap(err, "creating docker image src ref") + } + + dstRef, err := getDstRef(image, dst) + if err != nil { + return errors.Wrap(err, "creating docker archive dst ref") + } + + policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}} + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return errors.Wrap(err, "getting policy context") + } + + tmp, err := ioutil.TempDir("", "") + if err != nil { + return errors.Wrap(err, "making temp dir") + } + defer os.RemoveAll(tmp) + sourceCtx := &types.SystemContext{ + // By default, the image library will try to look at /etc/docker/certs.d + // As a non-root user, this would result in a permissions error, + // so, we skip this step by just looking in a newly created tmpdir. + DockerCertPath: tmp, + } + + err = copy.Image(policyContext, dstRef, srcRef, ©.Options{ + SourceCtx: sourceCtx, + }) + if err != nil { + return errors.Wrap(err, "copying image") + } + + return nil +} diff --git a/pkg/minikube/machine/cache_images_test.go b/pkg/minikube/machine/cache_images_test.go new file mode 100644 index 0000000000..2ecc884feb --- /dev/null +++ b/pkg/minikube/machine/cache_images_test.go @@ -0,0 +1,31 @@ +/* +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 ( + "testing" + + "k8s.io/minikube/pkg/minikube/constants" +) + +func TestGetSrcRef(t *testing.T) { + for _, image := range constants.LocalkubeCachedImages { + if _, err := getSrcRef(image); err != nil { + t.Errorf("Error getting src ref for %s: %s", image, err) + } + } +}