Repository: GoogleContainerTools/skaffold
Pull request #8459 (2023-02-22 08:47:55)

feat: support deploying to multiple clusters
<!-- Thank you for your contribution! -->

<!-- Include if applicable: -->
Fixes: #915 <!-- tracking issues that this PR will close -->

**Description**
<!-- Describe your changes here. The more detail, the easier the review! -->
This PR fixes some issues around deploying to multiple kubernetes clusters using multi module skaffold configurations.

* Skaffold commands `dev`, `run` and `debug` support deploying to multiple kubernetes clusters using multiple skaffold configurations. For example, in the `examples/getting-started` project we can modify the `skaffold.yaml` file:
  ```
  apiVersion: skaffold/v4beta2
  kind: Config
  build:
    artifacts:
    - image: skaffold-example
  manifests:
    rawYaml:
    - k8s-pod.yaml
  deploy:
    kubectl: {}
    kubeContext: my-cluster-1
  ---
  apiVersion: skaffold/v4beta2
  kind: Config
  manifests:
    rawYaml:
    - k8s-pod.yaml
  deploy:
    kubectl: {}
    kubeContext: my-cluster-2
  ```
* This will deploy the `skaffold-example` image to both clusters `my-cluster-1` and `my-cluster-2`.
  ```
  $ skaffold run  --tail
  ...
  Starting deploy...
   - pod/getting-started configured
  Waiting for deployments to stabilize...
   - pods is ready.
  Deployments stabilized in 2.659 seconds
   - pod/getting-started configured
  Waiting for deployments to stabilize...
   - pods is ready.
  Deployments stabilized in 2.61 seconds
  Press Ctrl+C to exit
  [my-cluster-1][getting-started] Hello world!
  [my-cluster-2][getting-started] Hello world!
  ```
* The container logs are prefixed with the cluster name when there is more than one cluster.


codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#8459](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (777b532) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `5.29%`.
> The diff coverage is `55.02%`.

```diff
@@            Coverage Diff             @@
##             main    #8459      +/-   ##
==========================================
- Coverage   70.48%   65.19%   -5.29%     
==========================================
  Files         515      603      +88     
  Lines       23150    29914    +6764     
==========================================
+ Hits        16317    19503    +3186     
- Misses       5776     8935    +3159     
- Partials     1057     1476     +419     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/completion.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29tcGxldGlvbi5nbw==) | `13.04% <0.00%> (-1.25%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/config/list.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL2xpc3QuZ28=) | `65.21% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/config/set.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL3NldC5nbw==) | `88.72% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/config/util.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY29uZmlnL3V0aWwuZ28=) | `54.28% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/credits.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy5nbw==) | `100.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `40.90% <0.00%> (-12.94%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/generate\_pipeline.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZ2VuZXJhdGVfcGlwZWxpbmUuZ28=) | `60.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_modules.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF9tb2R1bGVzLmdv) | `65.00% <ø> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_profiles.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF9wcm9maWxlcy5nbw==) | `66.66% <ø> (ø)` | |
| ... and [425 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8459?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)

aaron-prindle: Seems there is a gofmt error currently :

https://github.com/GoogleContainerTools/skaffold/actions/runs/4240944990/jobs/7405634565

```
Gofmt errors in files:
./pkg/skaffold/runner/runcontext/context.go
diff ./pkg/skaffold/runner/runcontext/context.go.orig ./pkg/skaffold/runner/runcontext/context.go
--- ./pkg/skaffold/runner/runcontext/context.go.orig
+++ ./pkg/skaffold/runner/runcontext/context.go
@@ -300,9 +300,[11](https://github.com/GoogleContainerTools/skaffold/actions/runs/4240944990/jobs/7405634565#step:4:12) @@
 	}
 	return strings.Trim(string(b), "'")
 }
-func (rc *RunContext) AutoBuild() bool                               { return rc.Opts.AutoBuild }
-func (rc *RunContext) DisableMultiPlatformBuild() bool               { return rc.Opts.DisableMultiPlatformBuild }
-func (rc *RunContext) CheckClusterNodePlatforms() bool               { return rc.Opts.CheckClusterNodePlatforms && !rc.IsMultiCluster() }
+func (rc *RunContext) AutoBuild() bool                 { return rc.Opts.AutoBuild }
+func (rc *RunContext) DisableMultiPlatformBuild() bool { return rc.Opts.DisableMultiPlatformBuild }
+func (rc *RunContext) CheckClusterNodePlatforms() bool {
+	return rc.Opts.CheckClusterNodePlatforms && !rc.IsMultiCluster()
+}
 func (rc *RunContext) AutoDeploy() bool                              { return rc.Opts.AutoDeploy }
 func (rc *RunContext) AutoSync() bool                                { return rc.Opts.AutoSync }
 func (rc *RunContext) ContainerDebugging() bool                      { return rc.Opts.ContainerDebugging }
