Merge pull request #6720 from priyawadhwa/preload-images-into-docker-volume

Preload images into docker volume
pull/6870/head
priyawadhwa 2020-03-03 10:25:01 -08:00 committed by GitHub
commit 22ca04aa80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 472 additions and 70 deletions

View File

@ -19,8 +19,10 @@ VERSION_BUILD ?= 3
RAW_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).${VERSION_BUILD}
VERSION ?= v$(RAW_VERSION)
KUBERNETES_VERSION ?= $(shell egrep "^var DefaultKubernetesVersion" pkg/minikube/constants/constants.go | cut -d \" -f2)
KUBERNETES_VERSION ?= $(shell egrep "DefaultKubernetesVersion =" pkg/minikube/constants/constants.go | cut -d \" -f2)
KIC_VERSION ?= $(shell egrep "Version =" pkg/drivers/kic/types.go | cut -d \" -f2)
PRELOADED_TARBALL_VERSION ?= $(shell egrep "Version =" pkg/minikube/preload/constants.go | cut -d \" -f2)
PRELOADED_VOLUMES_GCS_BUCKET ?= $(shell egrep "PreloadedVolumeTarballsBucket =" pkg/minikube/constants/constants.go | cut -d \" -f2)
# Default to .0 for higher cache hit rates, as build increments typically don't require new ISO versions
ISO_VERSION ?= v$(VERSION_MAJOR).$(VERSION_MINOR).3
@ -521,15 +523,14 @@ kic-base-image: ## builds the base image used for kic.
docker rmi -f $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot || true
docker build -f ./hack/images/kicbase.Dockerfile -t $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --target base .
.PHONY: kic-preloaded-base-image
kic-preloaded-base-image: generate-preloaded-images-tar ## builds the base image used for kic.
docker rmi -f $(REGISTRY)/kicbase:$(KIC_VERSION)-k8s-${KUBERNETES_VERSION} || true
docker build -f ./hack/images/kicbase.Dockerfile -t $(REGISTRY)/kicbase:$(KIC_VERSION)-k8s-${KUBERNETES_VERSION} --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --build-arg KUBERNETES_VERSION=${KUBERNETES_VERSION} .
.PHONY: upload-preloaded-images-tar
upload-preloaded-images-tar: generate-preloaded-images-tar # Upload the preloaded images tar to the GCS bucket. Specify a specific kubernetes version to build via `KUBERNETES_VERSION=vx.y.z make upload-preloaded-images-tar`.
gsutil cp out/preloaded-images-k8s-${PRELOADED_TARBALL_VERSION}-${KUBERNETES_VERSION}-docker-overlay2.tar.lz4 gs://${PRELOADED_VOLUMES_GCS_BUCKET}
gsutil acl ch -u AllUsers:R gs://${PRELOADED_VOLUMES_GCS_BUCKET}/preloaded-images-k8s-${PRELOADED_TARBALL_VERSION}-${KUBERNETES_VERSION}-docker-overlay2.tar.lz4
.PHONY: generate-preloaded-images-tar
generate-preloaded-images-tar: out/minikube
go run ./hack/preload-images/preload_images.go -kubernetes-version ${KUBERNETES_VERSION}
generate-preloaded-images-tar:
go run ./hack/preload-images/preload_images.go -kubernetes-version ${KUBERNETES_VERSION} -preloaded-tarball-version ${PRELOADED_TARBALL_VERSION}
.PHONY: push-storage-provisioner-image

2
go.mod
View File

@ -3,6 +3,7 @@ module k8s.io/minikube
go 1.13
require (
cloud.google.com/go v0.45.1
github.com/Parallels/docker-machine-parallels v1.3.0
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/blang/semver v3.5.0+incompatible
@ -69,6 +70,7 @@ require (
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20191010194322-b09406accb47
golang.org/x/text v0.3.2
google.golang.org/api v0.9.0
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
k8s.io/api v0.17.3
k8s.io/apimachinery v0.17.3

View File

@ -52,13 +52,3 @@ RUN apt-get clean -y && rm -rf \
/usr/share/man/* \
/usr/share/local/* \
RUN echo "kic! Build: ${COMMIT_SHA} Time :$(date)" > "/kic.txt"
FROM busybox
ARG KUBERNETES_VERSION
COPY out/preloaded-images-k8s-$KUBERNETES_VERSION.tar /preloaded-images.tar
RUN tar xvf /preloaded-images.tar -C /
FROM base
COPY --from=1 /var/lib/docker /var/lib/docker
COPY --from=1 /var/lib/minikube/binaries /var/lib/minikube/binaries

View File

@ -25,6 +25,11 @@ import (
"strings"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/drivers/kic"
"k8s.io/minikube/pkg/drivers/kic/oci"
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/localpath"
)
const (
@ -33,17 +38,28 @@ const (
)
var (
kubernetesVersion = ""
tarballFilename = ""
kubernetesVersion = ""
tarballFilename = ""
dockerStorageDriver = ""
preloadedTarballVersion = ""
containerRuntime = ""
)
func init() {
flag.StringVar(&kubernetesVersion, "kubernetes-version", "", "desired kubernetes version, for example `v1.17.2`")
flag.StringVar(&dockerStorageDriver, "docker-storage-driver", "overlay2", "docker storage driver backend")
flag.StringVar(&preloadedTarballVersion, "preloaded-tarball-version", "", "preloaded tarball version")
flag.StringVar(&containerRuntime, "container-runtime", "docker", "container runtime")
flag.Parse()
tarballFilename = fmt.Sprintf("preloaded-images-k8s-%s.tar", kubernetesVersion)
tarballFilename = fmt.Sprintf("preloaded-images-k8s-%s-%s-%s-%s.tar.lz4", preloadedTarballVersion, kubernetesVersion, containerRuntime, dockerStorageDriver)
}
func main() {
if err := verifyDockerStorage(); err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := executePreloadImages(); err != nil {
fmt.Println(err)
os.Exit(1)
@ -56,42 +72,74 @@ func executePreloadImages() error {
fmt.Println(err)
}
}()
if err := startMinikube(); err != nil {
driver := kic.NewDriver(kic.Config{
KubernetesVersion: kubernetesVersion,
ContainerRuntime: driver.Docker,
OCIBinary: oci.Docker,
MachineName: profile,
ImageDigest: kic.BaseImage,
StorePath: localpath.MiniPath(),
CPU: 2,
Memory: 4000,
APIServerPort: 8080,
})
baseDir := filepath.Dir(driver.GetSSHKeyPath())
defer os.Remove(baseDir)
if err := os.MkdirAll(baseDir, 0755); err != nil {
return err
}
if err := driver.Create(); err != nil {
return errors.Wrap(err, "creating kic driver")
}
// Now, get images to pull
imgs, err := images.Kubeadm("", kubernetesVersion)
if err != nil {
return errors.Wrap(err, "kubeadm images")
}
for _, img := range append(imgs, kic.OverlayImage) {
cmd := exec.Command("docker", "exec", profile, "docker", "pull", img)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "downloading %s", img)
}
}
// Create image tarball
if err := createImageTarball(); err != nil {
return err
}
return copyTarballToHost()
}
func startMinikube() error {
cmd := exec.Command(minikubePath, "start", "-p", profile, "--memory", "4000", "--kubernetes-version", kubernetesVersion, "--wait=false")
cmd.Stdout = os.Stdout
return cmd.Run()
}
func createImageTarball() error {
cmd := exec.Command(minikubePath, "ssh", "-p", profile, "--", "sudo", "tar", "cvf", tarballFilename, "/var/lib/docker", "/var/lib/minikube/binaries")
dirs := []string{
fmt.Sprintf("./lib/docker/%s", dockerStorageDriver),
"./lib/docker/image",
}
args := []string{"exec", profile, "sudo", "tar", "-I", "lz4", "-C", "/var", "-cvf", tarballFilename}
args = append(args, dirs...)
cmd := exec.Command("docker", args...)
cmd.Stdout = os.Stdout
return cmd.Run()
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "creating image tarball")
}
return nil
}
func copyTarballToHost() error {
sshKey, err := runCmd([]string{minikubePath, "ssh-key", "-p", profile})
if err != nil {
return errors.Wrap(err, "getting ssh-key")
}
ip, err := runCmd([]string{minikubePath, "ip", "-p", profile})
if err != nil {
return errors.Wrap(err, "getting ip")
}
dest := filepath.Join("out/", tarballFilename)
args := []string{"scp", "-o", "StrictHostKeyChecking=no", "-i", sshKey, fmt.Sprintf("docker@%s:/home/docker/%s", ip, tarballFilename), dest}
_, err = runCmd(args)
return err
cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:/%s", profile, tarballFilename), dest)
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "copying tarball to host")
}
return nil
}
func deleteMinikube() error {
@ -100,8 +148,15 @@ func deleteMinikube() error {
return cmd.Run()
}
func runCmd(command []string) (string, error) {
cmd := exec.Command(command[0], command[1:]...)
func verifyDockerStorage() error {
cmd := exec.Command("docker", "info", "-f", "{{.Info.Driver}}")
output, err := cmd.Output()
return strings.Trim(string(output), "\n "), err
if err != nil {
return err
}
driver := strings.Trim(string(output), " \n")
if driver != dockerStorageDriver {
return fmt.Errorf("docker storage driver %s does not match requested %s", driver, dockerStorageDriver)
}
return nil
}

View File

@ -36,6 +36,7 @@ import (
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/preload"
)
// Driver represents a kic driver https://minikube.sigs.k8s.io/docs/reference/drivers/docker
@ -90,14 +91,23 @@ func (d *Driver) Create() error {
ContainerPort: constants.DockerDaemonPort,
},
)
err := oci.CreateContainerNode(params)
if err != nil {
if err := oci.CreateContainerNode(params); err != nil {
return errors.Wrap(err, "create kic node")
}
if err := d.prepareSSH(); err != nil {
return errors.Wrap(err, "prepare kic ssh")
}
t := time.Now()
glog.Infof("Starting extracting preloaded images to volume")
// Extract preloaded images to container
if err := oci.ExtractTarballToVolume(preload.TarballFilepath(d.NodeConfig.KubernetesVersion), params.Name, BaseImage); err != nil {
glog.Infof("Unable to extract preloaded tarball to volume: %v", err)
} else {
glog.Infof("Took %f seconds to extract preloaded images to volume", time.Since(t).Seconds())
}
return nil
}

View File

@ -103,6 +103,19 @@ func allVolumesByLabel(ociBin string, label string) ([]string, error) {
return vols, err
}
// ExtractTarballToVolume runs a docker image imageName which extracts the tarball at tarballPath
// to the volume named volumeName
func ExtractTarballToVolume(tarballPath, volumeName, imageName string) error {
if err := PointToHostDockerDaemon(); err != nil {
return errors.Wrap(err, "point host docker-daemon")
}
cmd := exec.Command(Docker, "run", "--rm", "--entrypoint", "/usr/bin/tar", "-v", fmt.Sprintf("%s:/preloaded.tar:ro", tarballPath), "-v", fmt.Sprintf("%s:/extractDir", volumeName), imageName, "-I", "lz4", "-xvf", "/preloaded.tar", "-C", "/extractDir")
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Wrapf(err, "output %s", string(out))
}
return nil
}
// createDockerVolume creates a docker volume to be attached to the container with correct labels and prefixes based on profile name
// Caution ! if volume already exists does NOT return an error and will not apply the minikube labels on it.
// TODO: this should be fixed as a part of https://github.com/kubernetes/minikube/issues/6530

View File

@ -46,14 +46,16 @@ var (
// Config is configuration for the kic driver used by registry
type Config struct {
MachineName string // maps to the container name being created
CPU int // Number of CPU cores assigned to the container
Memory int // max memory in MB
StorePath string // libmachine store path
OCIBinary string // oci tool to use (docker, podman,...)
ImageDigest string // image name with sha to use for the node
Mounts []oci.Mount // mounts
APIServerPort int // kubernetes api server port inside the container
PortMappings []oci.PortMapping // container port mappings
Envs map[string]string // key,value of environment variables passed to the node
MachineName string // maps to the container name being created
CPU int // Number of CPU cores assigned to the container
Memory int // max memory in MB
StorePath string // libmachine store path
OCIBinary string // oci tool to use (docker, podman,...)
ImageDigest string // image name with sha to use for the node
Mounts []oci.Mount // mounts
APIServerPort int // kubernetes api server port inside the container
PortMappings []oci.PortMapping // container port mappings
Envs map[string]string // key,value of environment variables passed to the node
KubernetesVersion string // kubernetes version to install
ContainerRuntime string // container runtime kic is running
}

View File

@ -63,6 +63,9 @@ const (
MinikubeActiveDockerdEnv = "MINIKUBE_ACTIVE_DOCKERD"
// PodmanVarlinkBridgeEnv is used for podman settings
PodmanVarlinkBridgeEnv = "PODMAN_VARLINK_BRIDGE"
// PreloadedVolumeTarballsBucket is the name of the GCS bucket where preloaded volume tarballs exist
PreloadedVolumeTarballsBucket = "minikube-preloaded-volume-tarballs"
)
var (

View File

@ -20,8 +20,10 @@ import (
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/docker/docker/client"
@ -31,6 +33,8 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/drivers/kic/oci"
"k8s.io/minikube/pkg/minikube/constants"
)
@ -76,6 +80,37 @@ func DigestByGoLib(imgName string) string {
return cf.Hex
}
// WriteImageToDaemon write img to the local docker daemon
func WriteImageToDaemon(img string) error {
glog.Infof("Writing %s to local daemon", img)
if err := oci.PointToHostDockerDaemon(); err != nil {
return errors.Wrap(err, "point host docker-daemon")
}
// Check if image exists locally
cmd := exec.Command("docker", "images", "--format", "{{.Repository}}:{{.Tag}}@{{.Digest}}")
if output, err := cmd.Output(); err == nil {
if strings.Contains(string(output), img) {
glog.Infof("Found %s in local docker daemon, skipping pull", img)
return nil
}
}
// Else, pull it
ref, err := name.ParseReference(img)
if err != nil {
return errors.Wrap(err, "parsing reference")
}
i, err := remote.Image(ref)
if err != nil {
return errors.Wrap(err, "getting remote image")
}
tag, err := name.NewTag(strings.Split(img, "@")[0])
if err != nil {
return errors.Wrap(err, "getting tag")
}
_, err = daemon.Write(tag, i)
return err
}
func retrieveImage(ref name.Reference) (v1.Image, error) {
glog.Infof("retrieving image: %+v", ref)
img, err := daemon.Image(ref)

View File

@ -19,8 +19,10 @@ package machine
import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"time"
@ -62,6 +64,12 @@ func CacheImagesForBootstrapper(imageRepository string, version string, clusterB
// LoadImages loads previously cached images into the container runtime
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
// Skip loading images if images already exist
if imagesPreloaded(runner, images) {
glog.Infof("Images are preloaded, skipping loading")
return nil
}
glog.Infof("LoadImages start: %s", images)
start := time.Now()
@ -99,6 +107,28 @@ func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string
return nil
}
func imagesPreloaded(runner command.Runner, images []string) bool {
rr, err := runner.RunCmd(exec.Command("docker", "images", "--format", "{{.Repository}}:{{.Tag}}"))
if err != nil {
return false
}
preloadedImages := map[string]struct{}{}
for _, i := range strings.Split(rr.Stdout.String(), "\n") {
preloadedImages[i] = struct{}{}
}
glog.Infof("Got preloaded images: %s", rr.Output())
// Make sure images == imgs
for _, i := range images {
if _, ok := preloadedImages[i]; !ok {
glog.Infof("%s wasn't preloaded", i)
return false
}
}
return true
}
// needsTransfer returns an error if an image needs to be retransfered
func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager) error {
imgDgst := "" // for instance sha256:7c92a2c6bbcb6b6beff92d0a940779769c2477b807c202954c537e2e0deb9bed

View File

@ -24,6 +24,7 @@ import (
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
cmdcfg "k8s.io/minikube/cmd/minikube/cmd/config"
"k8s.io/minikube/pkg/drivers/kic"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/exit"
@ -31,6 +32,7 @@ import (
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/preload"
)
// beginCacheRequiredImages caches images required for kubernetes version in the background
@ -44,7 +46,7 @@ func beginCacheRequiredImages(g *errgroup.Group, imageRepository string, k8sVers
})
}
func handleDownloadOnly(cacheGroup *errgroup.Group, k8sVersion string) {
func handleDownloadOnly(cacheGroup, kicGroup *errgroup.Group, k8sVersion string) {
// If --download-only, complete the remaining downloads and exit.
if !viper.GetBool("download-only") {
return
@ -56,6 +58,7 @@ func handleDownloadOnly(cacheGroup *errgroup.Group, k8sVersion string) {
exit.WithError("Failed to cache kubectl", err)
}
waitCacheRequiredImages(cacheGroup)
waitDownloadKicArtifacts(kicGroup)
if err := saveImagesToTarFromConfig(); err != nil {
exit.WithError("Failed to cache images to tar", err)
}
@ -79,6 +82,26 @@ func doCacheBinaries(k8sVersion string) error {
return machine.CacheBinariesForBootstrapper(k8sVersion, viper.GetString(cmdcfg.Bootstrapper))
}
func beginDownloadKicArtifacts(g *errgroup.Group, k8sVersion, cRuntime string) {
glog.Info("Beginning downloading kic artifacts")
g.Go(func() error {
glog.Infof("Downloading %s to local daemon", kic.BaseImage)
return image.WriteImageToDaemon(kic.BaseImage)
})
g.Go(func() error {
glog.Info("Caching tarball of preloaded images")
return preload.CacheTarball(k8sVersion, cRuntime)
})
}
func waitDownloadKicArtifacts(g *errgroup.Group) {
if err := g.Wait(); err != nil {
glog.Errorln("Error downloading kic artifacts: ", err)
}
glog.Info("Successfully downloaded all kic artifacts")
}
// waitCacheRequiredImages blocks until the required images are all cached.
func waitCacheRequiredImages(g *errgroup.Group) {
if !viper.GetBool(cacheImages) {

View File

@ -34,9 +34,18 @@ import (
// Start spins up a guest and starts the kubernetes node.
func Start(mc config.ClusterConfig, n config.Node, primary bool, existingAddons map[string]bool) (*kubeconfig.Settings, error) {
k8sVersion := mc.KubernetesConfig.KubernetesVersion
driverName := mc.Driver
// See if we can create a volume of preloaded images
// If not, pull images in the background while the VM boots.
var kicGroup errgroup.Group
if driver.IsKIC(driverName) {
beginDownloadKicArtifacts(&kicGroup, k8sVersion, mc.KubernetesConfig.ContainerRuntime)
}
// Now that the ISO is downloaded, pull images in the background while the VM boots.
var cacheGroup errgroup.Group
beginCacheRequiredImages(&cacheGroup, mc.KubernetesConfig.ImageRepository, n.KubernetesVersion)
beginCacheRequiredImages(&cacheGroup, mc.KubernetesConfig.ImageRepository, k8sVersion)
// Abstraction leakage alert: startHost requires the config to be saved, to satistfy pkg/provision/buildroot.
// Hence, saveConfig must be called before startHost, and again afterwards when we know the IP.
@ -44,10 +53,10 @@ func Start(mc config.ClusterConfig, n config.Node, primary bool, existingAddons
exit.WithError("Failed to save config", err)
}
k8sVersion := mc.KubernetesConfig.KubernetesVersion
driverName := mc.Driver
// exits here in case of --download-only option.
handleDownloadOnly(&cacheGroup, k8sVersion)
handleDownloadOnly(&cacheGroup, &kicGroup, k8sVersion)
waitDownloadKicArtifacts(&kicGroup)
mRunner, preExists, machineAPI, host := startMachine(&mc, &n)
defer machineAPI.Close()
// configure the runtime (docker, containerd, crio)

View File

@ -0,0 +1,22 @@
/*
Copyright 2020 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 preload
const (
// Version is the current version of the preloaded tarball
Version = "v1"
)

View File

@ -0,0 +1,149 @@
/*
Copyright 2020 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 preload
import (
"context"
"crypto/md5"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"cloud.google.com/go/storage"
"google.golang.org/api/option"
"github.com/golang/glog"
"github.com/hashicorp/go-getter"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/util"
)
// returns name of the tarball
func tarballName(k8sVersion string) string {
return fmt.Sprintf("preloaded-images-k8s-%s-%s-docker-overlay2.tar.lz4", Version, k8sVersion)
}
// returns the name of the checksum file
func checksumName(k8sVersion string) string {
return fmt.Sprintf("%s.checksum", tarballName(k8sVersion))
}
// returns target dir for all cached items related to preloading
func targetDir() string {
return localpath.MakeMiniPath("cache", "preloaded-tarball")
}
// ChecksumFilepath returns path to checksum file
func ChecksumFilepath(k8sVersion string) string {
return path.Join(targetDir(), checksumName(k8sVersion))
}
// TarballFilepath returns the path to the preloaded tarball
func TarballFilepath(k8sVersion string) string {
return path.Join(targetDir(), tarballName(k8sVersion))
}
// remoteTarballURL returns the URL for the remote tarball in GCS
func remoteTarballURL(k8sVersion string) string {
return fmt.Sprintf("https://storage.googleapis.com/%s/%s", constants.PreloadedVolumeTarballsBucket, tarballName(k8sVersion))
}
// CacheTarball caches the preloaded images tarball on the host machine
func CacheTarball(k8sVersion, containerRuntime string) error {
if containerRuntime != "docker" {
return nil
}
targetFilepath := TarballFilepath(k8sVersion)
if _, err := os.Stat(targetFilepath); err == nil {
if err := verifyChecksum(k8sVersion); err == nil {
glog.Infof("Found %s in cache, skipping downloading", targetFilepath)
return nil
}
}
url := remoteTarballURL(k8sVersion)
// Make sure we support this k8s version
if _, err := http.Get(url); err != nil {
glog.Infof("Unable to get response from %s, skipping downloading: %v", url, err)
return nil
}
out.T(out.FileDownload, "Downloading preloaded images tarball for k8s {{.version}} ...", out.V{"version": k8sVersion})
client := &getter.Client{
Src: url,
Dst: targetFilepath,
Mode: getter.ClientModeFile,
Options: []getter.ClientOption{getter.WithProgress(util.DefaultProgressBar)},
}
glog.Infof("Downloading: %+v", client)
if err := client.Get(); err != nil {
return errors.Wrapf(err, "download failed: %s", url)
}
// Give downloaded drivers a baseline decent file permission
if err := os.Chmod(targetFilepath, 0755); err != nil {
return err
}
// Save checksum file locally
if err := saveChecksumFile(k8sVersion); err != nil {
return errors.Wrap(err, "saving checksum file")
}
return verifyChecksum(k8sVersion)
}
func saveChecksumFile(k8sVersion string) error {
ctx := context.Background()
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
if err != nil {
return errors.Wrap(err, "getting storage client")
}
attrs, err := client.Bucket(constants.PreloadedVolumeTarballsBucket).Object(tarballName(k8sVersion)).Attrs(ctx)
if err != nil {
return errors.Wrap(err, "getting storage object")
}
checksum := attrs.MD5
return ioutil.WriteFile(ChecksumFilepath(k8sVersion), checksum, 0644)
}
// verifyChecksum returns true if the checksum of the local binary matches
// the checksum of the remote binary
func verifyChecksum(k8sVersion string) error {
// get md5 checksum of tarball path
contents, err := ioutil.ReadFile(TarballFilepath(k8sVersion))
if err != nil {
return errors.Wrap(err, "reading tarball")
}
checksum := md5.Sum(contents)
remoteChecksum, err := ioutil.ReadFile(ChecksumFilepath(k8sVersion))
if err != nil {
return errors.Wrap(err, "reading checksum file")
}
// create a slice of checksum, which is [16]byte
if string(remoteChecksum) != string(checksum[:]) {
return fmt.Errorf("checksum of %s does not match remote checksum (%s != %s)", TarballFilepath(k8sVersion), string(remoteChecksum), string(checksum[:]))
}
return nil
}

View File

@ -45,13 +45,15 @@ func init() {
func configure(mc config.ClusterConfig) (interface{}, error) {
return kic.NewDriver(kic.Config{
MachineName: mc.Name,
StorePath: localpath.MiniPath(),
ImageDigest: kic.BaseImage,
CPU: mc.CPUs,
Memory: mc.Memory,
OCIBinary: oci.Docker,
APIServerPort: mc.Nodes[0].Port,
MachineName: mc.Name,
StorePath: localpath.MiniPath(),
ImageDigest: kic.BaseImage,
CPU: mc.CPUs,
Memory: mc.Memory,
OCIBinary: oci.Docker,
APIServerPort: mc.Nodes[0].Port,
KubernetesVersion: mc.KubernetesConfig.KubernetesVersion,
ContainerRuntime: mc.KubernetesConfig.ContainerRuntime,
}), nil
}

View File

@ -20,19 +20,26 @@ package integration
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"k8s.io/minikube/pkg/drivers/kic"
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/preload"
)
func TestDownloadOnly(t *testing.T) {
@ -160,3 +167,52 @@ func TestDownloadOnly(t *testing.T) {
})
}
func TestDownloadOnlyDocker(t *testing.T) {
if !runningDockerDriver(StartArgs()) {
t.Skip("this test only runs with the docker driver")
}
profile := UniqueProfileName("download-docker")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer Cleanup(t, profile, cancel)
args := []string{"start", "--download-only", "-p", profile, "--force", "--alsologtostderr", "--vm-driver=docker"}
rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
if err != nil {
t.Errorf("%s failed: %v:\n%s", args, err, rr.Output())
}
// Make sure the preloaded image tarball exists
tarball := preload.TarballFilepath(constants.DefaultKubernetesVersion)
contents, err := ioutil.ReadFile(tarball)
if err != nil {
t.Errorf("reading tarball: %v", err)
}
// Make sure it has the correct checksum
checksum := md5.Sum(contents)
remoteChecksum, err := ioutil.ReadFile(preload.ChecksumFilepath(constants.DefaultKubernetesVersion))
if err != nil {
t.Errorf("reading checksum file: %v", err)
}
if string(remoteChecksum) != string(checksum[:]) {
t.Errorf("checksum of %s does not match remote checksum (%s != %s)", tarball, string(remoteChecksum), string(checksum[:]))
}
// Make sure this image exists in the docker daemon
ref, err := name.ParseReference(kic.BaseImage)
if err != nil {
t.Errorf("parsing reference failed: %v", err)
}
if _, err := daemon.Image(ref); err != nil {
t.Errorf("expected image does not exist in local daemon: %v", err)
}
}
func runningDockerDriver(startArgs []string) bool {
for _, s := range startArgs {
if s == "--vm-driver=docker" {
return true
}
}
return false
}