Cache images for localkube

This PR introduces caching of localkube images.  It makes a best effort
to cache the essential images localkube needs as minikube starts up.
Currently, the list of cached images is hardcoded, but future work might
entail

1. Cached images as a property of the cluster bootstrapper - to allow
localkube and kubeadm to cache their respective images.
2. Addons contain image information.  Then, we can selectively cache and
preload only the addon images that are enabled.
pull/1881/head
Matt Rickard 2017-08-19 16:56:02 -07:00
parent 201e5f9eff
commit 55b41415ed
8 changed files with 257 additions and 5 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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,
}

View File

@ -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

View File

@ -41,6 +41,7 @@ import (
cfg "k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)

View File

@ -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")

View File

@ -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, &copy.Options{
SourceCtx: sourceCtx,
})
if err != nil {
return errors.Wrap(err, "copying image")
}
return nil
}

View File

@ -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)
}
}
}