FAILED hack/gofmt.sh
```
gsquared94: > Seems there is a gofmt error currently :
> 
> https://github.com/GoogleContainerTools/skaffold/actions/runs/4240944990/jobs/7405634565
> 
> ```
> Gofmt errors in files:
> ./pkg/skaffold/runner/runcontext/context.go
> diff ./pkg/skaffold/runner/runcontext/context.go.orig ./pkg/skaffold/runner/runcontext/context.go
> --- ./pkg/skaffold/runner/runcontext/context.go.orig
> +++ ./pkg/skaffold/runner/runcontext/context.go
> @@ -300,9 +300,[11](https://github.com/GoogleContainerTools/skaffold/actions/runs/4240944990/jobs/7405634565#step:4:12) @@
>  	}
>  	return strings.Trim(string(b), "'")
>  }
> -func (rc *RunContext) AutoBuild() bool                               { return rc.Opts.AutoBuild }
> -func (rc *RunContext) DisableMultiPlatformBuild() bool               { return rc.Opts.DisableMultiPlatformBuild }
> -func (rc *RunContext) CheckClusterNodePlatforms() bool               { return rc.Opts.CheckClusterNodePlatforms && !rc.IsMultiCluster() }
> +func (rc *RunContext) AutoBuild() bool                 { return rc.Opts.AutoBuild }
> +func (rc *RunContext) DisableMultiPlatformBuild() bool { return rc.Opts.DisableMultiPlatformBuild }
> +func (rc *RunContext) CheckClusterNodePlatforms() bool {
> +	return rc.Opts.CheckClusterNodePlatforms && !rc.IsMultiCluster()
> +}
>  func (rc *RunContext) AutoDeploy() bool                              { return rc.Opts.AutoDeploy }
>  func (rc *RunContext) AutoSync() bool                                { return rc.Opts.AutoSync }
>  func (rc *RunContext) ContainerDebugging() bool                      { return rc.Opts.ContainerDebugging }
> FAILED hack/gofmt.sh
> ```

fixed

Patch:
diff --git a/docs-v2/content/en/docs/deployers/helm.md b/docs-v2/content/en/docs/deployers/helm.md
index b1bca4739..543eec298 100644
--- a/docs-v2/content/en/docs/deployers/helm.md
+++ b/docs-v2/content/en/docs/deployers/helm.md
@@ -113,12 +113,13 @@ The `setValues` configuration binds a Helm key to the specified value. The `setV
 | `{{.IMAGE_REPO_NO_DOMAIN_<artifact-name>}}` | `example-repo` |
 
 ### Sanitizing the artifact name from invalid go template characters
-The `<artifact-name>` (eg: `{{.IMAGE_FULLY_QUALIFIED_<artifact-name>}}`) when used with `setValueTemplates` cannot have `/`, `-`, or `.` characters.  If you have an artifact name with these characters (eg: `localhost/nginx` or `gcr.io/foo-image/foo`), change them to use `_` in place of these characters in the `setValueTemplates` field
+The `<artifact-name>` (eg: `{{.IMAGE_FULLY_QUALIFIED_<artifact-name>}}`) when used with `setValueTemplates` cannot have `/`, `-`, `.`, or `:` characters.  If you have an artifact name with these characters (eg: `localhost/nginx` or `gcr.io/foo-image/foo`), change them to use `_` in place of these characters in the `setValueTemplates` field
 
 | Artifact name | Sanitized Name |
 | --------------- | --------------- |
 | `localhost/nginx` | `localhost_nginx`|
 | `gcr.io/example-repo/myImage` | `gcr_io_example_repo_myImage` |
+| `gcr.io/example-repo/myImage:1234_container` | `gcr_io_example_repo_myImage_1234_container` |
 
 Example
 ```yaml
diff --git a/docs-v2/content/en/docs/renderers/helm.md b/docs-v2/content/en/docs/renderers/helm.md
index 1555be2dc..8fb957818 100644
--- a/docs-v2/content/en/docs/renderers/helm.md
+++ b/docs-v2/content/en/docs/renderers/helm.md
@@ -113,12 +113,13 @@ The `setValues` configuration binds a Helm key to the specified value. The `setV
 | `{{.IMAGE_REPO_NO_DOMAIN_<artifact-name>}}` | `example-repo` |
 
 ### Sanitizing the artifact name from invalid go template characters
