diff --git a/Makefile b/Makefile index 06fd743b907..e9c3286e553 100644 --- a/Makefile +++ b/Makefile @@ -196,7 +196,7 @@ skaffold-builder: .PHONY: integration-in-kind integration-in-kind: kind-cluster skaffold-builder - docker exec -it kind-control-plane cat /etc/kubernetes/admin.conf > /tmp/kind-config + kind get kubeconfig --internal > /tmp/kind-config echo '{}' > /tmp/docker-config docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/docs/content/en/docs/environment/local-cluster.md b/docs/content/en/docs/environment/local-cluster.md index 3a3f459caeb..ad5784fec20 100644 --- a/docs/content/en/docs/environment/local-cluster.md +++ b/docs/content/en/docs/environment/local-cluster.md @@ -5,15 +5,38 @@ weight: 60 aliases: [/docs/concepts/local_development] --- -Skaffold can be easily configured to deploy against a cluster hosted locally, most commonly with -[`minikube`](https://github.com/kubernetes/minikube/) or `docker-for-desktop`. +Skaffold can be easily configured to deploy against a cluster hosted locally, most commonly +with [`minikube`] or [`Docker Desktop`]. + The advantage of this setup is that no images need to be pushed, since the local cluster -uses images straight from your local docker daemon. +uses images straight from your local docker daemon. It leads to much faster development cycles. + +### Auto detection + +Skaffold's heuristic to detect local clusters is based on the Kubernetes context name. +The following context names are checked: + +| Kubernetes context | Local cluster type | Notes | +| ------------------ | ------------------ | ----- | +| docker-desktop | [`Docker Desktop`] | | +| docker-for-desktop | [`Docker Desktop`] | This context name is deprecated | +| minikube | [`minikube`] | | +| kind-(.*) | [`kind`] | This pattern is used by kind v0.6.0 | +| (.*)@kind | [`kind`] | This pattern was used by kind < v0.6.0 | -For non-standard local setups, such as a custom `minikube` profile or [kind](https://github.com/kubernetes-sigs/kind), +For any other name, Skaffold assumes that the cluster is remote and that images +have to be pushed. + + [`minikube`]: https://github.com/kubernetes/minikube/ + [`Docker Desktop`]: https://www.docker.com/products/docker-desktop + [`kind`]: https://github.com/kubernetes-sigs/kind + +### Manual override + +For non-standard local setups, such as a custom `minikube` profile, some extra configuration is necessary. The essential steps are: -1. Ensure that Skaffold builds the images with the docker daemon, which also runs the containers. +1. Ensure that Skaffold builds the images with the same docker daemon that runs the pods' containers. 1. Tell Skaffold to skip pushing images either by configuring ```yaml @@ -35,3 +58,4 @@ For example, when running `minikube` with a custom profile (e.g. `minikube start ```bash skaffold config set --kube-context my-profile local-cluster true ``` + diff --git a/integration/dev_test.go b/integration/dev_test.go index 4edc42ba734..c4b29ec24c5 100644 --- a/integration/dev_test.go +++ b/integration/dev_test.go @@ -407,8 +407,8 @@ func createModifiedKubeconfig(namespace string) ([]byte, string, error) { } contextName := "modified-context" - if config.IsKindCluster(kubeConfig.CurrentContext) { - contextName += "@kind" + if isKind, _ := config.IsKindCluster(kubeConfig.CurrentContext); isKind { + contextName = "kind-" + contextName } activeContext := kubeConfig.Contexts[kubeConfig.CurrentContext] diff --git a/pkg/skaffold/config/util.go b/pkg/skaffold/config/util.go index 6c53e6fe981..91ff5403197 100644 --- a/pkg/skaffold/config/util.go +++ b/pkg/skaffold/config/util.go @@ -183,14 +183,37 @@ func GetInsecureRegistries(configFile string) ([]string, error) { } func isDefaultLocal(kubeContext string) bool { - return kubeContext == constants.DefaultMinikubeContext || + if kubeContext == constants.DefaultMinikubeContext || kubeContext == constants.DefaultDockerForDesktopContext || - kubeContext == constants.DefaultDockerDesktopContext || - IsKindCluster(kubeContext) + kubeContext == constants.DefaultDockerDesktopContext { + return true + } + + isKind, _ := IsKindCluster(kubeContext) + return isKind } -func IsKindCluster(kubeContext string) bool { - return strings.HasSuffix(kubeContext, "@kind") +// IsKindCluster checks that the given `kubeContext` is talking to `kind`. +// It also returns the name of the `kind` cluster. +func IsKindCluster(kubeContext string) (bool, string) { + switch { + // With kind version < 0.6.0, the k8s context + // is `[CLUSTER NAME]@kind`. + // For eg: `cluster@kind` + // the default name is `kind@kind` + case strings.HasSuffix(kubeContext, "@kind"): + return true, strings.TrimSuffix(kubeContext, "@kind") + + // With kind version == 0.6.0, the k8s context + // is `kind-[CLUSTER NAME]`. + // For eg: `kind-cluster` + // the default name is `kind-kind` + case strings.HasPrefix(kubeContext, "kind-"): + return true, strings.TrimPrefix(kubeContext, "kind-") + + default: + return false, "" + } } func IsUpdateCheckEnabled(configfile string) bool { diff --git a/pkg/skaffold/config/util_test.go b/pkg/skaffold/config/util_test.go index 9656af036c3..53435c74d85 100644 --- a/pkg/skaffold/config/util_test.go +++ b/pkg/skaffold/config/util_test.go @@ -262,3 +262,50 @@ func TestIsUpdateCheckEnabled(t *testing.T) { }) } } + +func TestIsDefaultLocal(t *testing.T) { + tests := []struct { + context string + expectedLocal bool + }{ + {context: "kind-other", expectedLocal: true}, + {context: "kind@kind", expectedLocal: true}, + {context: "docker-for-desktop", expectedLocal: true}, + {context: "minikube", expectedLocal: true}, + {context: "docker-for-desktop", expectedLocal: true}, + {context: "docker-desktop", expectedLocal: true}, + {context: "anything-else", expectedLocal: false}, + {context: "kind@blah", expectedLocal: false}, + {context: "other-kind", expectedLocal: false}, + } + for _, test := range tests { + testutil.Run(t, "", func(t *testutil.T) { + local := isDefaultLocal(test.context) + + t.CheckDeepEqual(test.expectedLocal, local) + }) + } +} + +func TestIsKindCluster(t *testing.T) { + tests := []struct { + context string + expectedName string + expectedIsKind bool + }{ + {context: "kind-kind", expectedName: "kind", expectedIsKind: true}, + {context: "kind-other", expectedName: "other", expectedIsKind: true}, + {context: "kind@kind", expectedName: "kind", expectedIsKind: true}, + {context: "other@kind", expectedName: "other", expectedIsKind: true}, + {context: "docker-for-desktop", expectedName: "", expectedIsKind: false}, + {context: "not-kind", expectedName: "", expectedIsKind: false}, + } + for _, test := range tests { + testutil.Run(t, "", func(t *testutil.T) { + isKind, name := IsKindCluster(test.context) + + t.CheckDeepEqual(test.expectedIsKind, isKind) + t.CheckDeepEqual(test.expectedName, name) + }) + } +} diff --git a/pkg/skaffold/runner/deploy.go b/pkg/skaffold/runner/deploy.go index 4c5d91971e6..d840da66c8f 100644 --- a/pkg/skaffold/runner/deploy.go +++ b/pkg/skaffold/runner/deploy.go @@ -45,9 +45,9 @@ func (r *SkaffoldRunner) Deploy(ctx context.Context, out io.Writer, artifacts [] color.Green.Fprintln(out, " local images can't be referenced by digest. They are tagged and referenced by a unique ID instead") } - if config.IsKindCluster(r.runCtx.KubeContext) { + if isKind, kindCluster := config.IsKindCluster(r.runCtx.KubeContext); isKind { // With `kind`, docker images have to be loaded with the `kind` CLI. - if err := r.loadImagesInKindNodes(ctx, out, artifacts); err != nil { + if err := r.loadImagesInKindNodes(ctx, out, kindCluster, artifacts); err != nil { return errors.Wrapf(err, "loading images into kind nodes") } } diff --git a/pkg/skaffold/runner/kind.go b/pkg/skaffold/runner/kind.go index ab9d992ccf6..8baa6b2ce88 100644 --- a/pkg/skaffold/runner/kind.go +++ b/pkg/skaffold/runner/kind.go @@ -32,7 +32,7 @@ import ( ) // loadImagesInKindNodes loads a list of artifact images into every node of kind cluster. -func (r *SkaffoldRunner) loadImagesInKindNodes(ctx context.Context, out io.Writer, artifacts []build.Artifact) error { +func (r *SkaffoldRunner) loadImagesInKindNodes(ctx context.Context, out io.Writer, kindCluster string, artifacts []build.Artifact) error { start := time.Now() color.Default.Fprintln(out, "Loading images into kind cluster nodes...") @@ -59,7 +59,7 @@ func (r *SkaffoldRunner) loadImagesInKindNodes(ctx context.Context, out io.Write continue } - cmd := exec.CommandContext(ctx, "kind", "load", "docker-image", artifact.Tag) + cmd := exec.CommandContext(ctx, "kind", "load", "docker-image", "--name", kindCluster, artifact.Tag) if err := util.RunCmd(cmd); err != nil { color.Red.Fprintln(out, "Failed") return errors.Wrapf(err, "unable to load image with kind: %s", artifact.Tag) diff --git a/pkg/skaffold/runner/kind_test.go b/pkg/skaffold/runner/kind_test.go index ae46e0b115c..87316e7dd9c 100644 --- a/pkg/skaffold/runner/kind_test.go +++ b/pkg/skaffold/runner/kind_test.go @@ -33,6 +33,7 @@ import ( func TestLoadImagesInKindNodes(t *testing.T) { tests := []struct { description string + kindCluster string built []build.Artifact deployed []build.Artifact commands util.Command @@ -41,19 +42,21 @@ func TestLoadImagesInKindNodes(t *testing.T) { }{ { description: "load image", + kindCluster: "kind", built: []build.Artifact{{Tag: "tag1"}}, deployed: []build.Artifact{{Tag: "tag1"}}, commands: testutil. CmdRunOut("kubectl --context kubecontext --namespace namespace get nodes -ojsonpath='{@.items[*].status.images[*].names[*]}'", ""). - AndRun("kind load docker-image tag1"), + AndRun("kind load docker-image --name kind tag1"), }, { description: "load missing image", + kindCluster: "other-kind", built: []build.Artifact{{Tag: "tag1"}, {Tag: "tag2"}}, deployed: []build.Artifact{{Tag: "tag1"}, {Tag: "tag2"}}, commands: testutil. CmdRunOut("kubectl --context kubecontext --namespace namespace get nodes -ojsonpath='{@.items[*].status.images[*].names[*]}'", "tag1"). - AndRun("kind load docker-image tag2"), + AndRun("kind load docker-image --name other-kind tag2"), }, { description: "inspect error", @@ -66,21 +69,23 @@ func TestLoadImagesInKindNodes(t *testing.T) { }, { description: "load error", + kindCluster: "kind", built: []build.Artifact{{Tag: "tag"}}, deployed: []build.Artifact{{Tag: "tag"}}, commands: testutil. CmdRunOut("kubectl --context kubecontext --namespace namespace get nodes -ojsonpath='{@.items[*].status.images[*].names[*]}'", ""). - AndRunErr("kind load docker-image tag", errors.New("BUG")), + AndRunErr("kind load docker-image --name kind tag", errors.New("BUG")), shouldErr: true, expectedError: "unable to load", }, { description: "ignore image that's not built", + kindCluster: "kind", built: []build.Artifact{{Tag: "built"}}, deployed: []build.Artifact{{Tag: "built"}, {Tag: "busybox"}}, commands: testutil. CmdRunOut("kubectl --context kubecontext --namespace namespace get nodes -ojsonpath='{@.items[*].status.images[*].names[*]}'", ""). - AndRun("kind load docker-image built"), + AndRun("kind load docker-image --name kind built"), }, { description: "no artifact", @@ -106,7 +111,7 @@ func TestLoadImagesInKindNodes(t *testing.T) { KubeContext: "kubecontext", }, } - err := r.loadImagesInKindNodes(context.Background(), ioutil.Discard, test.deployed) + err := r.loadImagesInKindNodes(context.Background(), ioutil.Discard, test.kindCluster, test.deployed) if test.shouldErr { t.CheckErrorContains(test.expectedError, err)