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
parent
201e5f9eff
commit
55b41415ed
4
Makefile
4
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)
|
# $(call MINIKUBE_GO_BUILD_CMD, output file, OS)
|
||||||
define MINIKUBE_GO_BUILD_CMD
|
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
|
$(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
|
endef
|
||||||
|
|
||||||
ifeq ($(BUILD_IN_DOCKER),y)
|
ifeq ($(BUILD_IN_DOCKER),y)
|
||||||
|
|
|
@ -86,6 +86,7 @@ assumes you have already installed one of the VM drivers: virtualbox/vmwarefusio
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStart(cmd *cobra.Command, args []string) {
|
func runStart(cmd *cobra.Command, args []string) {
|
||||||
|
go machine.CacheImagesForBootstrapper(viper.GetString(cmdcfg.Bootstrapper))
|
||||||
api, err := machine.NewAPIClient()
|
api, err := machine.NewAPIClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err)
|
fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err)
|
||||||
|
|
|
@ -16,7 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
package bootstrapper
|
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
|
// Bootstrapper contains all the methods needed to bootstrap a kubernetes cluster
|
||||||
type Bootstrapper interface {
|
type Bootstrapper interface {
|
||||||
|
@ -44,3 +47,7 @@ type KubernetesConfig struct {
|
||||||
const (
|
const (
|
||||||
BootstrapperTypeLocalkube = "localkube"
|
BootstrapperTypeLocalkube = "localkube"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var CachedImagesForBootstrapper = map[string][]string{
|
||||||
|
BootstrapperTypeLocalkube: constants.LocalkubeCachedImages,
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
"k8s.io/minikube/pkg/minikube/bootstrapper"
|
||||||
"k8s.io/minikube/pkg/minikube/config"
|
"k8s.io/minikube/pkg/minikube/config"
|
||||||
"k8s.io/minikube/pkg/minikube/constants"
|
"k8s.io/minikube/pkg/minikube/constants"
|
||||||
|
"k8s.io/minikube/pkg/minikube/machine"
|
||||||
"k8s.io/minikube/pkg/minikube/sshutil"
|
"k8s.io/minikube/pkg/minikube/sshutil"
|
||||||
|
|
||||||
"github.com/docker/machine/libmachine"
|
"github.com/docker/machine/libmachine"
|
||||||
|
@ -105,6 +106,9 @@ func (lk *LocalkubeBootstrapper) RestartCluster(kubernetesConfig bootstrapper.Ku
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lk *LocalkubeBootstrapper) UpdateCluster(config bootstrapper.KubernetesConfig) error {
|
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{}
|
copyableFiles := []assets.CopyableFile{}
|
||||||
var localkubeFile assets.CopyableFile
|
var localkubeFile assets.CopyableFile
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -41,6 +41,7 @@ import (
|
||||||
|
|
||||||
cfg "k8s.io/minikube/pkg/minikube/config"
|
cfg "k8s.io/minikube/pkg/minikube/config"
|
||||||
"k8s.io/minikube/pkg/minikube/constants"
|
"k8s.io/minikube/pkg/minikube/constants"
|
||||||
|
|
||||||
"k8s.io/minikube/pkg/util"
|
"k8s.io/minikube/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -146,3 +146,21 @@ const (
|
||||||
const IsMinikubeChildProcess = "IS_MINIKUBE_CHILD_PROCESS"
|
const IsMinikubeChildProcess = "IS_MINIKUBE_CHILD_PROCESS"
|
||||||
const DriverNone = "none"
|
const DriverNone = "none"
|
||||||
const FileScheme = "file"
|
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")
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue