Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

faster containerd start by preloading images #7793

Merged
merged 14 commits into from
Apr 20, 2020
51 changes: 44 additions & 7 deletions hack/preload-images/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ import (
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/cruntime"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/sysinit"
"k8s.io/minikube/pkg/util"
)

func generateTarball(kubernetesVersion, containerRuntime, tarballFilename string) error {
Expand Down Expand Up @@ -68,44 +72,77 @@ func generateTarball(kubernetesVersion, containerRuntime, tarballFilename string
if err != nil {
return errors.Wrap(err, "kubeadm images")
}

if containerRuntime != "docker" { // kic overlay image is only needed by containerd and cri-o https://github.com/kubernetes/minikube/issues/7428
imgs = append(imgs, kic.OverlayImage)
}

runner := command.NewKICRunner(profile, driver.OCIBinary)

// will need to do this to enable the container run-time service
sv, err := util.ParseKubernetesVersion(constants.DefaultKubernetesVersion)
if err != nil {
return errors.Wrap(err, "Failed to parse kubernetes version")
}

co := cruntime.Config{
Type: containerRuntime,
Runner: runner,
ImageRepository: "",
KubernetesVersion: sv, // I think this is just to statsify cruntime and shouldnt matter
medyagh marked this conversation as resolved.
Show resolved Hide resolved
}
cr, err := cruntime.New(co)
if err != nil {
exit.WithError("Failed runtime", err)
}
if err := cr.Enable(true); err != nil {
exit.WithError("enable container runtime ", err)
}

for _, img := range imgs {
cmd := exec.Command("docker", "exec", profile, "docker", "pull", img)
if containerRuntime == "containerd" {
medyagh marked this conversation as resolved.
Show resolved Hide resolved
cmd = exec.Command("docker", "exec", profile, "sudo", "crictl", "pull", img)
}

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return errors.Wrapf(err, "downloading %s", img)
return errors.Wrapf(err, "pulling image %s", img)
}
}

// Transfer in k8s binaries
kcfg := config.KubernetesConfig{
KubernetesVersion: kubernetesVersion,
}
runner := command.NewKICRunner(profile, driver.OCIBinary)

sm := sysinit.New(runner)

if err := bsutil.TransferBinaries(kcfg, runner, sm); err != nil {
return errors.Wrap(err, "transferring k8s binaries")
}
// Create image tarball
if err := createImageTarball(tarballFilename); err != nil {
if err := createImageTarball(tarballFilename, containerRuntime); err != nil {
return errors.Wrap(err, "create tarball")
}

return copyTarballToHost(tarballFilename)
}

func createImageTarball(tarballFilename string) error {
func createImageTarball(tarballFilename, containerRuntime string) error {
// directories to save into tarball
dirs := []string{
fmt.Sprintf("./lib/docker/%s", dockerStorageDriver),
"./lib/docker/image",
"./lib/minikube/binaries",
}

if containerRuntime == "docker" {
dirs = append(dirs, fmt.Sprintf("./lib/docker/%s", dockerStorageDriver), "./lib/docker/image")
}

if containerRuntime == "containerd" {
dirs = append(dirs, fmt.Sprintf("./lib/containerd"))
}

args := []string{"exec", profile, "sudo", "tar", "-I", "lz4", "-C", "/var", "-cvf", tarballFilename}
args = append(args, dirs...)
cmd := exec.Command("docker", args...)
Expand Down
6 changes: 3 additions & 3 deletions hack/preload-images/preload_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (

var (
dockerStorageDriver = "overlay2"
containerRuntimes = []string{"docker"}
containerRuntimes = []string{"docker", "containerd"}
k8sVersion string
k8sVersions []string
)
Expand Down Expand Up @@ -65,10 +65,10 @@ func main() {
for _, cr := range containerRuntimes {
tf := download.TarballName(kv, cr)
if download.PreloadExists(kv, cr) {
fmt.Printf("A preloaded tarball for k8s version %s already exists, skipping generation.\n", kv)
fmt.Printf("A preloaded tarball for k8s version %s - runtime %q already exists, skipping generation.\n", kv, cr)
continue
}
fmt.Printf("A preloaded tarball for k8s version %s doesn't exist, generating now...\n", kv)
fmt.Printf("A preloaded tarball for k8s version %s - runtime %q doesn't exist, generating now...\n", kv, cr)
if err := generateTarball(kv, cr, tf); err != nil {
exit.WithError(fmt.Sprintf("generating tarball for k8s version %s with %s", kv, cr), err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/drivers/kic/kic.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (d *Driver) Create() error {
return
}
t := time.Now()
glog.Infof("Starting extracting preloaded images to volume")
glog.Infof("Starting extracting preloaded images to volume ....")
medyagh marked this conversation as resolved.
Show resolved Hide resolved
// Extract preloaded images to container
if err := oci.ExtractTarballToVolume(download.TarballPath(d.NodeConfig.KubernetesVersion, d.NodeConfig.ContainerRuntime), params.Name, BaseImage); err != nil {
glog.Infof("Unable to extract preloaded tarball to volume: %v", err)
Expand Down
111 changes: 110 additions & 1 deletion pkg/minikube/cruntime/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ package cruntime
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"os/exec"
"path"
"strings"
"text/template"
"time"

"github.com/blang/semver"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/bootstrapper/images"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/download"
"k8s.io/minikube/pkg/minikube/out"
Expand Down Expand Up @@ -310,5 +314,110 @@ func (r *Containerd) Preload(cfg config.KubernetesConfig) error {
if !download.PreloadExists(cfg.KubernetesVersion, cfg.ContainerRuntime) {
return nil
}
return fmt.Errorf("not yet implemented for %s", r.Name())

k8sVersion := cfg.KubernetesVersion
cRuntime := cfg.ContainerRuntime

// If images already exist, return
images, err := images.Kubeadm(cfg.ImageRepository, k8sVersion)
if err != nil {
return errors.Wrap(err, "getting images")
}
if ContainerdImagesPreloaded(r.Runner, images) {
glog.Info("Images already preloaded, skipping extraction")
return nil
}

tarballPath := download.TarballPath(k8sVersion, cRuntime)
targetDir := "/"
targetName := "preloaded.tar.lz4"
dest := path.Join(targetDir, targetName)

c := exec.Command("which", "lz4")
if _, err := r.Runner.RunCmd(c); err != nil {
return NewErrISOFeature("lz4")
}

// Copy over tarball into host
fa, err := assets.NewFileAsset(tarballPath, targetDir, targetName, "0644")
if err != nil {
return errors.Wrap(err, "getting file asset")
}
t := time.Now()
if err := r.Runner.Copy(fa); err != nil {
return errors.Wrap(err, "copying file")
}
glog.Infof("Took %f seconds to copy over tarball", time.Since(t).Seconds())

t = time.Now()
// extract the tarball to /var in the VM
if rr, err := r.Runner.RunCmd(exec.Command("sudo", "tar", "-I", "lz4", "-C", "/var", "-xvf", dest)); err != nil {
return errors.Wrapf(err, "extracting tarball: %s", rr.Output())
}
glog.Infof("Took %f seconds t extract the tarball", time.Since(t).Seconds())

// remove the tarball in the VM
if err := r.Runner.Remove(fa); err != nil {
glog.Infof("error removing tarball: %v", err)
}

return r.Restart()
}

// Restart restarts Docker on a host
func (r *Containerd) Restart() error {
return r.Init.Restart("containerd")
}

// ContainerdImagesPreloaded returns true if all images have been preloaded
func ContainerdImagesPreloaded(runner command.Runner, images []string) bool {
rr, err := runner.RunCmd(exec.Command("sudo", "crictl", "images", "--output", "json"))
if err != nil {
return false
}
type containerdImages struct {
Images []struct {
ID string `json:"id"`
RepoTags []string `json:"repoTags"`
RepoDigests []string `json:"repoDigests"`
Size string `json:"size"`
UID interface{} `json:"uid"`
Username string `json:"username"`
} `json:"images"`
}

var jsonImages containerdImages
err = json.Unmarshal(rr.Stdout.Bytes(), &jsonImages)
if err != nil {
glog.Errorf("failed to unmarshal images, will assume images are not preloaded")
return false
}

// Make sure images == imgs
for _, i := range images {
found := false
for _, ji := range jsonImages.Images {
for _, rt := range ji.RepoTags {
// in crictl docker.io is appened to images without repo organization
// for example "kubernetesui/dashboard:v2.0.0-rc6 will show up as "docker.io/kubernetesui/dashboard:v2.0.0-rc6"
if !strings.Contains(i, ".io/") {
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
i = "docker.io/" + i
}
if i == rt {
found = true
break
}
}
if found {
break
}

}
if !found {
glog.Infof("couldn't find preloaded image for %q. assuming images are not preloaded.", i)
return false
}
}
glog.Infof("all images are preloaded for containerd runtime.")
return true
}
8 changes: 4 additions & 4 deletions pkg/minikube/download/preload.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ func remoteTarballURL(k8sVersion, containerRuntime string) string {

// PreloadExists returns true if there is a preloaded tarball that can be used
func PreloadExists(k8sVersion, containerRuntime string) bool {
// TODO: debug why this func is being called two times
glog.Infof("Checking if preload exists for k8s version %s and runtime %s", k8sVersion, containerRuntime)
if !viper.GetBool("preload") {
return false
}

// See https://github.com/kubernetes/minikube/issues/6933
// and https://github.com/kubernetes/minikube/issues/6934
// to track status of adding containerd & crio
if containerRuntime != "docker" {
glog.Info("Container runtime isn't docker, skipping preload")
// to track status of adding crio
if containerRuntime == "crio" {
glog.Info("crio is not supported yet, skipping preload")
return false
}

Expand Down
18 changes: 13 additions & 5 deletions pkg/minikube/machine/cache_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,20 @@ 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 cruntime.DockerImagesPreloaded(runner, images) {
glog.Infof("Images are preloaded, skipping loading")
return nil
if cc.KubernetesConfig.ContainerRuntime == "docker" {
medyagh marked this conversation as resolved.
Show resolved Hide resolved
// Skip loading images if images already exist
if cruntime.DockerImagesPreloaded(runner, images) {
glog.Infof("Images are preloaded, skipping loading")
return nil
}
}
if cc.KubernetesConfig.ContainerRuntime == "containerd" {
// Skip loading images if images already exist
if cruntime.ContainerdImagesPreloaded(runner, images) {
glog.Infof("Images are preloaded, skipping loading")
return nil
}
}

glog.Infof("LoadImages start: %s", images)
start := time.Now()

Expand Down
1 change: 1 addition & 0 deletions test/integration/aaa_download_only_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func TestDownloadOnlyKic(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
defer Cleanup(t, profile, cancel)

// TODO: #7795 add containerd to download only too
cRuntime := "docker"

args := []string{"start", "--download-only", "-p", profile, "--force", "--alsologtostderr"}
Expand Down