Repository: linkerd/linkerd2
Pull request #5567 (2021-01-19 08:13:28)

Fix `linkerd metrics` resource selector
The `linkerd metrics` command was selecting pods based on owner resource
names. If multiple owners existed with the same name (for example
`sts/web`, `deploy/web`), additional pods would be incorrectly included
in the output.

Fix the pod selector code to validate pods have owner references to the
given workload/owner.

Before:
```
$ linkerd metrics -n emojivoto deploy/web|grep POD
  # POD web-0 (1 of 3)
  # POD web-d9ffd684f-gnbcx (2 of 3)
  # POD web-fs6l7 (3 of 3)
```

After:
```
$ bin/go-run cli metrics -n emojivoto deploy/web|grep POD
  # POD web-d9ffd684f-gnbcx (1 of 1)
```

Signed-off-by: Andrew Seigner <siggy@buoyant.io>

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->



Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e1af959fd..b573b6e91 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -39,7 +39,7 @@ jobs:
       uses: crazy-max/ghaction-docker-buildx@552c9de
     - name: Build & Push Multi Arch Images
       env:
-        DOCKER_TRACE: 1
+        #DOCKER_TRACE: 1
         DOCKER_MULTIARCH: 1
         DOCKER_PUSH: 1
       run: |
diff --git a/.proxy-version b/.proxy-version
index 2d54b0604..64c5512ba 100644
--- a/.proxy-version
+++ b/.proxy-version
@@ -1,1 +1,1 @@
-v2.128.0
+v2.129.0
diff --git a/BUILD.md b/BUILD.md
index 7497568e2..fa059741e 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -121,7 +121,7 @@ Started](https://linkerd.io/2/getting-started/)
 bin/k3d cluster create
 
 # build all docker images
-DOCKER_TRACE=1 bin/docker-build
+bin/docker-build
 
 # load all the images into k3d
 bin/image-load --k3d
@@ -431,7 +431,7 @@ The `bin/docker-build-proxy` script builds the proxy by pulling a pre-published
 proxy binary:
 
 ```bash
-DOCKER_TRACE=1 bin/docker-build-proxy
+bin/docker-build-proxy
 ```
 
 ### Multi-architecture builds
diff --git a/CHANGES.md b/CHANGES.md
index d9c8e3d3b..719b7e96f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -115,8 +115,8 @@ async runtime.
   especially for busy HTTP/1.1 clients
 * Fixed an issue in the proxy's HTTP protocol detection which could cause false
   positives for non-HTTP traffic
-* Increased the proxy's default dispatch timeout to 5 seconds to accomodate
-  connection pools which might open conenctions without immediately making a
+* Increased the proxy's default dispatch timeout to 5 seconds to accommodate
+  connection pools which might open connections without immediately making a
   request
 * Updated the proxy's Tokio dependency to v0.3
 
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 25527d4b3..a0cf7e6d8 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -9,7 +9,6 @@ The Linkerd maintainers are:
 * Eliza Weisman <eliza@buoyant.io> @hawkw
 * Hema Lee <Hemalekha.Lee@nordstrom.com> @hemakl
 * Kevin Leimkuhler <kevinl@buoyant.io> @kleimkuhler
-* Risha Mars <mars@buoyant.io> @rmars
 * Zahari Dichev <zahari@buoyant.io> @zaharidichev
 
 ## Emeritus
@@ -17,6 +16,7 @@ The Linkerd maintainers are:
 Former maintainers include:
 
 * Kevin Ingleman <ki@buoyant.io> @klingerf
+* Risha Mars <mars@buoyant.io> @rmars
 
 <!--
 # Adding a new maintainer
diff --git a/bin/_docker.sh b/bin/_docker.sh
index e10f023f2..6481f288f 100644
--- a/bin/_docker.sh
+++ b/bin/_docker.sh
@@ -11,9 +11,6 @@ bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 # docker registry in, for instance, CI.
 export DOCKER_REGISTRY=${DOCKER_REGISTRY:-ghcr.io/linkerd}
 
-# When set, causes docker's build output to be emitted to stderr.
-export DOCKER_TRACE=${DOCKER_TRACE:-}
-
 # When set, use `docker buildx` and use the github actions cache to store/retrieve images
 export DOCKER_BUILDKIT=${DOCKER_BUILDKIT:-}
 
@@ -50,11 +47,6 @@ docker_build() {
     file=$1
     shift
 
-    output=/dev/null
-    if [ -n "$DOCKER_TRACE" ]; then
-        output=/dev/stderr
-    fi
-
     rootdir=$( cd "$bindir"/.. && pwd )
 
     if [ -n "$DOCKER_BUILDKIT" ]; then
@@ -82,15 +74,13 @@ docker_build() {
           $output_params \
           -t "$repo:$tag" \
           -f "$file" \
-          "$@" \
-          > "$output"
+          "$@"
     else
       log_debug "  :; docker build $rootdir -t $repo:$tag -f $file $*"
       docker build "$rootdir" \
           -t "$repo:$tag" \
           -f "$file" \
-          "$@" \
-          > "$output"
+          "$@"
     fi
 
     echo "$repo:$tag"
diff --git a/charts/linkerd2/templates/serviceprofile-crd.yaml b/charts/linkerd2/templates/serviceprofile-crd.yaml
index 27bdc3fd4..8eaaa48ba 100644
--- a/charts/linkerd2/templates/serviceprofile-crd.yaml
+++ b/charts/linkerd2/templates/serviceprofile-crd.yaml
@@ -21,6 +21,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
diff --git a/charts/linkerd2/templates/trafficsplit-crd.yaml b/charts/linkerd2/templates/trafficsplit-crd.yaml
index f89b0db2e..c843bb937 100644
--- a/charts/linkerd2/templates/trafficsplit-crd.yaml
+++ b/charts/linkerd2/templates/trafficsplit-crd.yaml
@@ -1,7 +1,7 @@
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -14,14 +14,61 @@ metadata:
     {{.Values.global.controllerNamespaceLabel}}: {{.Values.global.namespace}}
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/charts/partials/templates/_proxy.tpl b/charts/partials/templates/_proxy.tpl
index a32063144..e752cd884 100644
--- a/charts/partials/templates/_proxy.tpl
+++ b/charts/partials/templates/_proxy.tpl
@@ -90,13 +90,6 @@ env:
 - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
   value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
 {{ end -}}
-{{ if .Values.global.proxy.disableTap -}}
-- name: LINKERD2_PROXY_TAP_DISABLED
-  value: "true"
-{{ else if not .Values.global.proxy.disableIdentity -}}
-- name: LINKERD2_PROXY_TAP_SVC_NAME
-  value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-{{ end -}}
 image: {{.Values.global.proxy.image.name}}:{{.Values.global.proxy.image.version}}
 imagePullPolicy: {{.Values.global.proxy.image.pullPolicy}}
 livenessProbe:
diff --git a/cli/cmd/metrics.go b/cli/cmd/metrics.go
index 63eaa4de7..95240729b 100644
--- a/cli/cmd/metrics.go
+++ b/cli/cmd/metrics.go
@@ -14,6 +14,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes"
 )
 
@@ -137,15 +138,17 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 	}
 
 	var matchLabels map[string]string
+	var ownerUID types.UID
 	switch res.GetType() {
 	case k8s.CronJob:
 		jobs, err := clientset.BatchV1().Jobs(namespace).List(ctx, metav1.ListOptions{})
 		if err != nil {
 			return nil, err
 		}
+
 		var pods []corev1.Pod
 		for _, job := range jobs.Items {
-			if isOwner(res.GetName(), job.GetOwnerReferences()) {
+			if isOwner(job.GetUID(), job.GetOwnerReferences()) {
 				jobPods, err := getPodsFor(ctx, clientset, namespace, fmt.Sprintf("%s/%s", k8s.Job, job.GetName()))
 				if err != nil {
 					return nil, err
@@ -161,6 +164,7 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = ds.Spec.Selector.MatchLabels
+		ownerUID = ds.GetUID()
 
 	case k8s.Deployment:
 		deployment, err := clientset.AppsV1().Deployments(namespace).Get(ctx, res.GetName(), metav1.GetOptions{})
@@ -168,6 +172,29 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = deployment.Spec.Selector.MatchLabels
+		ownerUID = deployment.GetUID()
+
+		replicaSets, err := clientset.AppsV1().ReplicaSets(namespace).List(
+			ctx,
+			metav1.ListOptions{
+				LabelSelector: labels.Set(matchLabels).AsSelector().String(),
+			},
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		var pods []corev1.Pod
+		for _, rs := range replicaSets.Items {
+			if isOwner(ownerUID, rs.GetOwnerReferences()) {
+				podsRS, err := getPodsFor(ctx, clientset, namespace, fmt.Sprintf("%s/%s", k8s.ReplicaSet, rs.GetName()))
+				if err != nil {
+					return nil, err
+				}
+				pods = append(pods, podsRS...)
+			}
+		}
+		return pods, nil
 
 	case k8s.Job:
 		job, err := clientset.BatchV1().Jobs(namespace).Get(ctx, res.GetName(), metav1.GetOptions{})
@@ -175,6 +202,7 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = job.Spec.Selector.MatchLabels
+		ownerUID = job.GetUID()
 
 	case k8s.ReplicaSet:
 		rs, err := clientset.AppsV1().ReplicaSets(namespace).Get(ctx, res.GetName(), metav1.GetOptions{})
@@ -182,6 +210,7 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = rs.Spec.Selector.MatchLabels
+		ownerUID = rs.GetUID()
 
 	case k8s.ReplicationController:
 		rc, err := clientset.CoreV1().ReplicationControllers(namespace).Get(ctx, res.GetName(), metav1.GetOptions{})
@@ -189,6 +218,7 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = rc.Spec.Selector
+		ownerUID = rc.GetUID()
 
 	case k8s.StatefulSet:
 		ss, err := clientset.AppsV1().StatefulSets(namespace).Get(ctx, res.GetName(), metav1.GetOptions{})
@@ -196,6 +226,7 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 			return nil, err
 		}
 		matchLabels = ss.Spec.Selector.MatchLabels
+		ownerUID = ss.GetUID()
 
 	default:
 		return nil, fmt.Errorf("unsupported resource type: %s", res.GetType())
@@ -214,12 +245,23 @@ func getPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
 		return nil, err
 	}
 
-	return podList.Items, nil
+	if ownerUID == "" {
+		return podList.Items, nil
+	}
+
+	pods := []corev1.Pod{}
+	for _, pod := range podList.Items {
+		if isOwner(ownerUID, pod.GetOwnerReferences()) {
+			pods = append(pods, pod)
+		}
+	}
+
+	return pods, nil
 }
 
-func isOwner(resourceName string, ownerRefs []metav1.OwnerReference) bool {
+func isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {
 	for _, or := range ownerRefs {
-		if resourceName == or.Name {
+		if u == or.UID {
 			return true
 		}
 	}
diff --git a/controller/cmd/main.go b/controller/cmd/main.go
index 4de76cdd2..23fac22f5 100644
--- a/controller/cmd/main.go
+++ b/controller/cmd/main.go
@@ -11,6 +11,7 @@ import (
 	publicapi "github.com/linkerd/linkerd2/controller/cmd/public-api"
 	spvalidator "github.com/linkerd/linkerd2/controller/cmd/sp-validator"
 	"github.com/linkerd/linkerd2/controller/cmd/tap"
+	tapinjector "github.com/linkerd/linkerd2/controller/cmd/tap-injector"
 	servicemirror "github.com/linkerd/linkerd2/multicluster/cmd/service-mirror"
 )
 
@@ -35,6 +36,8 @@ func main() {
 		spvalidator.Main(os.Args[2:])
 	case "tap":
 		tap.Main(os.Args[2:])
+	case "tap-injector":
+		tapinjector.Main(os.Args[2:])
 	case "service-mirror":
 		servicemirror.Main(os.Args[2:])
 	default:
diff --git a/controller/cmd/tap-injector/main.go b/controller/cmd/tap-injector/main.go
new file mode 100644
index 000000000..0a6ca7841
--- /dev/null
+++ b/controller/cmd/tap-injector/main.go
@@ -0,0 +1,31 @@
+package tapinjector
+
+import (
+	"context"
+	"flag"
+	"fmt"
+
+	"github.com/linkerd/linkerd2/controller/k8s"
+	tapinjector "github.com/linkerd/linkerd2/controller/tap-injector"
+	"github.com/linkerd/linkerd2/controller/webhook"
+	"github.com/linkerd/linkerd2/pkg/flags"
+)
+
+// Main executes the tap-injector subcommand
+func Main(args []string) {
+	cmd := flag.NewFlagSet("tap-injector", flag.ExitOnError)
+	metricsAddr := cmd.String("metrics-addr", fmt.Sprintf(":%d", 9995), "address to serve scrapable metrics on")
+	addr := cmd.String("addr", ":8443", "address to serve on")
+	kubeconfig := cmd.String("kubeconfig", "", "path to kubeconfig")
+	tapSvcName := cmd.String("tap-service-name", "", "name of the tap service")
+	flags.ConfigureAndParse(cmd, args)
+	webhook.Launch(
+		context.Background(),
+		[]k8s.APIResource{k8s.NS},
+		tapinjector.Mutate(*tapSvcName),
+		"tap-injector",
+		*metricsAddr,
+		*addr,
+		*kubeconfig,
+	)
+}
diff --git a/controller/proxy-injector/fake/data/pod-with-debug.patch.json b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
index 04c79ec3d..61ba6cc64 100644
--- a/controller/proxy-injector/fake/data/pod-with-debug.patch.json
+++ b/controller/proxy-injector/fake/data/pod-with-debug.patch.json
@@ -183,10 +183,6 @@
         {
           "name": "LINKERD2_PROXY_IDENTITY_DISABLED",
           "value": "disabled"
-        },
-        {
-          "name": "LINKERD2_PROXY_TAP_DISABLED",
-          "value": "true"
         }
       ],
       "image": "ghcr.io/linkerd/proxy:dev-undefined",
diff --git a/controller/proxy-injector/fake/data/pod.patch.json b/controller/proxy-injector/fake/data/pod.patch.json
index 039fc5720..38a93e2e1 100644
--- a/controller/proxy-injector/fake/data/pod.patch.json
+++ b/controller/proxy-injector/fake/data/pod.patch.json
@@ -173,10 +173,6 @@
         {
           "name": "LINKERD2_PROXY_IDENTITY_DISABLED",
           "value": "disabled"
-        },
-        {
-          "name": "LINKERD2_PROXY_TAP_DISABLED",
-          "value": "true"
         }
       ],
       "image": "ghcr.io/linkerd/proxy:dev-undefined",
diff --git a/controller/tap-injector/patch.go b/controller/tap-injector/patch.go
new file mode 100644
index 000000000..86a481bfd
--- /dev/null
+++ b/controller/tap-injector/patch.go
@@ -0,0 +1,12 @@
+package tapinjector
+
+const tpl = `[
+  {
+    "op": "add",
+    "path": "/spec/containers/{{.ProxyIndex}}/env/-",
+    "value": {
+      "name": "LINKERD2_PROXY_TAP_SVC_NAME",
+      "value": "{{.ProxyTapSvcName}}"
+    }
+  }
+]`
diff --git a/controller/tap-injector/webhook.go b/controller/tap-injector/webhook.go
new file mode 100644
index 000000000..3f31fa05a
--- /dev/null
+++ b/controller/tap-injector/webhook.go
@@ -0,0 +1,83 @@
+package tapinjector
+
+import (
+	"bytes"
+	"context"
+	"html/template"
+
+	"github.com/ghodss/yaml"
+	"github.com/linkerd/linkerd2/controller/k8s"
+	"github.com/linkerd/linkerd2/controller/webhook"
+	labels "github.com/linkerd/linkerd2/pkg/k8s"
+	"github.com/prometheus/common/log"
+	admissionv1beta1 "k8s.io/api/admission/v1beta1"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/client-go/tools/record"
+)
+
+// Params holds the values used in the patch template.
+type Params struct {
+	ProxyIndex      int
+	ProxyTapSvcName string
+}
+
+// Mutate mutates an AdmissionRequest and adds the LINKERD2_PROXY_TAP_SVC_NAME
+// env var to a pod's proxy container if tap is not disabled via annotation on the
+// pod or the namespace.
+func Mutate(tapSvcName string) webhook.Handler {
+	return func(
+		ctx context.Context,
+		k8sAPI *k8s.API,
+		request *admissionv1beta1.AdmissionRequest,
+		recorder record.EventRecorder,
+	) (*admissionv1beta1.AdmissionResponse, error) {
+		log.Debugf("request object bytes: %s", request.Object.Raw)
+		admissionResponse := &admissionv1beta1.AdmissionResponse{
+			UID:     request.UID,
+			Allowed: true,
+		}
+		var pod *corev1.Pod
+		if err := yaml.Unmarshal(request.Object.Raw, &pod); err != nil {
+			return nil, err
+		}
+		params := Params{
+			ProxyIndex:      webhook.GetProxyContainerIndex(pod.Spec.Containers),
+			ProxyTapSvcName: tapSvcName,
+		}
+		if params.ProxyIndex < 0 {
+			return admissionResponse, nil
+		}
+		if alreadyMutated(pod.Spec.Containers[params.ProxyIndex]) {
+			return admissionResponse, nil
+		}
+		namespace, err := k8sAPI.NS().Lister().Get(request.Namespace)
+		if err != nil {
+			return nil, err
+		}
+		var t *template.Template
+		if labels.IsTapDisabled(namespace) || labels.IsTapDisabled(pod) {
+			return admissionResponse, nil
+		}
+		t, err = template.New("tpl").Parse(tpl)
+		if err != nil {
+			return nil, err
+		}
+		var patchJSON bytes.Buffer
+		if err = t.Execute(&patchJSON, params); err != nil {
+			return nil, err
+		}
+		patchType := admissionv1beta1.PatchTypeJSONPatch
+		admissionResponse.Patch = patchJSON.Bytes()
+		admissionResponse.PatchType = &patchType
+		return admissionResponse, nil
+	}
+}
+
+func alreadyMutated(container corev1.Container) bool {
+	for _, envVar := range container.Env {
+		if envVar.Name == "LINKERD2_PROXY_TAP_SVC_NAME" {
+			return true
+		}
+	}
+	return false
+}
diff --git a/controller/webhook/util.go b/controller/webhook/util.go
new file mode 100644
index 000000000..c456072ef
--- /dev/null
+++ b/controller/webhook/util.go
@@ -0,0 +1,17 @@
+package webhook
+
+import (
+	labels "github.com/linkerd/linkerd2/pkg/k8s"
+	corev1 "k8s.io/api/core/v1"
+)
+
+// GetProxyContainerIndex gets the proxy container index of a pod; the index
+// is required in webhooks because of how patches are created.
+func GetProxyContainerIndex(containers []corev1.Container) int {
+	for i, c := range containers {
+		if c.Name == labels.ProxyContainerName {
+			return i
+		}
+	}
+	return -1
+}
diff --git a/jaeger/charts/jaeger/templates/rbac.yaml b/jaeger/charts/jaeger/templates/rbac.yaml
index c3004a17f..67a5fa396 100644
--- a/jaeger/charts/jaeger/templates/rbac.yaml
+++ b/jaeger/charts/jaeger/templates/rbac.yaml
@@ -33,7 +33,7 @@ metadata:
 rules:
 - apiGroups: [""]
   resources: ["namespaces"]
-  verbs: ["get", "list"]
+  verbs: ["get", "list", "watch"]
 ---
 kind: ClusterRoleBinding
 apiVersion: rbac.authorization.k8s.io/v1
diff --git a/jaeger/injector/mutator/webhook.go b/jaeger/injector/mutator/webhook.go
index 0ea475df0..fbf27ecf9 100644
--- a/jaeger/injector/mutator/webhook.go
+++ b/jaeger/injector/mutator/webhook.go
@@ -56,7 +56,7 @@ func Mutate(collectorSvcAddr, collectorSvcAccount string) webhook.Handler {
 		}
 
 		params := Params{
-			ProxyIndex:          getProxyContainerIndex(pod.Spec.Containers),
+			ProxyIndex:          webhook.GetProxyContainerIndex(pod.Spec.Containers),
 			CollectorSvcAddr:    collectorSvcAddr,
 			CollectorSvcAccount: collectorSvcAccount,
 		}
@@ -88,15 +88,6 @@ func Mutate(collectorSvcAddr, collectorSvcAccount string) webhook.Handler {
 	}
 }
 
-func getProxyContainerIndex(containers []corev1.Container) int {
-	for i, c := range containers {
-		if c.Name == labels.ProxyContainerName {
-			return i
-		}
-	}
-	return -1
-}
-
 func alreadyMutated(pod *corev1.Pod, proxyIndex int) bool {
 	for _, v := range pod.Spec.Volumes {
 		if v.DownwardAPI != nil && v.Name == "podinfo" {
diff --git a/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml b/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
index c2efffa95..eb3fa6776 100644
--- a/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
+++ b/multicluster/charts/linkerd2-multicluster/templates/link-crd.yaml
@@ -20,3 +20,65 @@ spec:
     plural: links
     singular: link
     kind: Link
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          properties:
+            clusterCredentialsSecret:
+              description: Kubernetes secret of target cluster
+              type: string
+            gatewayAddress:
+              description: Gateway address of target cluster
+              type: string
+            gatewayIdentity:
+              description: Gateway Identity FQDN
+              type: string
+            gatewayPort:
+              description: Gateway Port
+              type: string
+            probeSpec:
+              description: Spec for gateway health probe
+              type: object
+              properties:
+                path:
+                  description: Path of remote gateway health endpoint
+                  type: string
+                period:
+                  description: Interval in between probe requests
+                  type: string
+                port:
+                  description: Port of remote gateway health endpoint
+                  type: string
+            selector:
+              description: Kubernetes Label Selector
+              type: object
+              properties:
+                matchExpressions:
+                  description: List of selector requirements
+                  type: array
+                  items:
+                    description: A selector item requires a key and an operator
+                    type: object
+                    required:
+                    - key
+                    - operator
+                    properties:
+                      key:
+                        description: Label key that selector should apply to
+                        type: string
+                      operator:
+                        description: Evaluation of a label in relation to set
+                        type: string
+            targetClusterName:
+              description: Name of target cluster to link to
+              type: string
+            targetDomainName:
+              description: Domain name of target cluster to link to
+              type: string
+            targetClusterLinkerdNamespace:
+              description: Name of namespace Linkerd control plane is installed in on target cluster
+              type: string
+
diff --git a/multicluster/cmd/install.go b/multicluster/cmd/install.go
index 636e122ae..33f8c73be 100644
--- a/multicluster/cmd/install.go
+++ b/multicluster/cmd/install.go
@@ -1,19 +1,25 @@
 package cmd
 
 import (
+	"bytes"
 	"context"
 	"errors"
 	"fmt"
 	"os"
+	"path"
 
 	"github.com/linkerd/linkerd2/multicluster/static"
 	multicluster "github.com/linkerd/linkerd2/multicluster/values"
 	"github.com/linkerd/linkerd2/pkg/charts"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/version"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"helm.sh/helm/v3/pkg/chart/loader"
 	chartloader "helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/chartutil"
+	valuespkg "helm.sh/helm/v3/pkg/cli/values"
+	"helm.sh/helm/v3/pkg/engine"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	"sigs.k8s.io/yaml"
 )
@@ -34,6 +40,8 @@ type (
 
 func newMulticlusterInstallCommand() *cobra.Command {
 	options, err := newMulticlusterInstallOptionsWithDefault()
+	var valuesOptions valuespkg.Options
+
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -43,6 +51,12 @@ func newMulticlusterInstallCommand() *cobra.Command {
 		Use:   "install",
 		Short: "Output Kubernetes configs to install the Linkerd multicluster add-on",
 		Args:  cobra.NoArgs,
+		Example: `  # Default install.
+  linkerd multicluster install | kubectl apply -f -
+  
+The installation can be configured by using the --set, --values, --set-string and --set-file flags.
+A full list of configurable values can be found at https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd2-multicluster/README.md
+  `,
 		RunE: func(cmd *cobra.Command, args []string) error {
 
 			values, err := buildMulticlusterInstallValues(cmd.Context(), options)
@@ -65,18 +79,48 @@ func newMulticlusterInstallCommand() *cobra.Command {
 				{Name: "templates/link-crd.yaml"},
 			}
 
-			chart := &charts.Chart{
-				Name:      helmMulticlusterDefaultChartName,
-				Dir:       helmMulticlusterDefaultChartName,
-				Namespace: controlPlaneNamespace,
-				RawValues: rawValues,
-				Files:     files,
-				Fs:        static.Templates,
+			// Load all multicluster install chart files into buffer
+			if err := charts.FilesReader(static.Templates, helmMulticlusterDefaultChartName+"/", files); err != nil {
+				return err
+			}
+
+			// Create a Chart obj from the files
+			chart, err := loader.LoadFiles(files)
+			if err != nil {
+				return err
 			}
-			buf, err := chart.RenderNoPartials()
+
+			// Store final Values generated from values.yaml and CLI flags
+			err = yaml.Unmarshal(rawValues, &chart.Values)
+			if err != nil {
+				return err
+			}
+
+			// Create values override
+			valuesOverrides, err := valuesOptions.MergeValues(nil)
 			if err != nil {
 				return err
 			}
+
+			vals, err := chartutil.CoalesceValues(chart, valuesOverrides)
+			if err != nil {
+				return err
+			}
+
+			// Attach the final values into the `Values` field for rendering to work
+			renderedTemplates, err := engine.Render(chart, map[string]interface{}{"Values": vals})
+			if err != nil {
+				return err
+			}
+
+			// Merge templates and inject
+			var buf bytes.Buffer
+			for _, tmpl := range chart.Templates {
+				t := path.Join(chart.Metadata.Name, tmpl.Name)
+				if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
+					return err
+				}
+			}
 			stdout.Write(buf.Bytes())
 			stdout.Write([]byte("---\n"))
 
@@ -84,6 +128,7 @@ func newMulticlusterInstallCommand() *cobra.Command {
 		},
 	}
 
+	flags.AddValueOptionsFlags(cmd.Flags(), &valuesOptions)
 	cmd.Flags().StringVar(&options.namespace, "namespace", options.namespace, "The namespace in which the multicluster add-on is to be installed. Must not be the control plane namespace. ")
 	cmd.Flags().BoolVar(&options.gateway, "gateway", options.gateway, "If the gateway component should be installed")
 	cmd.Flags().Uint32Var(&options.gatewayPort, "gateway-port", options.gatewayPort, "The port on the gateway used for all incoming traffic")
diff --git a/multicluster/cmd/link.go b/multicluster/cmd/link.go
index 2278229cf..4f7e96182 100644
--- a/multicluster/cmd/link.go
+++ b/multicluster/cmd/link.go
@@ -1,14 +1,17 @@
 package cmd
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"os"
+	"path"
 	"strings"
 
 	"github.com/linkerd/linkerd2/multicluster/static"
 	multicluster "github.com/linkerd/linkerd2/multicluster/values"
 	"github.com/linkerd/linkerd2/pkg/charts"
+	"github.com/linkerd/linkerd2/pkg/flags"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	mc "github.com/linkerd/linkerd2/pkg/multicluster"
 	"github.com/linkerd/linkerd2/pkg/version"
@@ -16,6 +19,8 @@ import (
 	"github.com/spf13/cobra"
 	chartloader "helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/chartutil"
+	valuespkg "helm.sh/helm/v3/pkg/cli/values"
+	"helm.sh/helm/v3/pkg/engine"
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -43,6 +48,8 @@ type (
 
 func newLinkCommand() *cobra.Command {
 	opts, err := newLinkOptionsWithDefault()
+	var valuesOptions valuespkg.Options
+
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "%s", err)
 		os.Exit(1)
@@ -52,6 +59,12 @@ func newLinkCommand() *cobra.Command {
 		Use:   "link",
 		Short: "Outputs resources that allow another cluster to mirror services from this one",
 		Args:  cobra.NoArgs,
+		Example: `  # To link the west cluster to east
+  linkerd --context=east multicluster link --cluster-name east | kubectl --context=west apply -f -
+
+The command can be configured by using the --set, --values, --set-string and --set-file flags.
+A full list of configurable values can be found at https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd2-multicluster-link/README.md
+  `,
 		RunE: func(cmd *cobra.Command, args []string) error {
 
 			if opts.clusterName == "" {
@@ -242,19 +255,49 @@ func newLinkCommand() *cobra.Command {
 				{Name: "templates/gateway-mirror.yaml"},
 			}
 
-			chart := &charts.Chart{
-				Name:      helmMulticlusterLinkDefaultChartName,
-				Dir:       helmMulticlusterLinkDefaultChartName,
-				Namespace: controlPlaneNamespace,
-				RawValues: rawValues,
-				Files:     files,
-				Fs:        static.Templates,
+			// Load all multicluster link chart files into buffer
+			if err := charts.FilesReader(static.Templates, helmMulticlusterLinkDefaultChartName+"/", files); err != nil {
+				return err
+			}
+
+			// Create a Chart obj from the files
+			chart, err := chartloader.LoadFiles(files)
+			if err != nil {
+				return err
+			}
+
+			// Store final Values generated from values.yaml and CLI flags
+			err = yaml.Unmarshal(rawValues, &chart.Values)
+			if err != nil {
+				return err
 			}
-			serviceMirrorOut, err := chart.RenderNoPartials()
+
+			// Create values override
+			valuesOverrides, err := valuesOptions.MergeValues(nil)
 			if err != nil {
 				return err
 			}
 
+			vals, err := chartutil.CoalesceValues(chart, valuesOverrides)
+			if err != nil {
+				return err
+			}
+
+			// Attach the final values into the `Values` field for rendering to work
+			renderedTemplates, err := engine.Render(chart, map[string]interface{}{"Values": vals})
+			if err != nil {
+				return err
+			}
+
+			// Merge templates and inject
+			var serviceMirrorOut bytes.Buffer
+			for _, tmpl := range chart.Templates {
+				t := path.Join(chart.Metadata.Name, tmpl.Name)
+				if _, err := serviceMirrorOut.WriteString(renderedTemplates[t]); err != nil {
+					return err
+				}
+			}
+
 			stdout.Write(credsOut)
 			stdout.Write([]byte("---\n"))
 			stdout.Write(linkOut)
@@ -266,6 +309,7 @@ func newLinkCommand() *cobra.Command {
 		},
 	}
 
+	flags.AddValueOptionsFlags(cmd.Flags(), &valuesOptions)
 	cmd.Flags().StringVar(&opts.namespace, "namespace", defaultMulticlusterNamespace, "The namespace for the service account")
 	cmd.Flags().StringVar(&opts.clusterName, "cluster-name", "", "Cluster name")
 	cmd.Flags().StringVar(&opts.apiServerAddress, "api-server-address", "", "The api server address of the target cluster")
diff --git a/pkg/k8s/labels.go b/pkg/k8s/labels.go
index 160242dc1..b44e55e2a 100644
--- a/pkg/k8s/labels.go
+++ b/pkg/k8s/labels.go
@@ -478,10 +478,17 @@ func IsMeshed(pod *corev1.Pod, controllerNS string) bool {
 	return pod.Labels[ControllerNSLabel] == controllerNS
 }
 
-// IsTapDisabled returns true if the pod has an annotation for explicitly
-// disabling tap
-func IsTapDisabled(pod *corev1.Pod) bool {
-	if valStr := pod.Annotations[ProxyDisableTapAnnotation]; valStr != "" {
+// IsTapDisabled returns true if a namespace or pod has an annotation for
+// explicitly disabling tap
+func IsTapDisabled(obj interface{}) bool {
+	var valStr string
+	switch resource := obj.(type) {
+	case *corev1.Pod:
+		valStr = resource.GetAnnotations()[ProxyDisableTapAnnotation]
+	case *corev1.Namespace:
+		valStr = resource.GetAnnotations()[ProxyDisableTapAnnotation]
+	}
+	if valStr != "" {
 		valBool, err := strconv.ParseBool(valStr)
 		if err == nil && valBool {
 			return true
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index 4231f44d2..922701e4d 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -71,7 +71,7 @@ Kubernetes: `>=1.13.0-0`
 
 | Key | Type | Default | Description |
 |-----|------|---------|-------------|
-| clusterDomain | string | `"cluster.local"` | Kubernetes DNS Domain name to use   |
+| clusterDomain | string | `"cluster.local"` | Kubernetes DNS Domain name to use |
 | createdByAnnotation | string | `"linkerd.io/created-by"` |  |
 | dashboard.UID | int | `2103` |  |
 | dashboard.enforcedHostRegexp | string | `""` | Host header validation regex for the dashboard. See the [Linkerd documentation](https://linkerd.io/2/tasks/exposing-dashboard) for more information |
@@ -99,15 +99,15 @@ Kubernetes: `>=1.13.0-0`
 | grafana.resources.cpu.request | string | `nil` | Amount of CPU units that the grafana container requests |
 | grafana.resources.memory.limit | string | `nil` | Maximum amount of memory that grafana container can use |
 | grafana.resources.memory.request | string | `nil` | Amount of memory that the grafana container requests |
-| identityTrustDomain | string | `"cluster.local"` | Trust domain used for identity  |
+| identityTrustDomain | string | `"cluster.local"` | Trust domain used for identity |
 | imagePullSecrets | list | `[]` | For Private docker registries, authentication is needed.  Registry secrets are applied to the respective service accounts |
 | installNamespace | bool | `true` | Set to false when installing in a custom namespace. |
 | linkerdNamespace | string | `"linkerd"` | Namespace of the Linkerd core control-plane install |
 | linkerdVersion | string | `"linkerdVersionValue"` | control plane version. See Proxy section for proxy version |
 | namespace | string | `"linkerd-viz"` | Namespace in which the Linkerd Viz extension has to be installed |
 | nodeSelector | object | `{"beta.kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
-| prometheus.alertManagers | string | `nil` | Alertmanager instances the Prometheus server sends alerts to configured via the static_configs parameter. |
 | prometheus.alertRelabelConfigs | string | `nil` | Alert relabeling is applied to alerts before they are sent to the Alertmanager. |
+| prometheus.alertmanagers | string | `nil` | Alertmanager instances the Prometheus server sends alerts to configured via the static_configs parameter. |
 | prometheus.args | object | `{"config.file":"/etc/prometheus/prometheus.yml","log.level":"info","storage.tsdb.path":"/data","storage.tsdb.retention.time":"6h"}` | Command line options for Prometheus binary |
 | prometheus.enabled | bool | `true` | toggle field to enable or disable prometheus |
 | prometheus.globalConfig | object | `{"evaluation_interval":"10s","scrape_interval":"10s","scrape_timeout":"10s"}` | The global configuration specifies parameters that are valid in all other configuration contexts. |
@@ -132,7 +132,7 @@ Kubernetes: `>=1.13.0-0`
 | tap.image.name | string | `"controller"` | Docker image name for the tap instance |
 | tap.image.registry | string | `"ghcr.io/linkerd"` | Docker registry for the tap instance |
 | tap.image.tag | string | `"linkerdVersionValue"` | Docker image tag for the tap instance |
-| tap.keyPEM | string | `""` | Certificate key for Tap component. If not provided then Helm will generate one.  |
+| tap.keyPEM | string | `""` | Certificate key for Tap component. If not provided then Helm will generate one. |
 | tap.logLevel | string | `"info"` | log level of the tap component |
 | tap.proxy | string | `nil` |  |
 | tap.replicas | int | `1` |  |
@@ -140,6 +140,23 @@ Kubernetes: `>=1.13.0-0`
 | tap.resources.cpu.request | string | `nil` | Amount of CPU units that the tap container requests |
 | tap.resources.memory.limit | string | `nil` | Maximum amount of memory that tap container can use |
 | tap.resources.memory.request | string | `nil` | Amount of memory that the tap container requests |
+| tapInjector.UID | int | `2103` |  |
+| tapInjector.caBundle | string | `""` | Bundle of CA certificates for the tapInjector. If not provided then Helm will use the certificate generated  for `tapInjector.crtPEM`. If `tapInjector.externalSecret` is set to true, this value must be set, as no certificate will be generated. |
+| tapInjector.crtPEM | string | `""` | Certificate for the tapInjector. If not provided then Helm will generate one. |
+| tapInjector.externalSecret | bool | `false` | Do not create a secret resource for the tapInjector webhook. If this is set to `true`, the value `tapInjector.caBundle` must be set (see below) |
+| tapInjector.failurePolicy | string | `"Ignore"` |  |
+| tapInjector.image.name | string | `"controller"` | Docker image name for the tapInjector instance |
+| tapInjector.image.registry | string | `"ghcr.io/linkerd"` | Docker registry for the tapInjector instance |
+| tapInjector.image.tag | string | `"linkerdVersionValue"` | Docker image tag for the tapInjector instance |
+| tapInjector.keyPEM | string | `""` | Certificate key for the tapInjector. If not provided then Helm will generate one. |
+| tapInjector.namespaceSelector | string | `nil` |  |
+| tapInjector.objectSelector | string | `nil` |  |
+| tapInjector.proxy | string | `nil` |  |
+| tapInjector.replicas | int | `1` | Number of replicas of tapInjector |
+| tapInjector.resources.cpu.limit | string | `nil` | Maximum amount of CPU units that the tapInjector container can use |
+| tapInjector.resources.cpu.request | string | `nil` | Amount of CPU units that the tapInjector container requests |
+| tapInjector.resources.memory.limit | string | `nil` | Maximum amount of memory that tapInjector container can use |
+| tapInjector.resources.memory.request | string | `nil` | Amount of memory that the tapInjector container requests |
 
 ----------------------------------------------
 Autogenerated from chart metadata using [helm-docs v1.4.0](https://github.com/norwoodj/helm-docs/releases/v1.4.0)
diff --git a/viz/charts/linkerd-viz/templates/grafana.yaml b/viz/charts/linkerd-viz/templates/grafana.yaml
index b31d6df2c..a9762360e 100644
--- a/viz/charts/linkerd-viz/templates/grafana.yaml
+++ b/viz/charts/linkerd-viz/templates/grafana.yaml
@@ -130,7 +130,7 @@ spec:
         # see https://github.com/grafana/grafana/issues/20096
         - name: GODEBUG
           value: netdns=go
-        image: {{.Values.grafana.image.registry}}/{{.Values.grafana.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.grafana.image.tag}}
+        image: {{.Values.grafana.image.registry}}/{{.Values.grafana.image.name}}:{{.Values.grafana.image.tag}}
         imagePullPolicy: {{.Values.imagePullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/templates/prometheus.yaml b/viz/charts/linkerd-viz/templates/prometheus.yaml
index 17a60bbf6..791705d28 100644
--- a/viz/charts/linkerd-viz/templates/prometheus.yaml
+++ b/viz/charts/linkerd-viz/templates/prometheus.yaml
@@ -154,15 +154,15 @@ data:
     {{- toYaml .Values.prometheus.scrapeConfigs | trim | nindent 4 }}
     {{- end }}
 
-    {{-  if (or .Values.prometheus.alertManagers .Values.prometheus.alertRelabelConfigs) }}
+    {{-  if (or .Values.prometheus.alertmanagers .Values.prometheus.alertRelabelConfigs) }}
     alerting:
       alert_relabel_configs:
         {{- if .Values.prometheus.alertRelabelConfigs }}
         {{- toYaml .Values.prometheus.alertRelabelConfigs | trim | nindent 6 }}
         {{- end }}
       alertmanagers:
-        {{- if .Values.prometheus.alertManagers }}
-        {{- toYaml .Values.prometheus.alertManagers | trim | nindent 6 }}
+        {{- if .Values.prometheus.alertmanagers }}
+        {{- toYaml .Values.prometheus.alertmanagers | trim | nindent 6 }}
         {{- end }}
     {{- end }}
 
diff --git a/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
new file mode 100644
index 000000000..b4e6887f2
--- /dev/null
+++ b/viz/charts/linkerd-viz/templates/tap-injector-rbac.yaml
@@ -0,0 +1,86 @@
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+{{- include "partials.image-pull-secrets" .Values.imagePullSecrets }}
+---
+{{- $host := printf "tap-injector.%s.svc" .Values.namespace }}
+{{- $ca := genSelfSignedCert $host (list) (list $host) 365 }}
+{{- if (not .Values.tapInjector.externalSecret) }}
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: {{ .Values.namespace }}
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+type: kubernetes.io/tls
+data:
+  tls.crt: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.crtPEM)) (empty .Values.tapInjector.crtPEM) }}
+  tls.key: {{ ternary (b64enc (trim $ca.Key)) (b64enc (trim .Values.tapInjector.keyPEM)) (empty .Values.tapInjector.keyPEM) }}
+---
+{{- end }}
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  {{- if .Values.tapInjector.namespaceSelector }}
+  namespaceSelector:
+{{ toYaml .Values.tapInjector.namespaceSelector | trim | indent 4 -}}
+  {{- end }}
+  {{- if .Values.tapInjector.objectSelector }}
+  objectSelector:
+{{ toYaml .Values.tapInjector.objectSelector | trim | indent 4 -}}
+  {{- end }}
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: {{ .Values.namespace }}
+      path: "/"
+{{- if and (.Values.tapInjector.externalSecret) (empty .Values.tapInjector.caBundle) }}
+  {{- fail "If tapInjector.externalSecret is true then you need to provide tapInjector.caBundle" }}
+{{- end }}
+    caBundle: {{ ternary (b64enc (trim $ca.Cert)) (b64enc (trim .Values.tapInjector.caBundle)) (empty .Values.tapInjector.caBundle) }}
+  failurePolicy: {{.Values.tapInjector.failurePolicy}}
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
diff --git a/viz/charts/linkerd-viz/templates/tap-injector.yaml b/viz/charts/linkerd-viz/templates/tap-injector.yaml
new file mode 100644
index 000000000..bca64e376
--- /dev/null
+++ b/viz/charts/linkerd-viz/templates/tap-injector.yaml
@@ -0,0 +1,106 @@
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+    component: tap-injector
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+  labels:
+    {{.Values.extensionAnnotation}}: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: {{.Values.namespace}}
+spec:
+  replicas: {{.Values.tapInjector.replicas}}
+  selector:
+    matchLabels:
+      component: tap-injector
+  {{- if .Values.enablePodAntiAffinity }}
+  strategy:
+    rollingUpdate:
+      maxUnavailable: 1
+  {{- end }}
+  template:
+    metadata:
+      annotations:
+        {{- if empty .Values.cliVersion }}
+        checksum/config: {{ include (print $.Template.BasePath "/tap-injector-rbac.yaml") . | sha256sum }}
+        {{- end }}
+        {{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
+        {{- with .Values.tapInjector.proxy }}
+        {{- include "partials.proxy.config.annotations" .resources | nindent 8 }}
+        {{- end }}
+        {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
+      labels:
+        {{.Values.extensionAnnotation}}: linkerd-viz
+        component: tap-injector
+        {{- with .Values.podLabels }}{{ toYaml . | trim | nindent 8 }}{{- end }}
+    spec:
+      {{- if .Values.tolerations -}}
+      {{- include "linkerd.tolerations" . | nindent 6 }}
+      {{- end -}}
+      {{- include "linkerd.node-selector" . | nindent 6 }}
+      {{- if .Values.enablePodAntiAffinity -}}
+      {{- $local := dict "component" "tap-injector" "label" "component" -}}
+      {{- include "linkerd.pod-affinity" $local | nindent 6 -}}
+      {{- end }}
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.{{.Values.namespace}}.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: {{.Values.tapInjector.image.registry}}/{{.Values.tapInjector.image.name}}:{{.Values.tapInjector.image.tag}}
+        imagePullPolicy: {{.Values.tapInjector.image.pullPolicy}}
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        {{- if .Values.tapInjector.resources -}}
+        {{- include "partials.resources" .Values.tapInjector.resources | nindent 8 }}
+        {{- end }}
+        securityContext:
+          runAsUser: {{.Values.tapInjector.UID}}
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
diff --git a/viz/charts/linkerd-viz/templates/tap.yaml b/viz/charts/linkerd-viz/templates/tap.yaml
index 660bcf055..773cc12eb 100644
--- a/viz/charts/linkerd-viz/templates/tap.yaml
+++ b/viz/charts/linkerd-viz/templates/tap.yaml
@@ -84,7 +84,7 @@ spec:
         - -controller-namespace={{.Values.linkerdNamespace}}
         - -log-level={{.Values.tap.logLevel}}
         - -identity-trust-domain={{.Values.identityTrustDomain}}
-        image: {{.Values.tap.image.registry}}/{{.Values.tap.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.tap.image.tag}}
+        image: {{.Values.tap.image.registry}}/{{.Values.tap.image.name}}:{{.Values.tap.image.tag}}
         imagePullPolicy: {{.Values.tap.image.pullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/templates/web.yaml b/viz/charts/linkerd-viz/templates/web.yaml
index b16818a64..2253decc6 100644
--- a/viz/charts/linkerd-viz/templates/web.yaml
+++ b/viz/charts/linkerd-viz/templates/web.yaml
@@ -87,7 +87,7 @@ spec:
         {{- $hostAbbrev := replace "." "\\." (printf "linkerd-web.%s.svc" .Values.namespace) }}
         - -enforced-host=^(localhost|127\.0\.0\.1|{{ $hostFull }}|{{ $hostAbbrev }}|\[::1\])(:\d+)?$
         {{- end}}
-        image: {{.Values.dashboard.image.registry}}/{{.Values.dashboard.image.name}}:{{ default (default .Values.linkerdVersion .Values.controllerImageVersion) .Values.dashboard.image.tag}}
+        image: {{.Values.dashboard.image.registry}}/{{.Values.dashboard.image.name}}:{{.Values.dashboard.image.tag}}
         imagePullPolicy: {{.Values.dashboard.image.pullPolicy}}
         livenessProbe:
           httpGet:
diff --git a/viz/charts/linkerd-viz/values.yaml b/viz/charts/linkerd-viz/values.yaml
index b9d57d305..1b504bdbd 100644
--- a/viz/charts/linkerd-viz/values.yaml
+++ b/viz/charts/linkerd-viz/values.yaml
@@ -10,10 +10,10 @@ linkerdVersion: &linkerd_version linkerdVersionValue
 # -- Default Docker Registry
 defaultRegistry: &default_registry ghcr.io/linkerd
 
-# -- Kubernetes DNS Domain name to use  
+# -- Kubernetes DNS Domain name to use
 clusterDomain: &cluster_domain cluster.local
 
-# -- Trust domain used for identity 
+# -- Trust domain used for identity
 identityTrustDomain: *cluster_domain
 
 # -- Namespace of the Linkerd core control-plane install
@@ -46,7 +46,7 @@ nodeSelector:
 imagePullSecrets: []
 # - name: my-private-docker-registry-login-secret
 
-# -|- Tolerations section, See the 
+# -|- Tolerations section, See the
 # [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)
 # for more information
 # tolerations:
@@ -78,7 +78,7 @@ tap:
   crtPEM: |
 
   # -- Certificate key for Tap component. If not provided then Helm will
-  # generate one. 
+  # generate one.
   keyPEM: |
 
   # -- Bundle of CA certificates for Tap component. If not provided then Helm
@@ -106,6 +106,60 @@ tap:
 
   UID: *uid
 
+# tapInjector configuration
+tapInjector:
+  # -- Number of replicas of tapInjector
+  replicas: 1
+  image:
+    # -- Docker registry for the tapInjector instance
+    registry: *default_registry
+    # -- Docker image name for the tapInjector instance
+    name: controller
+    # -- Docker image tag for the tapInjector instance
+    tag: *linkerd_version
+  namespaceSelector:
+    # matchExpressions:
+    # - key: runlevel
+    #   operator: NotIn
+    #   values: ["0","1"]
+  objectSelector:
+    # matchLabels:
+    #   foo: bar
+  UID: *uid
+  failurePolicy: Ignore
+  resources:
+    cpu:
+      # -- Maximum amount of CPU units that the tapInjector container can use
+      limit:
+      # -- Amount of CPU units that the tapInjector container requests
+      request:
+    memory:
+      # -- Maximum amount of memory that tapInjector container can use
+      limit:
+      # -- Amount of memory that the tapInjector container requests
+      request:
+  proxy:
+    # -- If set, overrides default proxy resources for the proxy injected
+    # into the tapInjector component
+    # resources:
+
+  # -- Do not create a secret resource for the tapInjector webhook. If this is
+  # set to `true`, the value `tapInjector.caBundle` must be set (see below)
+  externalSecret: false
+  # -- Certificate for the tapInjector. If not provided then Helm will
+  # generate one.
+  crtPEM: |
+
+  # -- Certificate key for the tapInjector. If not provided then Helm will
+  # generate one.
+  keyPEM: |
+
+  # -- Bundle of CA certificates for the tapInjector. If not provided then
+  # Helm will use the certificate generated  for `tapInjector.crtPEM`. If
+  # `tapInjector.externalSecret` is set to true, this value must be set, as no
+  # certificate will be generated.
+  caBundle: |
+
 # web dashboard configuration
 dashboard:
   # -- Number of replicas of dashboard
@@ -210,7 +264,7 @@ prometheus:
 
   # -- Alertmanager instances the Prometheus server sends alerts to configured via
   # the static_configs parameter.
-  alertManagers:
+  alertmanagers:
   # Ex:
   # - scheme: http
   #   static_configs:
diff --git a/viz/cmd/install.go b/viz/cmd/install.go
index d3c0f6f82..d508111ac 100644
--- a/viz/cmd/install.go
+++ b/viz/cmd/install.go
@@ -32,6 +32,8 @@ var (
 		"templates/grafana.yaml",
 		"templates/prometheus.yaml",
 		"templates/tap.yaml",
+		"templates/tap-injector-rbac.yaml",
+		"templates/tap-injector.yaml",
 		"templates/web.yaml",
 	}
 )


Test patch:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index cd8112e27..7a5102828 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -40,8 +40,8 @@ jobs:
         restore-keys: |
           ${{ runner.os }}-buildx-${{ matrix.target }}-
     - name: Build docker images
-      env:
-        DOCKER_TRACE: 1
+      #env:
+      #  DOCKER_TRACE: 1
       run: |
         docker buildx create --driver docker-container --use
         bin/docker-build-${{ matrix.target }}
diff --git a/TEST.md b/TEST.md
index f93e685df..21b6fa60d 100644
--- a/TEST.md
+++ b/TEST.md
@@ -167,7 +167,7 @@ You can also test a locally-built version of the `linkerd` CLI.
 First build all of the Linkerd images by running:
 
 ```bash
-DOCKER_TRACE=1 bin/docker-build
+bin/docker-build
 ```
 
 That command also copies the corresponding `linkerd` binaries into the
diff --git a/bin/docker-test-proxy b/bin/docker-test-proxy
index b187b8ec7..baf14de95 100755
--- a/bin/docker-test-proxy
+++ b/bin/docker-test-proxy
@@ -2,21 +2,13 @@
 
 set -eu
 
-# When set, causes docker's build output to be emitted to stderr.
-export DOCKER_TRACE=${DOCKER_TRACE:-}
 export RUST_LOG=${RUST_LOG:-}
 
 bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
 rootdir=$( cd "$bindir"/.. && pwd )
 
-if [ -n "$DOCKER_TRACE" ]; then
-	output=/dev/stderr
-else
-	output=/dev/null
-fi
-
 docker build -f "$rootdir/proxy/Dockerfile" . \
     --target build \
     -t proxy-build \
-    --build-arg=PROXY_UNOPTIMIZED=1 > $output
+    --build-arg=PROXY_UNOPTIMIZED=1
 docker run --rm -it proxy-build env RUST_LOG="$RUST_LOG" cargo test "$@"
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
index 2a10b2888..69052416c 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
index d041d5007..2d080c10b 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_nginx_redis.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -265,8 +263,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
index 4a840b0c0..80b6b500d 100644
--- a/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
+++ b/cli/cmd/testdata/inject-filepath/expected/injected_redis.yaml
@@ -96,8 +96,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_contour.golden.yml b/cli/cmd/testdata/inject_contour.golden.yml
index d6d13c698..8366af6b4 100644
--- a/cli/cmd/testdata/inject_contour.golden.yml
+++ b/cli/cmd/testdata/inject_contour.golden.yml
@@ -125,8 +125,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
index f90fc8d21..58fdb370b 100644
--- a/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_already_injected.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -287,8 +285,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -467,8 +463,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -647,8 +641,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
index f8384b4bd..2028b6f11 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
index 0aa30b223..9897a9fba 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_capabilities.golden.yml
@@ -115,8 +115,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
index d6331002c..8ee40f663 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_config_overrides.golden.yml
@@ -118,8 +118,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_DISABLED
-          value: "true"
         image: ghcr.io/linkerd/proxy:override
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
index bec78d5f8..1b7e177c4 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_controller_name.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -287,8 +285,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
index 683d57dab..85489fb2a 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_debug.golden.yml
@@ -112,8 +112,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
index ec7f28235..b6d3882eb 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_empty_resources.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
index 21cd8c3f4..ca995d71f 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_hostNetwork_false.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
index 04557fa3c..d9be4d73b 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_no_init_container.golden.yml
@@ -107,8 +107,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
index 526e5bba7..fdb07f2a6 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_overridden.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
index e3a7f14ca..3d55e28fd 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_proxyignores.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
index 931983c6f..04f4a75ff 100644
--- a/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_deployment_udp.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_list.golden.yml b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
index 4706f8f12..59e5a31f2 100644
--- a/cli/cmd/testdata/inject_emojivoto_list.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list.golden.yml
@@ -109,8 +109,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
@@ -283,8 +281,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
index 1d8a01fd8..d01c0544c 100644
--- a/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_list_empty_resources.golden.yml
@@ -109,8 +109,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
@@ -283,8 +281,6 @@ items:
             value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
             value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-          - name: LINKERD2_PROXY_TAP_SVC_NAME
-            value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
           image: ghcr.io/linkerd/proxy:test-inject-proxy-version
           imagePullPolicy: IfNotPresent
           livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
index 0b977c8a2..303d52e92 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod.golden.yml
@@ -92,8 +92,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
index 0c321a1fc..927449568 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_proxyignores.golden.yml
@@ -94,8 +94,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
index 2bb69715a..4489e8082 100644
--- a/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_pod_with_requests.golden.yml
@@ -96,8 +96,6 @@ spec:
       value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
       value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-    - name: LINKERD2_PROXY_TAP_SVC_NAME
-      value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
     image: ghcr.io/linkerd/proxy:test-inject-proxy-version
     imagePullPolicy: IfNotPresent
     livenessProbe:
diff --git a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
index 5c2bc0a1d..bfccc5ee6 100644
--- a/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
+++ b/cli/cmd/testdata/inject_emojivoto_statefulset.golden.yml
@@ -108,8 +108,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
index f5a166cda..8a5efc45d 100644
--- a/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
+++ b/cli/cmd/testdata/inject_gettest_deployment.good.golden.yml
@@ -109,8 +109,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:testinjectversion
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -291,8 +289,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:testinjectversion
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
index d0c28e6fb..f75cadb9f 100644
--- a/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
+++ b/cli/cmd/testdata/inject_tap_deployment_debug.golden.yml
@@ -160,8 +160,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-inject-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_config.golden b/cli/cmd/testdata/install_config.golden
index a39a22245..6980d77a3 100644
--- a/cli/cmd/testdata/install_config.golden
+++ b/cli/cmd/testdata/install_config.golden
@@ -352,7 +352,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -365,14 +365,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index 21d667f28..bd2d2ce18 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -915,8 +1088,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1150,8 +1321,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1403,8 +1572,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1663,8 +1830,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1918,8 +2083,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index f3fd64ed7..4cd78e7ef 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: my.custom.registry/linkerd-io/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index a0d9c5edd..2af449cc0 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_default_override_dst_get_nets.golden b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
index 22538d6fa..fc5c9a56b 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_grafana_existing.golden b/cli/cmd/testdata/install_grafana_existing.golden
index d899f5829..d77243da1 100644
--- a/cli/cmd/testdata/install_grafana_existing.golden
+++ b/cli/cmd/testdata/install_grafana_existing.golden
@@ -240,7 +240,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +253,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index c499a1e07..0042e724e 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -995,8 +1168,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1279,8 +1450,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1581,8 +1750,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1881,8 +2048,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2202,8 +2367,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_ha_with_overrides_output.golden b/cli/cmd/testdata/install_ha_with_overrides_output.golden
index 4ef8955e3..cfb6bcb30 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -995,8 +1168,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1279,8 +1450,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1581,8 +1750,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1881,8 +2048,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2202,8 +2367,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_heartbeat_disabled_output.golden b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
index fbb3f106c..4881007b5 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -190,6 +190,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -199,7 +325,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -212,14 +338,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -870,8 +1043,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1104,8 +1275,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1356,8 +1525,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1570,8 +1737,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1825,8 +1990,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output.golden b/cli/cmd/testdata/install_helm_output.golden
index d68c8f6f7..af872d573 100644
--- a/cli/cmd/testdata/install_helm_output.golden
+++ b/cli/cmd/testdata/install_helm_output.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -907,8 +1080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1132,8 +1303,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1375,8 +1544,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1628,8 +1795,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1874,8 +2039,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha.golden b/cli/cmd/testdata/install_helm_output_ha.golden
index e85027d65..3a546b9cc 100644
--- a/cli/cmd/testdata/install_helm_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_output_ha.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -988,8 +1161,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1263,8 +1434,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1556,8 +1725,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1849,8 +2016,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2161,8 +2326,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha_labels.golden b/cli/cmd/testdata/install_helm_output_ha_labels.golden
index 1d60a9fc7..3db8330aa 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -996,8 +1169,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1275,8 +1446,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1572,8 +1741,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1873,8 +2040,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2189,8 +2354,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
index abbd40afb..a01ddc234 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -243,6 +243,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -254,7 +380,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -267,14 +393,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -988,8 +1161,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1263,8 +1434,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1556,8 +1725,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1849,8 +2016,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -2161,8 +2326,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:test-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_no_init_container.golden b/cli/cmd/testdata/install_no_init_container.golden
index bb963ea98..06d0203d4 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -911,8 +1084,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1107,8 +1278,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1321,8 +1490,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1543,8 +1710,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1760,8 +1925,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 461747494..76b539986 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     ControllerNamespaceLabel: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -910,8 +1083,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1146,8 +1317,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1662,8 +1829,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1919,8 +2084,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ProxyImageName:ProxyVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
diff --git a/cli/cmd/testdata/install_prometheus_overwrite.golden b/cli/cmd/testdata/install_prometheus_overwrite.golden
index 87657dccb..09f77b8aa 100644
--- a/cli/cmd/testdata/install_prometheus_overwrite.golden
+++ b/cli/cmd/testdata/install_prometheus_overwrite.golden
@@ -240,7 +240,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +253,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -697,7 +744,7 @@ data:
           values:
           - disabled
     prometheus:
-      alertManagers:
+      alertmanagers:
       - scheme: http
         static_configs:
         - targets:
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index e82ab3086..31a8bdf06 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -231,6 +231,132 @@ spec:
     served: true
     storage: true
   scope: Namespaced
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          description: Spec is the custom resource spec
+          required:
+          - routes
+          properties:
+            dstOverrides:
+              type: array
+              required:
+              - authority
+              - weight
+              items:
+                type: object
+                description: WeightedDst is a weighted alternate destination.
+                properties:
+                  authority:
+                    type: string
+                  weight:
+                    x-kubernetes-int-or-string: true
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+            opaquePorts:
+              type: array
+              items:
+                type: string
+            retryBudget:
+              type: object
+              required:
+              - minRetriesPerSecond
+              - retryRatio
+              - ttl
+              description: RetryBudget describes the maximum number of retries that should be issued to this service.
+              properties:
+                minRetriesPerSecond:
+                  format: int32
+                  type: integer
+                retryRatio: 
+                  type: number
+                  format: float
+                ttl:
+                  type: string
+            routes:
+              type: array
+              items:
+                type: object
+                description: RouteSpec specifies a Route resource.
+                required:
+                - condition
+                - name
+                properties:
+                  condition:
+                    type: object
+                    description: RequestMatch describes the conditions under which to match a Route.
+                    properties:
+                      pathRegex: 
+                        type: string
+                      method: 
+                        type: string
+                      all:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      any:
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                      not: 
+                        type: array
+                        items:
+                          type: object
+                          x-kubernetes-preserve-unknown-fields: true
+                  isRetryable:
+                    type: boolean
+                  name:
+                    type: string
+                  timeout:
+                    type: string
+                  responseClasses:
+                    type: array 
+                    items:
+                      type: object
+                      required:
+                      - condition
+                      description: ResponseClass describes how to classify a response (e.g. success or failures).
+                      properties:
+                        condition:
+                          type: object
+                          description: ResponseMatch describes the conditions under
+                            which to classify a response.
+                          properties:
+                            all:
+                              type: array
+                              items: 
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            any:
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            not: 
+                              type: array
+                              items:
+                                type: object
+                                x-kubernetes-preserve-unknown-fields: true
+                            status:
+                              type: object
+                              description: Range describes a range of integers (e.g. status codes).
+                              properties:
+                                max:
+                                  format: int32
+                                  type: integer
+                                min:
+                                  format: int32
+                                  type: integer
+                        isFailure:
+                          type: boolean
   names:
     plural: serviceprofiles
     singular: serviceprofile
@@ -240,7 +366,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -253,14 +379,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
@@ -914,8 +1087,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1148,8 +1319,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1400,8 +1569,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1660,8 +1827,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
@@ -1915,8 +2080,6 @@ spec:
           value: linkerd-identity.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         - name: LINKERD2_PROXY_DESTINATION_SVC_NAME
           value: linkerd-destination.$(_l5d_ns).serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
-        - name: LINKERD2_PROXY_TAP_SVC_NAME
-          value: linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
         image: ghcr.io/linkerd/proxy:install-proxy-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_tracing_overwrite.golden b/cli/cmd/testdata/install_tracing_overwrite.golden
index ecf426865..ec6549c52 100644
--- a/cli/cmd/testdata/install_tracing_overwrite.golden
+++ b/cli/cmd/testdata/install_tracing_overwrite.golden
@@ -352,7 +352,7 @@ spec:
 ---
 ###
 ### TrafficSplit CRD
-### Copied from https://github.com/deislabs/smi-sdk-go/blob/cea7e1e9372304bbb6c74a3f6ca788d9eaa9cc58/crds/split.yaml
+### Copied from github.com/servicemeshinterface/smi-sdk-go/blob/d4e76b1cd7a33ead5f38d1262dd838a31c80f4e5/crds/split.yaml
 ###
 ---
 apiVersion: apiextensions.k8s.io/v1beta1
@@ -365,14 +365,61 @@ metadata:
     linkerd.io/control-plane-ns: linkerd
 spec:
   group: split.smi-spec.io
-  version: v1alpha1
   scope: Namespaced
   names:
     kind: TrafficSplit
+    listKind: TrafficSplitList
     shortNames:
       - ts
     plural: trafficsplits
     singular: trafficsplit
+  version: v1alpha1
+  versions:
+    - name: v1alpha1
+      served: true
+      storage: true
+  preserveUnknownFields: false
+  validation:
+    openAPIV3Schema:
+      type: object
+      properties:
+        spec:
+          type: object
+          required:
+            - service
+            - backends
+          properties:
+            service:
+              description: The apex service of this split.
+              type: string
+            matches:
+              description: The HTTP route groups that this traffic split should match.
+              type: array
+              items:
+                type: object
+                required: ['kind', 'name']
+                properties:
+                  kind:
+                    description: Kind of the matching group.
+                    type: string
+                    enum:
+                      - HTTPRouteGroup
+                  name:
+                    description: Name of the matching group.
+                    type: string
+            backends:
+              description: The backend services of this split.
+              type: array
+              items:
+                type: object
+                required: ['service', 'weight']
+                properties:
+                  service:
+                    description: Name of the Kubernetes service.
+                    type: string
+                  weight:
+                    description: Traffic weight value of this backend.
+                    x-kubernetes-int-or-string: true
   additionalPrinterColumns:
   - name: Service
     type: string
diff --git a/cli/cmd/testdata/prom-config.yaml b/cli/cmd/testdata/prom-config.yaml
index 43f571c27..5f008a171 100644
--- a/cli/cmd/testdata/prom-config.yaml
+++ b/cli/cmd/testdata/prom-config.yaml
@@ -18,7 +18,7 @@ prometheus:
     relabel_configs:
     - action: labelmap
       regex: __meta_kubernetes_node_label_(.+)
-  alertManagers:
+  alertmanagers:
   - scheme: http
     static_configs:
     - targets:
diff --git a/pkg/charts/charts_test.go b/pkg/charts/charts_test.go
new file mode 100644
index 000000000..ad0213b9b
--- /dev/null
+++ b/pkg/charts/charts_test.go
@@ -0,0 +1,37 @@
+package charts
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestMergeMaps(t *testing.T) {
+	for _, tc := range []struct {
+		a, b, expected map[string]interface{}
+	}{
+		{
+			a:        map[string]interface{}{"aaa": "foo"},
+			b:        map[string]interface{}{"bbb": "bar"},
+			expected: map[string]interface{}{"aaa": "foo", "bbb": "bar"},
+		},
+		{
+			a:        map[string]interface{}{"aaa": "foo"},
+			b:        map[string]interface{}{"aaa": "bar", "bbb": "bar"},
+			expected: map[string]interface{}{"aaa": "bar", "bbb": "bar"},
+		},
+		{
+			a:        map[string]interface{}{"aaa": "foo", "bbb": map[string]interface{}{"aaa": "foo"}},
+			b:        map[string]interface{}{"aaa": "bar", "bbb": map[string]interface{}{"aaa": "bar"}},
+			expected: map[string]interface{}{"aaa": "bar", "bbb": map[string]interface{}{"aaa": "bar"}},
+		},
+		{
+			a:        map[string]interface{}{"aaa": "foo", "bbb": map[string]interface{}{"aaa": "foo"}},
+			b:        map[string]interface{}{"aaa": "foo", "bbb": map[string]interface{}{"aaa": "bar", "ccc": "foo"}},
+			expected: map[string]interface{}{"aaa": "foo", "bbb": map[string]interface{}{"aaa": "bar", "ccc": "foo"}},
+		},
+	} {
+		if !reflect.DeepEqual(MergeMaps(tc.a, tc.b), tc.expected) {
+			t.Errorf("expected: %v, got: %v", tc.expected, MergeMaps(tc.a, tc.b))
+		}
+	}
+}
diff --git a/test/integration/install_test.go b/test/integration/install_test.go
index 6a5b440f2..b432d0405 100644
--- a/test/integration/install_test.go
+++ b/test/integration/install_test.go
@@ -496,6 +496,7 @@ func TestInstallHelm(t *testing.T) {
 			"--set", "dashboard.image.tag=" + TestHelper.GetVersion(),
 			"--set", "grafana.image.tag=" + TestHelper.GetVersion(),
 			"--set", "tap.image.tag=" + TestHelper.GetVersion(),
+			"--set", "tapInjector.image.tag=" + TestHelper.GetVersion(),
 		}
 		// Install Viz Extension Chart
 		if stdout, stderr, err := TestHelper.HelmInstallPlain(vizChart, "l5d-viz", vizArgs...); err != nil {
@@ -605,6 +606,7 @@ func TestUpgradeHelm(t *testing.T) {
 		"--set", "dashboard.image.tag=" + TestHelper.GetVersion(),
 		"--set", "grafana.image.tag=" + TestHelper.GetVersion(),
 		"--set", "tap.image.tag=" + TestHelper.GetVersion(),
+		"--set", "tapInjector.image.tag=" + TestHelper.GetVersion(),
 		"--wait",
 	}
 	// Install Viz Extension Chart
diff --git a/test/integration/opaqueports/opaque_ports_test.go b/test/integration/opaqueports/opaque_ports_test.go
index 385be30b3..28b1afcc5 100644
--- a/test/integration/opaqueports/opaque_ports_test.go
+++ b/test/integration/opaqueports/opaque_ports_test.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
-	"strings"
+	"regexp"
 	"testing"
 	"time"
 
@@ -17,9 +17,9 @@ var TestHelper *testutil.TestHelper
 var (
 	appName = "app"
 
-	// With the app's port marked as opaque, we expect to find a single open
-	// TCP connection that is not TLS'd because the port is skipped.
-	tcpMetric = "tcp_open_connections{peer=\"src\",direction=\"inbound\",tls=\"true\",client_id=\"default.linkerd-opaque-ports-test.serviceaccount.identity.linkerd.cluster.local\"}"
+	tcpMetricRE = regexp.MustCompile(
+		`^tcp_open_total\{direction="inbound",peer="src",target_addr=".*:4143",tls="true",client_id="default\.linkerd-opaque-ports-test\.serviceaccount\.identity\.linkerd\.cluster\.local"\}$`,
+	)
 )
 
 func TestMain(m *testing.M) {
@@ -78,7 +78,7 @@ func TestOpaquePorts(t *testing.T) {
 		if err != nil {
 			testutil.AnnotatedFatalf(t, "error getting metrics for pod", "error getting metrics for pod\n%s", err)
 		}
-		if !strings.Contains(metrics, tcpMetric) {
+		if tcpMetricRE.MatchString(metrics) {
 			testutil.AnnotatedFatalf(t, "failed to find expected TCP metric when port is marked as opaque", "failed to find expected TCP metric when port is marked as opaque\n%s", metrics)
 		}
 	})
diff --git a/test/integration/stat/stat_test.go b/test/integration/stat/stat_test.go
index 586059b52..836c9712d 100644
--- a/test/integration/stat/stat_test.go
+++ b/test/integration/stat/stat_test.go
@@ -80,6 +80,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 				"linkerd-prometheus": "1/1",
 				"linkerd-tap":        "1/1",
 				"linkerd-web":        "1/1",
+				"tap-injector":       "1/1",
 			},
 		},
 		{
@@ -116,7 +117,7 @@ func TestCliStatForLinkerdNamespace(t *testing.T) {
 		{
 			args: []string{"viz", "stat", "ns", TestHelper.GetVizNamespace()},
 			expectedRows: map[string]string{
-				TestHelper.GetVizNamespace(): "4/4",
+				TestHelper.GetVizNamespace(): "5/5",
 			},
 		},
 		{
diff --git a/testutil/inject_validator.go b/testutil/inject_validator.go
index 2800904b5..bc616ca4d 100644
--- a/testutil/inject_validator.go
+++ b/testutil/inject_validator.go
@@ -77,6 +77,15 @@ func (iv *InjectValidator) validateEnvVar(container *v1.Container, envName, expe
 	return fmt.Errorf("cannot find env: %s", envName)
 }
 
+func (iv *InjectValidator) validateNoEnvVar(container *v1.Container, envName string) error {
+	for _, env := range container.Env {
+		if env.Name == envName {
+			return fmt.Errorf("env: %s, expected to not be set, actual %s", envName, env.Value)
+		}
+	}
+	return nil
+}
+
 func (iv *InjectValidator) validatePort(container *v1.Container, portName string, expectedValue int) error {
 	for _, port := range container.Ports {
 		if port.Name == portName {
@@ -129,7 +138,7 @@ func (iv *InjectValidator) validateProxyContainer(pod *v1.PodSpec) error {
 	}
 
 	if iv.DisableTap {
-		if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_TAP_DISABLED", enabled); err != nil {
+		if err := iv.validateNoEnvVar(proxyContainer, "LINKERD2_PROXY_TAP_SVC"); err != nil {
 			return err
 		}
 	}
diff --git a/viz/cmd/install_test.go b/viz/cmd/install_test.go
index 93fd4fa34..6d77c7167 100644
--- a/viz/cmd/install_test.go
+++ b/viz/cmd/install_test.go
@@ -17,6 +17,11 @@ func TestRender(t *testing.T) {
 			"crtPEM":   "test-tap-crt-pem",
 			"caBundle": "test-tap-ca-bundle",
 		},
+		"tapInjector": map[string]interface{}{
+			"keyPEM":   "test-tap-key-pem",
+			"crtPEM":   "test-tap-crt-pem",
+			"caBundle": "test-tap-ca-bundle",
+		},
 	}
 
 	testCases := []struct {
diff --git a/viz/cmd/testdata/install_default.golden b/viz/cmd/testdata/install_default.golden
index 819bf6aee..17f9f291d 100644
--- a/viz/cmd/testdata/install_default.golden
+++ b/viz/cmd/testdata/install_default.golden
@@ -841,6 +841,162 @@ spec:
         secret:
           secretName: linkerd-tap-k8s-tls
 
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: linkerd-viz
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+---
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: linkerd-viz
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+type: kubernetes.io/tls
+data:
+  tls.crt: dGVzdC10YXAtY3J0LXBlbQ==
+  tls.key: dGVzdC10YXAta2V5LXBlbQ==
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    linkerd.io/extension: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: linkerd-viz
+      path: "/"
+    caBundle: dGVzdC10YXAtY2EtYnVuZGxl
+  failurePolicy: Ignore
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: linkerd-viz
+    component: tap-injector
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+  labels:
+    linkerd.io/extension: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: linkerd-viz
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      component: tap-injector
+  template:
+    metadata:
+      annotations:
+        checksum/config: 33e988bace52d67983db9661563009d9e7f2a4877dd05add7300d2f25d2579ba
+        linkerd.io/created-by: linkerd/helm dev-undefined
+      labels:
+        linkerd.io/extension: linkerd-viz
+        component: tap-injector
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: ghcr.io/linkerd/controller:dev-undefined
+        imagePullPolicy: 
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        resources:
+        securityContext:
+          runAsUser: 2103
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
 ---
 ###
 ### Web
diff --git a/viz/cmd/testdata/install_prometheus_disabled.golden b/viz/cmd/testdata/install_prometheus_disabled.golden
index cd5522bd1..59df1ebce 100644
--- a/viz/cmd/testdata/install_prometheus_disabled.golden
+++ b/viz/cmd/testdata/install_prometheus_disabled.golden
@@ -549,6 +549,162 @@ spec:
         secret:
           secretName: linkerd-tap-k8s-tls
 
+###
+### Tap Injector RBAC
+###
+---
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+rules:
+- apiGroups: [""]
+  resources: ["namespaces"]
+  verbs: ["get", "list", "watch"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: linkerd-tap-injector
+  labels:
+    linkerd.io/extension: linkerd-viz
+subjects:
+- kind: ServiceAccount
+  name: tap-injector
+  namespace: linkerd-viz
+roleRef:
+  kind: ClusterRole
+  name: linkerd-tap-injector
+  apiGroup: rbac.authorization.k8s.io
+---
+kind: ServiceAccount
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+---
+kind: Secret
+apiVersion: v1
+metadata:
+  name: tap-injector-k8s-tls
+  namespace: linkerd-viz
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+type: kubernetes.io/tls
+data:
+  tls.crt: dGVzdC10YXAtY3J0LXBlbQ==
+  tls.key: dGVzdC10YXAta2V5LXBlbQ==
+---
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-tap-injector-webhook-config
+  labels:
+    linkerd.io/extension: linkerd-viz
+webhooks:
+- name: tap-injector.linkerd.io
+  clientConfig:
+    service:
+      name: tap-injector
+      namespace: linkerd-viz
+      path: "/"
+    caBundle: dGVzdC10YXAtY2EtYnVuZGxl
+  failurePolicy: Ignore
+  reinvocationPolicy: IfNeeded
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None
+---
+###
+### Tap Injector
+###
+---
+kind: Service
+apiVersion: v1
+metadata:
+  name: tap-injector
+  namespace: linkerd-viz
+  labels:
+    linkerd.io/extension: linkerd-viz
+    component: tap-injector
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+spec:
+  type: ClusterIP
+  selector:
+    component: tap-injector
+  ports:
+  - name: tap-injector
+    port: 443
+    targetPort: tap-injector
+---
+kind: Deployment
+apiVersion: apps/v1
+metadata:
+  annotations:
+    linkerd.io/created-by: linkerd/helm dev-undefined
+  labels:
+    linkerd.io/extension: linkerd-viz
+    app.kubernetes.io/name: tap-injector
+    app.kubernetes.io/part-of: Linkerd
+    component: tap-injector
+  name: tap-injector
+  namespace: linkerd-viz
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      component: tap-injector
+  template:
+    metadata:
+      annotations:
+        checksum/config: 33e988bace52d67983db9661563009d9e7f2a4877dd05add7300d2f25d2579ba
+        linkerd.io/created-by: linkerd/helm dev-undefined
+      labels:
+        linkerd.io/extension: linkerd-viz
+        component: tap-injector
+    spec:
+      nodeSelector:
+        beta.kubernetes.io/os: linux
+      containers:
+      - args:
+        - tap-injector
+        - -tap-service-name=linkerd-tap.linkerd-viz.serviceaccount.identity.$(_l5d_ns).$(_l5d_trustdomain)
+        image: ghcr.io/linkerd/controller:dev-undefined
+        imagePullPolicy: 
+        livenessProbe:
+          httpGet:
+            path: /ping
+            port: 9995
+          initialDelaySeconds: 10
+        name: tap-injector
+        ports:
+        - containerPort: 8443
+          name: tap-injector
+        - containerPort: 9995
+          name: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: 9995
+        resources:
+        securityContext:
+          runAsUser: 2103
+        volumeMounts:
+        - mountPath: /var/run/linkerd/tls
+          name: tls
+          readOnly: true
+      serviceAccountName: tap-injector
+      volumes:
+      - name: tls
+        secret:
+          secretName: tap-injector-k8s-tls
 ---
 ###
 ### Web