Skip to content

Commit

Permalink
Fix kind 0.6.0 support (GoogleContainerTools#3298)
Browse files Browse the repository at this point in the history
* Support new kind naming scheme

Signed-off-by: David Gageot <[email protected]>

* Load images into the proper kind cluster

Signed-off-by: David Gageot <[email protected]>

* Feedback 

Signed-off-by: David Gageot <[email protected]>

* Update local cluster doc

Signed-off-by: David Gageot <[email protected]>
  • Loading branch information
dgageot authored Dec 3, 2019
1 parent ddabf1d commit 82c4e40
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
34 changes: 29 additions & 5 deletions docs/content/en/docs/environment/local-cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
```

4 changes: 2 additions & 2 deletions integration/dev_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
33 changes: 28 additions & 5 deletions pkg/skaffold/config/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions pkg/skaffold/config/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
4 changes: 2 additions & 2 deletions pkg/skaffold/runner/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/skaffold/runner/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...")

Expand All @@ -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)
Expand Down
15 changes: 10 additions & 5 deletions pkg/skaffold/runner/kind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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)
Expand Down

0 comments on commit 82c4e40

Please sign in to comment.