-The `<artifact-name>` (eg: `{{.IMAGE_FULLY_QUALIFIED_<artifact-name>}}`) when used with `setValueTemplates` cannot have `/`, `-`, or `.` characters.  If you have an artifact name with these characters (eg: `localhost/nginx` or `gcr.io/foo-image/foo`), change them to use `_` in place of these characters in the `setValueTemplates` field
+The `<artifact-name>` (eg: `{{.IMAGE_FULLY_QUALIFIED_<artifact-name>}}`) when used with `setValueTemplates` cannot have `/`, `-`, `.`, or `:` characters.  If you have an artifact name with these characters (eg: `localhost/nginx` or `gcr.io/foo-image/foo`), change them to use `_` in place of these characters in the `setValueTemplates` field
 
 | Artifact name | Sanitized Name |
 | --------------- | --------------- |
 | `localhost/nginx` | `localhost_nginx`|
 | `gcr.io/example-repo/myImage` | `gcr_io_example_repo_myImage` |
+| `gcr.io/example-repo/myImage:1234_container` | `gcr_io_example_repo_myImage_1234_container` |
 
 Example
 ```yaml
diff --git a/pkg/skaffold/deploy/kubectl/cli.go b/pkg/skaffold/deploy/kubectl/cli.go
index 7a9dbf6a5..8ac3743de 100644
--- a/pkg/skaffold/deploy/kubectl/cli.go
+++ b/pkg/skaffold/deploy/kubectl/cli.go
@@ -61,6 +61,7 @@ type Config interface {
 	GetNamespace() string
 	DefaultPipeline() latest.Pipeline
 	Tail() bool
+	IsMultiCluster() bool
 	PipelineForImage(imageName string) (latest.Pipeline, bool)
 	JSONParseConfig() latest.JSONParseConfig
 	EnablePlatformNodeAffinityInRenderedManifests() bool
diff --git a/pkg/skaffold/docker/image.go b/pkg/skaffold/docker/image.go
index 9593b071b..ea096aff4 100644
--- a/pkg/skaffold/docker/image.go
+++ b/pkg/skaffold/docker/image.go
@@ -46,6 +46,7 @@ import (
 	sErrors "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/errors"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util/term"
 )
 
@@ -667,7 +668,7 @@ func ToCLIBuildArgs(a *latest.DockerArtifact, evaluatedArgs map[string]*string)
 	for _, secret := range a.Secrets {
 		secretString := fmt.Sprintf("id=%s", secret.ID)
 		if secret.Source != "" {
-			secretString += ",src=" + secret.Source
+			secretString += ",src=" + util.ExpandHomePath(secret.Source)
 		}
 		if secret.Env != "" {
 			secretString += ",env=" + secret.Env
diff --git a/pkg/skaffold/kubernetes/logger/formatter.go b/pkg/skaffold/kubernetes/logger/formatter.go
index f1e347f2d..371836cf3 100644
--- a/pkg/skaffold/kubernetes/logger/formatter.go
+++ b/pkg/skaffold/kubernetes/logger/formatter.go
@@ -36,6 +36,7 @@ type kubernetesLogFormatter struct {
 	colorPicker     output.ColorPicker
 	prefix          string
 	JSONParseConfig latest.JSONParseConfig
+	kubeContext     string
 
 	pod       *v1.Pod
 	container v1.ContainerStatus
@@ -44,11 +45,12 @@ type kubernetesLogFormatter struct {
 	isMuted func() bool
 }
 
-func newKubernetesLogFormatter(config Config, colorPicker output.ColorPicker, isMuted func() bool, pod *v1.Pod, container v1.ContainerStatus) *kubernetesLogFormatter {
+func newKubernetesLogFormatter(config Config, colorPicker output.ColorPicker, isMuted func() bool, kubeContext string, pod *v1.Pod, container v1.ContainerStatus) *kubernetesLogFormatter {
 	return &kubernetesLogFormatter{
 		colorPicker:     colorPicker,
-		prefix:          prefix(config, pod, container),
+		prefix:          clusterPrefix(config, kubeContext, pod, container),
 		JSONParseConfig: config.JSONParseConfig(),
+		kubeContext:     kubeContext,
 		pod:             pod,
 		container:       container,
 		isMuted:         isMuted,
@@ -91,6 +93,13 @@ func (k *kubernetesLogFormatter) color() output.Color {
 	return output.None
 }
 
+func clusterPrefix(config Config, kubeContext string, pod *v1.Pod, container v1.ContainerStatus) string {
+	if config.IsMultiCluster() {
+		return fmt.Sprintf("[%s]%s", kubeContext, prefix(config, pod, container))
+	}
+	return prefix(config, pod, container)
+}
+
 func prefix(config Config, pod *v1.Pod, container v1.ContainerStatus) string {
 	var c latest.Pipeline
 	var present bool
diff --git a/pkg/skaffold/kubernetes/logger/log.go b/pkg/skaffold/kubernetes/logger/log.go
index f74b9aa98..98a885a83 100644
--- a/pkg/skaffold/kubernetes/logger/log.go
+++ b/pkg/skaffold/kubernetes/logger/log.go
@@ -61,6 +61,7 @@ type LogAggregator struct {
 
 type Config interface {
 	Tail() bool
+	IsMultiCluster() bool
 	PipelineForImage(imageName string) (latest.Pipeline, bool)
 	DefaultPipeline() latest.Pipeline
 	JSONParseConfig() latest.JSONParseConfig
@@ -80,7 +81,7 @@ func NewLogAggregator(cli *kubectl.CLI, podSelector kubernetes.PodSelector, name
 	}
 	a.formatter = func(p v1.Pod, c v1.ContainerStatus, isMuted func() bool) log.Formatter {
 		pod := p
-		return newKubernetesLogFormatter(config, a.colorPicker, isMuted, &pod, c)
+		return newKubernetesLogFormatter(config, a.colorPicker, isMuted, cli.KubeContext, &pod, c)
 	}
 	return a
 }
diff --git a/pkg/skaffold/render/renderer/kpt/kpt.go b/pkg/skaffold/render/renderer/kpt/kpt.go
index 53ba33f6b..81b071b38 100644
--- a/pkg/skaffold/render/renderer/kpt/kpt.go
+++ b/pkg/skaffold/render/renderer/kpt/kpt.go
@@ -17,19 +17,22 @@ limitations under the License.
 package kpt
 
 import (
+	"bytes"
 	"context"
-	"fmt"
 	"io"
 	"os/exec"
 
-	"github.com/blang/semver"
 	apimachinery "k8s.io/apimachinery/pkg/runtime/schema"
+	"sigs.k8s.io/kustomize/kyaml/fn/framework"
+	"sigs.k8s.io/kustomize/kyaml/kio"
+	"sigs.k8s.io/kustomize/kyaml/yaml"
 
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/instrumentation"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/kubernetes/manifest"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/generate"
+	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/kptfile"
 	rUtil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/renderer/util"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/transform"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/validate"
@@ -54,15 +57,6 @@ type Kpt struct {
 	transformDenylist  map[apimachinery.GroupKind]latest.ResourceFilter
 }
 
-const (
-	DryFileName = "manifests.yaml"
-)
-
-var (
-	KptVersion                      = currentKptVersion
-	maxKptVersionAllowedForDeployer = "1.0.0-beta.24"
-)
-
 func New(cfg render.Config, rCfg latest.RenderConfig, hydrationDir string, labels map[string]string, configName string, ns string, manifestOverrides map[string]string) (*Kpt, error) {
 	transformAllowlist, transformDenylist, err := rUtil.ConsolidateTransformConfiguration(cfg)
 	if err != nil {
@@ -117,7 +111,21 @@ func (r *Kpt) Render(ctx context.Context, out io.Writer, builds []graph.Artifact
 		cmd := exec.Command("kpt", "fn", "render", p, "-o", "unwrap")
 
 		if buf, err := util.RunCmdOut(rCtx, cmd); err == nil {
-			manifestList.Append(buf)
+			reader := kio.ByteReader{Reader: bytes.NewBuffer(buf)}
+			b := bytes.NewBuffer([]byte{})
+			writer := kio.ByteWriter{Writer: b}
+			// Kpt fn render outputs Kptfile and Config data files content in result, we don't want them in our manifestList as these cannot be deployed to k8s cluster.
+			pipeline := kio.Pipeline{Filters: []kio.Filter{framework.ResourceMatcherFunc(func(node *yaml.RNode) bool {
+				meta, _ := node.GetMeta()
+				return node.GetKind() != kptfile.KptFileKind && meta.Annotations["config.kubernetes.io/local-config"] != "true"
+			})},
+				Inputs:  []kio.Reader{&reader},
+				Outputs: []kio.Writer{writer},
+			}
+			if err := pipeline.Execute(); err != nil {
+				return ml, err
+			}
+			manifestList.Append(b.Bytes())
 		} else {
 			endTrace(instrumentation.TraceEndError(err))
 			// TODO(yuwenma): How to guide users when they face kpt error (may due to bad user config)?
@@ -156,32 +164,3 @@ func (r *Kpt) Render(ctx context.Context, out io.Writer, builds []graph.Artifact
 	ml.Add(r.configName, manifestList)
 	return ml, err
 }
-
-func CheckIsProperBinVersion(ctx context.Context) error {
-	maxAllowedVersion := semver.MustParse(maxKptVersionAllowedForDeployer)
-	version, err := KptVersion(ctx)
-	if err != nil {
-		return err
-	}
-
-	currentVersion, err := semver.ParseTolerant(version)
-	if err != nil {
-		return err
-	}
-
-	if currentVersion.GT(maxAllowedVersion) {
-		return fmt.Errorf("max allowed verion for Kpt renderer without Kpt deployer is %v, detected version is %v", maxKptVersionAllowedForDeployer, currentVersion)
-	}
-
-	return nil
-}
-
-func currentKptVersion(ctx context.Context) (string, error) {
-	cmd := exec.Command("kpt", "version")
-	b, err := util.RunCmdOut(ctx, cmd)
-	if err != nil {
-		return "", fmt.Errorf("kpt version command failed: %w", err)
-	}
-	version := string(b)
-	return version, nil
-}
diff --git a/pkg/skaffold/runner/runcontext/context.go b/pkg/skaffold/runner/runcontext/context.go
index 99accfc81..0641ddfb0 100644
--- a/pkg/skaffold/runner/runcontext/context.go
+++ b/pkg/skaffold/runner/runcontext/context.go
@@ -84,6 +84,22 @@ func (ps Pipelines) IsMultiPipeline() bool {
 	return len(ps.pipelines) > 1
 }
 
+// IsMultiPipeline returns true if there are more than one target kubernetes clusters.
+func (ps Pipelines) IsMultiCluster() bool {
+	var k string
+	for _, p := range ps.pipelines {
+		if p.Deploy.KubeContext == "" {
+			continue
+		}
+		if k == "" {
+			k = p.Deploy.KubeContext
+		} else if k != p.Deploy.KubeContext {
+			return true
+		}
+	}
+	return false
+}
+
 func (ps Pipelines) PortForwardResources() []*latest.PortForwardResource {
 	var pf []*latest.PortForwardResource
 	for _, p := range ps.pipelines {
@@ -284,9 +300,11 @@ func (rc *RunContext) GetNamespace() string {
 	}
 	return strings.Trim(string(b), "'")
 }
-func (rc *RunContext) AutoBuild() bool                               { return rc.Opts.AutoBuild }
-func (rc *RunContext) DisableMultiPlatformBuild() bool               { return rc.Opts.DisableMultiPlatformBuild }
-func (rc *RunContext) CheckClusterNodePlatforms() bool               { return rc.Opts.CheckClusterNodePlatforms }
+func (rc *RunContext) AutoBuild() bool                 { return rc.Opts.AutoBuild }
+func (rc *RunContext) DisableMultiPlatformBuild() bool { return rc.Opts.DisableMultiPlatformBuild }
+func (rc *RunContext) CheckClusterNodePlatforms() bool {
+	return rc.Opts.CheckClusterNodePlatforms && !rc.IsMultiCluster()
+}
 func (rc *RunContext) AutoDeploy() bool                              { return rc.Opts.AutoDeploy }
 func (rc *RunContext) AutoSync() bool                                { return rc.Opts.AutoSync }
 func (rc *RunContext) ContainerDebugging() bool                      { return rc.Opts.ContainerDebugging }
@@ -297,6 +315,7 @@ func (rc *RunContext) CustomLabels() []string                        { return rc
 func (rc *RunContext) CustomTag() string                             { return rc.Opts.CustomTag }
 func (rc *RunContext) DefaultRepo() *string                          { return rc.Cluster.DefaultRepo.Value() }
 func (rc *RunContext) MultiLevelRepo() *bool                         { return rc.Opts.MultiLevelRepo }
+func (rc *RunContext) IsMultiCluster() bool                          { return rc.Pipelines.IsMultiCluster() }
 func (rc *RunContext) Mode() config.RunMode                          { return rc.Opts.Mode() }
 func (rc *RunContext) DryRun() bool                                  { return rc.Opts.DryRun }
 func (rc *RunContext) ForceDeploy() bool                             { return rc.Opts.Force }
diff --git a/pkg/skaffold/schema/validation/validation.go b/pkg/skaffold/schema/validation/validation.go
index 872adec28..506c13b1b 100644
--- a/pkg/skaffold/schema/validation/validation.go
+++ b/pkg/skaffold/schema/validation/validation.go
@@ -36,7 +36,6 @@ import (
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/parser"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/parser/configlocations"
-	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/renderer/kpt"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
@@ -87,7 +86,6 @@ func ProcessToErrorWithLocation(configs parser.SkaffoldConfigSet, validateConfig
 		errs = append(errs, validateTaggingPolicy(config, config.Build)...)
 		errs = append(errs, validateCustomTest(config, config.Test)...)
 		errs = append(errs, validateGCBConfig(config, config.Build)...)
-		errs = append(errs, validateKptRendererVersion(config, config.Deploy, config.Render)...)
 	}
 	errs = append(errs, validateArtifactDependencies(configs)...)
 	if validateConfig.CheckDeploySource {
@@ -100,25 +98,6 @@ func ProcessToErrorWithLocation(configs parser.SkaffoldConfigSet, validateConfig
 	return errs
 }
 
-func validateKptRendererVersion(cfg *parser.SkaffoldConfigEntry, dc latest.DeployConfig, rc latest.RenderConfig) (cfgErrs []ErrorWithLocation) {
-	if dc.KptDeploy != nil {
-		return
-	}
-
-	if rc.Kpt == nil && rc.Transform == nil && rc.Validate == nil { // no kpt renderer created
-		return
-	}
-
-	if err := kpt.CheckIsProperBinVersion(context.TODO()); err != nil {
-		cfgErrs = append(cfgErrs, ErrorWithLocation{
-			Error:    err,
-			Location: cfg.YAMLInfos.LocateField(cfg, "Render"),
-		})
-	}
-
-	return
-}
-
 // Process checks if the Skaffold pipeline is valid and returns all encountered errors as a concatenated string
 func Process(configs parser.SkaffoldConfigSet, validateConfig Options) error {
 	errs := ProcessToErrorWithLocation(configs, validateConfig)
diff --git a/pkg/skaffold/util/util.go b/pkg/skaffold/util/util.go
index 4ec669730..b3547910b 100644
--- a/pkg/skaffold/util/util.go
+++ b/pkg/skaffold/util/util.go
@@ -317,8 +317,10 @@ func hasHiddenPrefix(s string) bool {
 }
 
 func SanitizeHelmTemplateValue(s string) string {
-	// replaces commonly used image name chars that are illegal go template chars -> replaces "/", "-" and "." with "_"
-	return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(s, ".", "_"), "-", "_"), "/", "_")
+	// replaces commonly used image name chars that are illegal go template chars
+	// replaces "/", "-", "." and ":" with "_"
+	r := strings.NewReplacer(".", "_", "-", "_", "/", "_", ":", "_")
+	return r.Replace(s)
 }
 
 func ParseNamespaceFromFlags(flgs []string) string {
@@ -338,3 +340,11 @@ func ParseNamespaceFromFlags(flgs []string) string {
 	}
 	return ""
 }
+
+func ExpandHomePath(path string) string {
+	if strings.HasPrefix(path, "~/") {
+		dirname, _ := os.UserHomeDir()
+		path = filepath.Join(dirname, path[2:])
+	}
+	return path
+}


Test patch:
diff --git a/integration/examples/grpc-e2e-tests/service/go.mod b/integration/examples/grpc-e2e-tests/service/go.mod
index e9b851895..86e1f45a7 100644
--- a/integration/examples/grpc-e2e-tests/service/go.mod
+++ b/integration/examples/grpc-e2e-tests/service/go.mod
@@ -23,10 +23,10 @@ require (
 	github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
 	github.com/googleapis/gax-go/v2 v2.4.0 // indirect
 	go.opencensus.io v0.23.0 // indirect
-	golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
+	golang.org/x/net v0.7.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
-	golang.org/x/text v0.3.8 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20220916134934-764224ccc2d1 // indirect
diff --git a/integration/examples/grpc-e2e-tests/service/go.sum b/integration/examples/grpc-e2e-tests/service/go.sum
index 2e20d53f1..e0ed33e17 100644
--- a/integration/examples/grpc-e2e-tests/service/go.sum
+++ b/integration/examples/grpc-e2e-tests/service/go.sum
@@ -302,8 +302,9 @@ golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
 golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -397,8 +398,9 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -410,8 +412,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/integration/render_test.go b/integration/render_test.go
index 454e64c92..5dac19d9d 100644
--- a/integration/render_test.go
+++ b/integration/render_test.go
@@ -1346,6 +1346,196 @@ spec:
 	}
 }
 
+func TestKptRender(t *testing.T) {
+	tests := []struct {
+		description string
+		args        []string
+		config      string
+		input       map[string]string // file path => content
+		expectedOut string
+	}{
+		{
+			description: "simple kpt render",
+			args:        []string{"--offline=true"},
+			config: `apiVersion: skaffold/v4beta2
+kind: Config
+metadata:
+  name: getting-started-kustomize
+manifests:
+  kpt:
+    - apply-simple
+`, input: map[string]string{"apply-simple/Kptfile": `apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: apply-setters-simple
+upstream:
+  type: git
+  git:
+    repo: https://github.com/GoogleContainerTools/kpt-functions-catalog
+    directory: /examples/apply-setters-simple
+    ref: apply-setters/v0.2.0
+  updateStrategy: resource-merge
+upstreamLock:
+  type: git
+  git:
+    repo: https://github.com/GoogleContainerTools/kpt-functions-catalog
+    directory: /examples/apply-setters-simple
+    ref: apply-setters/v0.2.0
+    commit: 9b6ce80e355a53727d21b2b336f8da55e760e20ca
+pipeline:
+  mutators:
+    - image: gcr.io/kpt-fn/apply-setters:v0.2
+      configMap:
+        nginx-replicas: 3
+        tag: 1.16.2
+`, "apply-simple/resources.yaml": `apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-nginx
+spec:
+  replicas: 4 # kpt-set: ${nginx-replicas}
+  selector:
+    matchLabels:
+      app: nginx
+  template:
+    metadata:
+      labels:
+        app: nginx
+    spec:
+      containers:
+        - name: nginx
+          image: "nginx:1.16.1" # kpt-set: nginx:${tag}
+          ports:
+            - protocol: TCP
+              containerPort: 80`},
+			expectedOut: `apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-nginx
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: nginx
+  template:
+    metadata:
+      labels:
+        app: nginx
+    spec:
+      containers:
+        - name: nginx
+          image: "nginx:1.16.2"
+          ports:
+            - protocol: TCP
+              containerPort: 80
+`},
+		{
+			description: "kpt render with data config file",
+			args:        []string{"--offline=true"},
+			config: `apiVersion: skaffold/v4beta2
+kind: Config
+metadata:
+  name: getting-started-kustomize
+manifests:
+  kpt:
+    - set-annotations
+`,
+			input: map[string]string{"set-annotations/Kptfile": `apiVersion: kpt.dev/v1
+kind: Kptfile
+metadata:
+  name: example
+pipeline:
+  mutators:
+    - image: gcr.io/kpt-fn/set-annotations:v0.1.4
+      configPath: fn-config.yaml`,
+				"set-annotations/fn-config.yaml": `apiVersion: fn.kpt.dev/v1alpha1
+kind: SetAnnotations
+metadata: # kpt-merge: /my-func-config
+  name: my-func-config
+  annotations:
+    config.kubernetes.io/local-config: "true"
+    internal.kpt.dev/upstream-identifier: 'fn.kpt.dev|SetAnnotations|default|my-func-config'
+annotations:
+  color: orange
+  fruit: apple
+`,
+				"set-annotations/resources.yaml": `apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-nginx
+spec:
+  replicas: 3 # kpt-set: ${nginx-replicas}
+  selector:
+    matchLabels:
+      app: nginx
+  template:
+    metadata:
+      labels:
+        app: nginx
+    spec:
+      containers:
+        - name: nginx
+          image: "nginx:1.16.1" # kpt-set: nginx:${tag}
+          ports:
+            - protocol: TCP
+              containerPort: 80`},
+			expectedOut: `apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: my-nginx
+  annotations: 
+    color: orange
+    fruit: apple
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: nginx
+  template:
+    metadata:
+      annotations: 
+        color: orange
+        fruit: apple
+      labels:
+        app: nginx
+    spec:
+      containers:
+        - name: nginx
+          image: "nginx:1.16.1"
+          ports:
+            - protocol: TCP
+              containerPort: 80
+`},
+	}
+
+	for _, test := range tests {
+		testutil.Run(t, test.description, func(t *testutil.T) {
+			MarkIntegrationTest(t.T, CanRunWithoutGcp)
+			tmpDir := t.NewTempDir()
+			tmpDir.Write("skaffold.yaml", test.config)
+
+			for filePath, content := range test.input {
+				tmpDir.Write(filePath, content)
+			}
+
+			tmpDir.Chdir()
+			output := skaffold.Render(test.args...).RunOrFailOutput(t.T)
+			var out map[string]any
+			var ex map[string]any
+			err := yaml.Unmarshal(output, &out)
+			if err != nil {
+				return
+			}
+			err = yaml.Unmarshal([]byte(test.expectedOut), &ex)
+			if err != nil {
+				return
+			}
+
+			t.CheckDeepEqual(ex, out)
+		})
+	}
+}
+
 func TestHelmRenderWithImagesFlag(t *testing.T) {
 	tests := []struct {
 		description  string
diff --git a/pkg/skaffold/docker/image_test.go b/pkg/skaffold/docker/image_test.go
index 448b3fbff..5c84e9d11 100644
--- a/pkg/skaffold/docker/image_test.go
+++ b/pkg/skaffold/docker/image_test.go
@@ -18,6 +18,7 @@ package docker
 
 import (
 	"context"
+	"fmt"
 	"io"
 	"sync"
 	"sync/atomic"
@@ -370,6 +371,15 @@ func TestGetBuildArgs(t *testing.T) {
 			},
 			want: []string{"--secret", "id=mysecret,src=foo.src"},
 		},
+		{
+			description: "secret with file source in home directory",
+			artifact: &latest.DockerArtifact{
+				Secrets: []*latest.DockerSecret{
+					{ID: "mysecret", Source: "~/foo.src"},
+				},
+			},
+			want: []string{"--secret", fmt.Sprintf("id=mysecret,src=%s", util.ExpandHomePath("~/foo.src"))},
+		},
 		{
 			description: "secret with env source",
 			artifact: &latest.DockerArtifact{
diff --git a/pkg/skaffold/kubernetes/logger/formatter_test.go b/pkg/skaffold/kubernetes/logger/formatter_test.go
index 2fcf0b67c..5d9000ef5 100644
--- a/pkg/skaffold/kubernetes/logger/formatter_test.go
+++ b/pkg/skaffold/kubernetes/logger/formatter_test.go
@@ -38,7 +38,8 @@ func (m *mockColorPicker) Pick(image string) output.Color {
 func (m *mockColorPicker) AddImage(string) {}
 
 type mockConfig struct {
-	log latest.LogsConfig
+	log            latest.LogsConfig
+	isMultiCluster bool
 }
 
 func (c *mockConfig) Tail() bool {
@@ -61,6 +62,10 @@ func (c *mockConfig) JSONParseConfig() latest.JSONParseConfig {
 	return c.log.JSONParse
 }
 
+func (c *mockConfig) IsMultiCluster() bool {
+	return c.isMultiCluster
+}
+
 func TestPrintLogLine(t *testing.T) {
 	testutil.Run(t, "verify lines are not intermixed", func(t *testutil.T) {
 		var (
@@ -71,7 +76,7 @@ func TestPrintLogLine(t *testing.T) {
 			groups        = 5
 		)
 
-		f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{Prefix: "none"}}, &mockColorPicker{}, func() bool { return false }, &v1.Pod{}, v1.ContainerStatus{})
+		f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{Prefix: "none"}}, &mockColorPicker{}, func() bool { return false }, "my-context", &v1.Pod{}, v1.ContainerStatus{})
 
 		for i := 0; i < groups; i++ {
 			wg.Add(1)
@@ -155,7 +160,7 @@ func TestColorForPod(t *testing.T) {
 	p.AddImage("second")
 
 	for _, test := range tests {
-		f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{Prefix: "none"}}, p, func() bool { return false }, test.pod, v1.ContainerStatus{})
+		f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{Prefix: "none"}}, p, func() bool { return false }, "my-context", test.pod, v1.ContainerStatus{})
 
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			color := f.color()
@@ -171,6 +176,7 @@ func TestPrefix(t *testing.T) {
 		prefix         string
 		pod            v1.Pod
 		container      v1.ContainerStatus
+		isMultiCluster bool
 		expectedPrefix string
 	}{
 		{
@@ -215,12 +221,20 @@ func TestPrefix(t *testing.T) {
 			container:      containerWithName("hello"),
 			expectedPrefix: "",
 		},
+		{
+			description:    "multi cluster",
+			prefix:         "container",
+			isMultiCluster: true,
+			pod:            podWithName("pod"),
+			container:      containerWithName("container"),
+			expectedPrefix: "[my-cluster][container]",
+		},
 	}
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
 			f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{
 				Prefix: test.prefix,
-			}}, &mockColorPicker{}, func() bool { return false }, &test.pod, test.container)
+			}, isMultiCluster: test.isMultiCluster}, &mockColorPicker{}, func() bool { return false }, "my-cluster", &test.pod, test.container)
 
 			t.CheckDeepEqual(test.expectedPrefix, f.prefix)
 		})
@@ -247,7 +261,7 @@ func TestPrintline(t *testing.T) {
 			pod := podWithName("hello")
 			f := newKubernetesLogFormatter(&mockConfig{log: latest.LogsConfig{
 				Prefix: "auto",
-			}}, &mockColorPicker{}, func() bool { return test.isMuted }, &pod,
+			}}, &mockColorPicker{}, func() bool { return test.isMuted }, "my-context", &pod,
 				containerWithName("container"))
 			var out bytes.Buffer
 			f.PrintLine(&out, "test line")
diff --git a/pkg/skaffold/schema/validation/samples_test.go b/pkg/skaffold/schema/validation/samples_test.go
index 5a53d624c..8df24f099 100644
--- a/pkg/skaffold/schema/validation/samples_test.go
+++ b/pkg/skaffold/schema/validation/samples_test.go
@@ -18,7 +18,6 @@ package validation
 
 import (
 	"bytes"
-	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -26,7 +25,6 @@ import (
 	"testing"
 
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/parser"
-	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/render/renderer/kpt"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/defaults"
 	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
@@ -94,9 +92,6 @@ func checkSkaffoldConfig(t *testutil.T, yaml []byte) {
 		t.CheckNoError(err)
 		cfgs = append(cfgs, cfg)
 	}
-	t.Override(&kpt.KptVersion, func(_ context.Context) (string, error) {
-		return "1.0.0-beta.13", nil
-	})
 	err = Process(cfgs, Options{CheckDeploySource: false})
 	t.CheckNoError(err)
 }