Repository: linkerd/linkerd2
Pull request #5820 (2021-02-25 02:03:12)

Add viz and jaeger list commands
Pods can only participate in tracing if they have been injected by the jaeger-injector.  Similarly, pods may only be tapped if they have been injected by the tap-injector.  Since pods which were already running when the injector starts will not be injected until those pods are restarted, it can be difficult to know which pods in your cluster will participate in tracing or be valid tap targets.

We add the command `linkerd viz list` to list meshed pods and indicate which can be tapped, which need to be restarted before they can be tapped, and which have tap disabled.

```console
> linkerd viz list -A
Pods with tap enabled:
	* collector-7f585dc68-z8vc8.linkerd-jaeger
	* jaeger-69fc767648-mxtc4.linkerd-jaeger
	* jaeger-injector-67fbccc487-sjh4c.linkerd-jaeger
	* grafana-84c9b569b9-27qsj.linkerd-viz
	* metrics-api-6c956b4b58-5xvz8.linkerd-viz
	* prometheus-7fdb866467-s4q5m.linkerd-viz
	* tap-768b5ddc6c-hdfb2.linkerd-viz
	* tap-injector-ff446c479-4wtsm.linkerd-viz
	* web-5f79854c4f-lpv5l.linkerd-viz
Pods missing tap configuration (restart these pods to enable tap):
	* linkerd-gateway-777b7cb9bf-7fr2n.linkerd-multicluster
	* linkerd-controller-6864cf5f8c-bxp7l.linkerd
	* linkerd-destination-67499b8df7-fqqb9.linkerd
	* linkerd-identity-7c577f7454-c2v7r.linkerd
	* linkerd-proxy-injector-c7844b9f6-hwbsm.linkerd
	* linkerd-sp-validator-f4c8cc6d6-658tb.linkerd
``` 

Similarly, we add the command `linkerd jaeger list` to list meshed pods and indicate which will participate in tracing.

```console
> linkerd jaeger list -A
Pods with tracing enabled:
	* grafana-84c9b569b9-27qsj.linkerd-viz
	* metrics-api-6c956b4b58-5xvz8.linkerd-viz
	* prometheus-7fdb866467-s4q5m.linkerd-viz
	* tap-768b5ddc6c-hdfb2.linkerd-viz
	* tap-injector-ff446c479-4wtsm.linkerd-viz
	* web-5f79854c4f-lpv5l.linkerd-viz
Pods missing tracing configuration (restart these pods to enable tracing):
	* collector-7f585dc68-z8vc8.linkerd-jaeger
	* jaeger-69fc767648-mxtc4.linkerd-jaeger
	* jaeger-injector-67fbccc487-sjh4c.linkerd-jaeger
	* linkerd-gateway-777b7cb9bf-7fr2n.linkerd-multicluster
	* linkerd-controller-6864cf5f8c-bxp7l.linkerd
	* linkerd-destination-67499b8df7-fqqb9.linkerd
	* linkerd-identity-7c577f7454-c2v7r.linkerd
	* linkerd-proxy-injector-c7844b9f6-hwbsm.linkerd
	* linkerd-sp-validator-f4c8cc6d6-658tb.linkerd
```

This commands list pods in the context's default namespcae by default, but can be configured with the usual `-n` and `-A` flags.

This replaces the jaeger extension's data plane check which gave a warning if there were pods with tracing.  That check has been removed here.


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 6d0b56cff..ffc4d68ea 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,6 +1,6 @@
 # Changes
 
-## edge 21.2.4
+## edge-21.2.4
 
 This edge is a release candidate for `stable-2.10.0`! It wraps up the functional
 changes planned for the upcoming stable release. We hope you can help us test
diff --git a/charts/linkerd2/templates/controller.yaml b/charts/linkerd2/templates/controller.yaml
index 791989503..1c1ad9abf 100644
--- a/charts/linkerd2/templates/controller.yaml
+++ b/charts/linkerd2/templates/controller.yaml
@@ -72,7 +72,7 @@ spec:
     metadata:
       annotations:
         {{ include "partials.annotations.created-by" . }}
-        {{- include "partials.proxy.annotations" .Values.proxy| nindent 8}}
+        {{- include "partials.proxy.annotations" . | nindent 8}}
         {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
       labels:
         linkerd.io/control-plane-component: controller
diff --git a/charts/linkerd2/templates/destination.yaml b/charts/linkerd2/templates/destination.yaml
index 21bec1dcc..37a3709cb 100644
--- a/charts/linkerd2/templates/destination.yaml
+++ b/charts/linkerd2/templates/destination.yaml
@@ -91,7 +91,7 @@ spec:
     metadata:
       annotations:
         {{ include "partials.annotations.created-by" . }}
-        {{- include "partials.proxy.annotations" .Values.proxy| nindent 8}}
+        {{- include "partials.proxy.annotations" . | nindent 8}}
         {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
       labels:
         linkerd.io/control-plane-component: destination
@@ -119,6 +119,7 @@ spec:
         - -enable-endpoint-slices={{.Values.enableEndpointSlices}}
         - -cluster-domain={{.Values.clusterDomain}}
         - -identity-trust-domain={{.Values.identityTrustDomain | default .Values.clusterDomain}}
+        - -default-opaque-ports={{.Values.proxy.opaquePorts}}
         {{- include "partials.linkerd.trace" . | nindent 8 -}}
         image: {{.Values.controllerImage}}:{{default .Values.linkerdVersion .Values.controllerImageVersion}}
         imagePullPolicy: {{.Values.imagePullPolicy}}
diff --git a/charts/linkerd2/templates/identity.yaml b/charts/linkerd2/templates/identity.yaml
index 2492efa8e..5578e9f07 100644
--- a/charts/linkerd2/templates/identity.yaml
+++ b/charts/linkerd2/templates/identity.yaml
@@ -110,7 +110,7 @@ spec:
     metadata:
       annotations:
         {{ include "partials.annotations.created-by" . }}
-        {{- include "partials.proxy.annotations" .Values.proxy| nindent 8}}
+        {{- include "partials.proxy.annotations" . | nindent 8}}
         {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
       labels:
         linkerd.io/control-plane-component: identity
diff --git a/charts/linkerd2/templates/proxy-injector.yaml b/charts/linkerd2/templates/proxy-injector.yaml
index 0ec351109..26ad212ce 100644
--- a/charts/linkerd2/templates/proxy-injector.yaml
+++ b/charts/linkerd2/templates/proxy-injector.yaml
@@ -36,7 +36,7 @@ spec:
         checksum/config: {{ include (print $.Template.BasePath "/proxy-injector-rbac.yaml") . | sha256sum }}
         {{- end }}
         {{ include "partials.annotations.created-by" . }}
-        {{- include "partials.proxy.annotations" .Values.proxy| nindent 8}}
+        {{- include "partials.proxy.annotations" . | nindent 8}}
         {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
       labels:
         linkerd.io/control-plane-component: proxy-injector
diff --git a/charts/linkerd2/templates/sp-validator.yaml b/charts/linkerd2/templates/sp-validator.yaml
index bff04477a..39589c11c 100644
--- a/charts/linkerd2/templates/sp-validator.yaml
+++ b/charts/linkerd2/templates/sp-validator.yaml
@@ -73,7 +73,7 @@ spec:
         checksum/config: {{ include (print $.Template.BasePath "/sp-validator-rbac.yaml") . | sha256sum }}
         {{- end }}
         {{ include "partials.annotations.created-by" . }}
-        {{- include "partials.proxy.annotations" .Values.proxy| nindent 8}}
+        {{- include "partials.proxy.annotations" . | nindent 8}}
         {{- with .Values.podAnnotations }}{{ toYaml . | trim | nindent 8 }}{{- end }}
       labels:
         linkerd.io/control-plane-component: sp-validator
diff --git a/charts/partials/templates/_metadata.tpl b/charts/partials/templates/_metadata.tpl
index 637afed32..7cf074458 100644
--- a/charts/partials/templates/_metadata.tpl
+++ b/charts/partials/templates/_metadata.tpl
@@ -3,8 +3,8 @@ linkerd.io/created-by: {{ .Values.cliVersion | default (printf "linkerd/helm %s"
 {{- end -}}
 
 {{- define "partials.proxy.annotations" -}}
-linkerd.io/identity-mode: {{ternary "default" "disabled" (not .disableIdentity)}}
-linkerd.io/proxy-version: {{.image.version}}
+linkerd.io/identity-mode: {{ternary "default" "disabled" (not .Values.proxy.disableIdentity)}}
+linkerd.io/proxy-version: {{.Values.proxy.image.version | default .Values.linkerdVersion}}
 {{- end -}}
 
 {{/*
diff --git a/controller/Dockerfile b/controller/Dockerfile
index f9fd014cf..53770d014 100644
--- a/controller/Dockerfile
+++ b/controller/Dockerfile
@@ -2,8 +2,8 @@ ARG BUILDPLATFORM=linux/amd64
 
 FROM --platform=$BUILDPLATFORM docker.io/curlimages/curl:7.75.0  as await
 WORKDIR /tmp
-ARG TARGETARCH=amd64
 ARG LINKERD_AWAIT_VERSION=v0.2.1
+ARG TARGETARCH
 RUN curl -fsSvLo linkerd-await https://github.com/olix0r/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await
 
 # Precompile key slow-to-build dependencies
diff --git a/controller/api/destination/endpoint_translator.go b/controller/api/destination/endpoint_translator.go
index 3a849da1d..3fb4b6648 100644
--- a/controller/api/destination/endpoint_translator.go
+++ b/controller/api/destination/endpoint_translator.go
@@ -10,7 +10,6 @@ import (
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
 	"github.com/linkerd/linkerd2/pkg/addr"
 	"github.com/linkerd/linkerd2/pkg/k8s"
-	"github.com/linkerd/linkerd2/pkg/opaqueports"
 	logging "github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	coreinformers "k8s.io/client-go/informers/core/v1"
@@ -30,6 +29,7 @@ type endpointTranslator struct {
 	identityTrustDomain string
 	enableH2Upgrade     bool
 	nodeTopologyLabels  map[string]string
+	defaultOpaquePorts  map[uint32]struct{}
 
 	availableEndpoints watcher.AddressSet
 	filteredSnapshot   watcher.AddressSet
@@ -43,6 +43,7 @@ func newEndpointTranslator(
 	enableH2Upgrade bool,
 	service string,
 	srcNodeName string,
+	defaultOpaquePorts map[uint32]struct{},
 	nodes coreinformers.NodeInformer,
 	stream pb.Destination_GetServer,
 	log *logging.Entry,
@@ -65,6 +66,7 @@ func newEndpointTranslator(
 		identityTrustDomain,
 		enableH2Upgrade,
 		nodeTopologyLabels,
+		defaultOpaquePorts,
 		availableEndpoints,
 		filteredSnapshot,
 		stream,
@@ -221,7 +223,7 @@ func (et *endpointTranslator) sendClientAdd(set watcher.AddressSet) {
 			// If the opaque ports annotation was not set, then set the
 			// endpoint's opaque ports to the default value.
 			if !ok {
-				opaquePorts = opaqueports.DefaultOpaquePorts
+				opaquePorts = et.defaultOpaquePorts
 			}
 			wa, err = toWeightedAddr(address, opaquePorts, et.enableH2Upgrade, et.identityTrustDomain, et.controllerNS, et.log)
 		} else {
diff --git a/controller/api/destination/server.go b/controller/api/destination/server.go
index 34ab39d77..da0e8b50c 100644
--- a/controller/api/destination/server.go
+++ b/controller/api/destination/server.go
@@ -12,7 +12,6 @@ import (
 	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	labels "github.com/linkerd/linkerd2/pkg/k8s"
-	"github.com/linkerd/linkerd2/pkg/opaqueports"
 	"github.com/linkerd/linkerd2/pkg/prometheus"
 	"github.com/linkerd/linkerd2/pkg/util"
 	logging "github.com/sirupsen/logrus"
@@ -37,6 +36,7 @@ type (
 		controllerNS        string
 		identityTrustDomain string
 		clusterDomain       string
+		defaultOpaquePorts  map[uint32]struct{}
 
 		k8sAPI   *k8s.API
 		log      *logging.Entry
@@ -64,14 +64,16 @@ func NewServer(
 	enableEndpointSlices bool,
 	k8sAPI *k8s.API,
 	clusterDomain string,
+	defaultOpaquePorts map[uint32]struct{},
 	shutdown <-chan struct{},
 ) *grpc.Server {
 	log := logging.WithFields(logging.Fields{
 		"addr":      addr,
 		"component": "server",
 	})
+
 	endpoints := watcher.NewEndpointsWatcher(k8sAPI, log, enableEndpointSlices)
-	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log)
+	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)
 	profiles := watcher.NewProfileWatcher(k8sAPI, log)
 	trafficSplits := watcher.NewTrafficSplitWatcher(k8sAPI, log)
 	ips := watcher.NewIPWatcher(k8sAPI, endpoints, log)
@@ -87,6 +89,7 @@ func NewServer(
 		controllerNS,
 		identityTrustDomain,
 		clusterDomain,
+		defaultOpaquePorts,
 		k8sAPI,
 		log,
 		shutdown,
@@ -118,6 +121,7 @@ func (s *server) Get(dest *pb.GetDestination, stream pb.Destination_GetServer) e
 		s.enableH2Upgrade,
 		dest.GetPath(),
 		token.NodeName,
+		s.defaultOpaquePorts,
 		s.nodes,
 		stream,
 		log,
@@ -225,7 +229,7 @@ func (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetPr
 				// If the opaque ports annotation was not set, then set the
 				// endpoint's opaque ports to the default value.
 				if !ok {
-					opaquePorts = opaqueports.DefaultOpaquePorts
+					opaquePorts = s.defaultOpaquePorts
 				}
 				endpoint, err = toWeightedAddr(podSet.Addresses[podID], opaquePorts, s.enableH2Upgrade, s.identityTrustDomain, s.controllerNS, log)
 				if err != nil {
diff --git a/controller/api/destination/watcher/opaque_ports_watcher.go b/controller/api/destination/watcher/opaque_ports_watcher.go
index 33a45304a..02b828085 100644
--- a/controller/api/destination/watcher/opaque_ports_watcher.go
+++ b/controller/api/destination/watcher/opaque_ports_watcher.go
@@ -7,7 +7,6 @@ import (
 	"github.com/linkerd/linkerd2-proxy-init/ports"
 	"github.com/linkerd/linkerd2/controller/k8s"
 	labels "github.com/linkerd/linkerd2/pkg/k8s"
-	"github.com/linkerd/linkerd2/pkg/opaqueports"
 	"github.com/linkerd/linkerd2/pkg/util"
 	log "github.com/sirupsen/logrus"
 	logging "github.com/sirupsen/logrus"
@@ -20,9 +19,10 @@ type (
 	// opaque ports annotation is added to a service, the watcher will update
 	// listeners—if any—subscribed to that service.
 	OpaquePortsWatcher struct {
-		subscriptions map[ServiceID]*svcSubscriptions
-		k8sAPI        *k8s.API
-		log           *logging.Entry
+		subscriptions      map[ServiceID]*svcSubscriptions
+		k8sAPI             *k8s.API
+		log                *logging.Entry
+		defaultOpaquePorts map[uint32]struct{}
 		sync.RWMutex
 	}
 
@@ -39,11 +39,12 @@ type (
 
 // NewOpaquePortsWatcher creates a OpaquePortsWatcher and begins watching for
 // k8sAPI for service changes.
-func NewOpaquePortsWatcher(k8sAPI *k8s.API, log *logging.Entry) *OpaquePortsWatcher {
+func NewOpaquePortsWatcher(k8sAPI *k8s.API, log *logging.Entry, opaquePorts map[uint32]struct{}) *OpaquePortsWatcher {
 	opw := &OpaquePortsWatcher{
-		subscriptions: make(map[ServiceID]*svcSubscriptions),
-		k8sAPI:        k8sAPI,
-		log:           log.WithField("component", "opaque-ports-watcher"),
+		subscriptions:      make(map[ServiceID]*svcSubscriptions),
+		k8sAPI:             k8sAPI,
+		log:                log.WithField("component", "opaque-ports-watcher"),
+		defaultOpaquePorts: opaquePorts,
 	}
 	k8sAPI.Svc().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
 		AddFunc:    opw.addService,
@@ -69,7 +70,7 @@ func (opw *OpaquePortsWatcher) Subscribe(id ServiceID, listener OpaquePortsUpdat
 	// and no opaque ports
 	if !ok {
 		opw.subscriptions[id] = &svcSubscriptions{
-			opaquePorts: opaqueports.DefaultOpaquePorts,
+			opaquePorts: opw.defaultOpaquePorts,
 			listeners:   []OpaquePortsUpdateListener{listener},
 		}
 		return nil
@@ -121,7 +122,7 @@ func (opw *OpaquePortsWatcher) addService(obj interface{}) {
 	// If the opaque ports annotation was not set, then set the service's
 	// opaque ports to the default value.
 	if !ok {
-		opaquePorts = opaqueports.DefaultOpaquePorts
+		opaquePorts = opw.defaultOpaquePorts
 	}
 	ss, ok := opw.subscriptions[id]
 	// If there are no subscriptions for this service, create one with the
@@ -172,7 +173,7 @@ func (opw *OpaquePortsWatcher) deleteService(obj interface{}) {
 		return
 	}
 	old := ss.opaquePorts
-	ss.opaquePorts = opaqueports.DefaultOpaquePorts
+	ss.opaquePorts = opw.defaultOpaquePorts
 	// Do not send an update if the service already had the default opaque ports
 	if portsEqual(old, ss.opaquePorts) {
 		return
diff --git a/controller/cmd/destination/main.go b/controller/cmd/destination/main.go
index 02ff3ae9f..197d33886 100644
--- a/controller/cmd/destination/main.go
+++ b/controller/cmd/destination/main.go
@@ -14,6 +14,7 @@ import (
 	"github.com/linkerd/linkerd2/pkg/flags"
 	pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
 	"github.com/linkerd/linkerd2/pkg/trace"
+	"github.com/linkerd/linkerd2/pkg/util"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -30,6 +31,8 @@ func Main(args []string) {
 	enableEndpointSlices := cmd.Bool("enable-endpoint-slices", false, "Enable the usage of EndpointSlice informers and resources")
 	trustDomain := cmd.String("identity-trust-domain", "", "configures the name suffix used for identities")
 	clusterDomain := cmd.String("cluster-domain", "", "kubernetes cluster domain")
+	defaultOpaquePorts := cmd.String("default-opaque-ports", "", "configures the default opaque ports")
+
 	traceCollector := flags.AddTraceFlags(cmd)
 
 	flags.ConfigureAndParse(cmd, args)
@@ -58,6 +61,13 @@ func Main(args []string) {
 		log.Warnf("expected cluster domain through args (falling back to %s)", *clusterDomain)
 	}
 
+	opaquePorts, err := util.ParsePorts(*defaultOpaquePorts)
+	if err != nil {
+		log.Fatalf("Failed to parse opaque Ports %s: %s", *defaultOpaquePorts, err)
+	}
+
+	log.Infof("Using default opaque ports: %v", opaquePorts)
+
 	if *traceCollector != "" {
 		if err := trace.InitializeTracing("linkerd-destination", *traceCollector); err != nil {
 			log.Warnf("failed to initialize tracing: %s", err)
@@ -106,6 +116,7 @@ func Main(args []string) {
 		*enableEndpointSlices,
 		k8sAPI,
 		*clusterDomain,
+		opaquePorts,
 		done,
 	)
 
diff --git a/jaeger/cmd/check.go b/jaeger/cmd/check.go
index d208ff641..0083c9167 100644
--- a/jaeger/cmd/check.go
+++ b/jaeger/cmd/check.go
@@ -5,13 +5,10 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"strings"
 	"time"
 
-	"github.com/linkerd/linkerd2/jaeger/pkg/labels"
 	"github.com/linkerd/linkerd2/pkg/healthcheck"
 	"github.com/spf13/cobra"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -22,9 +19,6 @@ const (
 
 	// linkerdJaegerExtensionCheck adds checks related to the jaeger extension
 	linkerdJaegerExtensionCheck healthcheck.CategoryID = "linkerd-jaeger"
-
-	// linkerdJaegerExtensionDataPlaneCheck adds checks related to the jaeger extension
-	linkerdJaegerExtensionDataPlaneCheck healthcheck.CategoryID = JaegerExtensionName + "-data-plane"
 )
 
 var (
@@ -32,10 +26,8 @@ var (
 )
 
 type checkOptions struct {
-	wait      time.Duration
-	output    string
-	proxy     bool
-	namespace string
+	wait   time.Duration
+	output string
 }
 
 func jaegerCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
@@ -140,53 +132,6 @@ func jaegerCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
 	return healthcheck.NewCategory(linkerdJaegerExtensionCheck, checkers, true)
 }
 
-// JaegerDataPlaneCategory returns a healthcheck.Category containing checkers
-// to verify the jaeger injection
-func JaegerDataPlaneCategory(hc *healthcheck.HealthChecker) *healthcheck.Category {
-	return healthcheck.NewCategory(linkerdJaegerExtensionDataPlaneCheck, []healthcheck.Checker{
-		*healthcheck.NewChecker("data plane namespace exists").
-			WithHintAnchor("l5d-data-plane-exists").
-			Fatal().
-			WithCheck(func(ctx context.Context) error {
-				if hc.DataPlaneNamespace == "" {
-					// when checking proxies in all namespaces, this check is a no-op
-					return nil
-				}
-				return hc.CheckNamespace(ctx, hc.DataPlaneNamespace, true)
-			}),
-		*healthcheck.NewChecker("data-plane pods have tracing enabled").
-			WithHintAnchor("l5d-jaeger-data-plane-trace").
-			Warning().
-			WithCheck(func(ctx context.Context) error {
-				pods, err := hc.KubeAPIClient().GetPodsByNamespace(ctx, hc.DataPlaneNamespace)
-				if err != nil {
-					return err
-				}
-
-				return checkForTraceConfiguration(pods)
-			}),
-	}, true)
-}
-
-// checkForTraceConfiguration checks if the trace annotation is present
-// only for the pods with tracing enabled
-func checkForTraceConfiguration(pods []corev1.Pod) error {
-	var podsWithoutTraceConfig []string
-	for i := range pods {
-		pod := pods[i]
-		// Check for jaeger-injector annotation
-		if !labels.IsTracingEnabled(&pod) {
-			podsWithoutTraceConfig = append(podsWithoutTraceConfig, fmt.Sprintf("* %s/%s", pod.Namespace, pod.Name))
-		}
-
-	}
-
-	if len(podsWithoutTraceConfig) > 0 {
-		return fmt.Errorf("Some data plane pods do not have tracing configured and cannot be traced:\n\t%s", strings.Join(podsWithoutTraceConfig, "\n\t"))
-	}
-	return nil
-}
-
 func newCheckOptions() *checkOptions {
 	return &checkOptions{
 		wait:   300 * time.Second,
@@ -223,8 +168,10 @@ code.`,
 
 	cmd.Flags().StringVarP(&options.output, "output", "o", options.output, "Output format. One of: basic, json")
 	cmd.Flags().DurationVar(&options.wait, "wait", options.wait, "Maximum allowed time for all tests to pass")
-	cmd.Flags().BoolVar(&options.proxy, "proxy", options.proxy, "Also run data-plane checks, to determine if the data plane is healthy")
-	cmd.Flags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
+	cmd.Flags().Bool("proxy", false, "Also run data-plane checks, to determine if the data plane is healthy")
+	cmd.Flags().StringP("namespace", "n", "", "Namespace to use for --proxy checks (default: all namespaces)")
+	cmd.Flags().MarkHidden("proxy")
+	cmd.Flags().MarkHidden("namespace")
 
 	return cmd
 }
@@ -243,7 +190,6 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
 		ImpersonateGroup:      impersonateGroup,
 		APIAddr:               apiAddr,
 		RetryDeadline:         time.Now().Add(options.wait),
-		DataPlaneNamespace:    options.namespace,
 	})
 
 	err = hc.InitializeKubeAPIClient()
@@ -254,9 +200,6 @@ func configureAndRunChecks(wout io.Writer, werr io.Writer, options *checkOptions
 	}
 
 	hc.AppendCategories(*jaegerCategory(hc))
-	if options.proxy {
-		hc.AppendCategories(*JaegerDataPlaneCategory(hc))
-	}
 
 	success := healthcheck.RunChecks(wout, werr, hc, options.output)
 
diff --git a/jaeger/cmd/list.go b/jaeger/cmd/list.go
new file mode 100644
index 000000000..1f2d964a6
--- /dev/null
+++ b/jaeger/cmd/list.go
@@ -0,0 +1,85 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/linkerd/linkerd2/jaeger/pkg/labels"
+	pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
+	"github.com/linkerd/linkerd2/pkg/k8s"
+	pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
+	"github.com/spf13/cobra"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type listOptions struct {
+	namespace     string
+	allNamespaces bool
+}
+
+func newCmdList() *cobra.Command {
+	var options listOptions
+
+	cmd := &cobra.Command{
+		Use:   "list [flags]",
+		Short: "Lists which pods have tracing enabled",
+		Args:  cobra.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
+			if err != nil {
+				return err
+			}
+
+			if options.namespace == "" {
+				options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
+			}
+			if options.allNamespaces {
+				options.namespace = v1.NamespaceAll
+			}
+
+			pods, err := k8sAPI.CoreV1().Pods(options.namespace).List(cmd.Context(), metav1.ListOptions{})
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err)
+				os.Exit(1)
+			}
+
+			var tracingEnabled, tracingNotEnabled []v1.Pod
+
+			for _, pod := range pods.Items {
+				pod := pod
+				if pkgK8s.IsMeshed(&pod, controlPlaneNamespace) {
+					if labels.IsTracingEnabled(&pod) {
+						tracingEnabled = append(tracingEnabled, pod)
+					} else {
+						tracingNotEnabled = append(tracingNotEnabled, pod)
+					}
+				}
+			}
+
+			if len(tracingEnabled) > 0 {
+				fmt.Println("Pods with tracing enabled:")
+				for _, pod := range tracingEnabled {
+					fmt.Printf("\t* %s/%s\n", pod.Namespace, pod.Name)
+				}
+			}
+
+			if len(tracingNotEnabled) > 0 {
+				fmt.Println("Pods missing tracing configuration (restart these pods to enable tracing):")
+				for _, pod := range tracingNotEnabled {
+					fmt.Printf("\t* %s/%s\n", pod.Namespace, pod.Name)
+				}
+			}
+
+			if len(tracingEnabled)+len(tracingNotEnabled) == 0 {
+				fmt.Println("No meshed pods found")
+			}
+
+			return nil
+		},
+	}
+	cmd.Flags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "The namespace to list pods in")
+	cmd.Flags().BoolVarP(&options.allNamespaces, "all-namespaces", "A", options.allNamespaces, "If present, list pods across all namespaces")
+
+	return cmd
+}
diff --git a/jaeger/cmd/root.go b/jaeger/cmd/root.go
index 183bdd018..8849c07cf 100644
--- a/jaeger/cmd/root.go
+++ b/jaeger/cmd/root.go
@@ -63,10 +63,11 @@ func NewCmdJaeger() *cobra.Command {
 	jaegerCmd.PersistentFlags().StringArrayVar(&impersonateGroup, "as-group", []string{}, "Group to impersonate for Kubernetes operations")
 	jaegerCmd.PersistentFlags().StringVar(&apiAddr, "api-addr", "", "Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)")
 	jaegerCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Turn on debug logging")
-	jaegerCmd.AddCommand(newCmdInstall())
 	jaegerCmd.AddCommand(NewCmdCheck())
-	jaegerCmd.AddCommand(newCmdUninstall())
 	jaegerCmd.AddCommand(newCmdDashboard())
+	jaegerCmd.AddCommand(newCmdInstall())
+	jaegerCmd.AddCommand(newCmdList())
+	jaegerCmd.AddCommand(newCmdUninstall())
 
 	return jaegerCmd
 }
diff --git a/jaeger/injector/Dockerfile b/jaeger/injector/Dockerfile
index 15e21f0c9..99804c370 100644
--- a/jaeger/injector/Dockerfile
+++ b/jaeger/injector/Dockerfile
@@ -2,8 +2,8 @@ ARG BUILDPLATFORM=linux/amd64
 
 FROM --platform=$BUILDPLATFORM docker.io/curlimages/curl:7.75.0  as await
 WORKDIR /tmp
-ARG TARGETARCH=amd64
 ARG LINKERD_AWAIT_VERSION=v0.2.1
+ARG TARGETARCH
 RUN curl -fsSvLo linkerd-await https://github.com/olix0r/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await
 
 # Precompile key slow-to-build dependencies
diff --git a/pkg/opaqueports/defaults.go b/pkg/opaqueports/defaults.go
deleted file mode 100644
index 11b142553..000000000
--- a/pkg/opaqueports/defaults.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package opaqueports
-
-// DefaultOpaquePorts is the default list of opaque ports that the destination
-// server will use to determine whether a destination is an opaque protocol.
-// When a pod or service already has its own annotation, that value will have
-// priority of this.
-//
-// Note: Keep in sync with proxy.opaquePorts in values.yaml
-var DefaultOpaquePorts = map[uint32]struct{}{
-	25:    {},
-	443:   {},
-	587:   {},
-	3306:  {},
-	5432:  {},
-	11211: {},
-}
diff --git a/pkg/util/parsing.go b/pkg/util/parsing.go
index 7f1fba0a0..4ee6de48c 100644
--- a/pkg/util/parsing.go
+++ b/pkg/util/parsing.go
@@ -10,6 +10,28 @@ import (
 	corev1 "k8s.io/api/core/v1"
 )
 
+// ParsePorts parses the given ports string into a map of ports;
+// this includes converting port ranges into separate ports
+func ParsePorts(portsString string) (map[uint32]struct{}, error) {
+	opaquePorts := make(map[uint32]struct{})
+	if portsString != "" {
+		portRanges := GetPortRanges(portsString)
+		for _, portRange := range portRanges {
+			pr := portRange.GetPortRange()
+			portsRange, err := ports.ParsePortRange(pr)
+			if err != nil {
+				log.Warnf("Invalid port range [%v]: %s", pr, err)
+				continue
+			}
+			for i := portsRange.LowerBound; i <= portsRange.UpperBound; i++ {
+				opaquePorts[uint32(i)] = struct{}{}
+			}
+
+		}
+	}
+	return opaquePorts, nil
+}
+
 // ParseContainerOpaquePorts parses the opaque ports annotation into a list of ports;
 // this includes converting port ranges into separate ports and named ports
 // into their port number equivalents.
diff --git a/viz/cmd/list.go b/viz/cmd/list.go
new file mode 100644
index 000000000..2abe868a4
--- /dev/null
+++ b/viz/cmd/list.go
@@ -0,0 +1,94 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+
+	pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
+	"github.com/linkerd/linkerd2/pkg/k8s"
+	pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
+	vizLabels "github.com/linkerd/linkerd2/viz/pkg/labels"
+	"github.com/spf13/cobra"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type listOptions struct {
+	namespace     string
+	allNamespaces bool
+}
+
+func newCmdList() *cobra.Command {
+	var options listOptions
+
+	cmd := &cobra.Command{
+		Use:   "list [flags]",
+		Short: "Lists which pods can be tapped",
+		Args:  cobra.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
+			if err != nil {
+				return err
+			}
+
+			if options.namespace == "" {
+				options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
+			}
+			if options.allNamespaces {
+				options.namespace = v1.NamespaceAll
+			}
+
+			pods, err := k8sAPI.CoreV1().Pods(options.namespace).List(cmd.Context(), metav1.ListOptions{})
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err)
+				os.Exit(1)
+			}
+
+			var tapEnabled, tapDisabled, tapNotEnabled []v1.Pod
+
+			for _, pod := range pods.Items {
+				pod := pod
+				if pkgK8s.IsMeshed(&pod, controlPlaneNamespace) {
+					if vizLabels.IsTapDisabled(pod) {
+						tapDisabled = append(tapDisabled, pod)
+					} else if !vizLabels.IsTapEnabled(&pod) {
+						tapNotEnabled = append(tapNotEnabled, pod)
+					} else {
+						tapEnabled = append(tapEnabled, pod)
+					}
+				}
+			}
+
+			if len(tapEnabled) > 0 {
+				fmt.Println("Pods with tap enabled:")
+				for _, pod := range tapEnabled {
+					fmt.Printf("\t* %s/%s\n", pod.Namespace, pod.Name)
+				}
+			}
+
+			if len(tapDisabled) > 0 {
+				fmt.Println("Pods with tap disabled:")
+				for _, pod := range tapDisabled {
+					fmt.Printf("\t* %s/%s\n", pod.Namespace, pod.Name)
+				}
+			}
+
+			if len(tapNotEnabled) > 0 {
+				fmt.Println("Pods missing tap configuration (restart these pods to enable tap):")
+				for _, pod := range tapNotEnabled {
+					fmt.Printf("\t* %s/%s\n", pod.Namespace, pod.Name)
+				}
+			}
+
+			if len(tapEnabled)+len(tapDisabled)+len(tapNotEnabled) == 0 {
+				fmt.Println("No meshed pods found")
+			}
+
+			return nil
+		},
+	}
+	cmd.Flags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "The namespace to list pods in")
+	cmd.Flags().BoolVarP(&options.allNamespaces, "all-namespaces", "A", options.allNamespaces, "If present, list pods across all namespaces")
+
+	return cmd
+}
diff --git a/viz/cmd/root.go b/viz/cmd/root.go
index 75134d26e..f5ecb19d4 100644
--- a/viz/cmd/root.go
+++ b/viz/cmd/root.go
@@ -70,16 +70,17 @@ func NewCmdViz() *cobra.Command {
 	vizCmd.PersistentFlags().StringArrayVar(&impersonateGroup, "as-group", []string{}, "Group to impersonate for Kubernetes operations")
 	vizCmd.PersistentFlags().StringVar(&apiAddr, "api-addr", "", "Override kubeconfig and communicate directly with the control plane at host:port (mostly for testing)")
 	vizCmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Turn on debug logging")
+	vizCmd.AddCommand(NewCmdCheck())
+	vizCmd.AddCommand(NewCmdDashboard())
+	vizCmd.AddCommand(NewCmdEdges())
 	vizCmd.AddCommand(newCmdInstall())
+	vizCmd.AddCommand(newCmdList())
+	vizCmd.AddCommand(newCmdProfile())
 	vizCmd.AddCommand(NewCmdRoutes())
 	vizCmd.AddCommand(NewCmdStat())
 	vizCmd.AddCommand(NewCmdTap())
 	vizCmd.AddCommand(NewCmdTop())
-	vizCmd.AddCommand(NewCmdEdges())
-	vizCmd.AddCommand(NewCmdDashboard())
 	vizCmd.AddCommand(newCmdUninstall())
-	vizCmd.AddCommand(newCmdProfile())
-	vizCmd.AddCommand(NewCmdCheck())
 
 	return vizCmd
 }
diff --git a/viz/metrics-api/Dockerfile b/viz/metrics-api/Dockerfile
index 557aa79e2..f9e6b376c 100644
--- a/viz/metrics-api/Dockerfile
+++ b/viz/metrics-api/Dockerfile
@@ -2,8 +2,8 @@ ARG BUILDPLATFORM=linux/amd64
 
 FROM --platform=$BUILDPLATFORM docker.io/curlimages/curl:7.75.0  as await
 WORKDIR /tmp
-ARG TARGETARCH=amd64
 ARG LINKERD_AWAIT_VERSION=v0.2.1
+ARG TARGETARCH
 RUN curl -fsSvLo linkerd-await https://github.com/olix0r/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await
 
 # Precompile key slow-to-build dependencies
diff --git a/viz/pkg/healthcheck/healthcheck.go b/viz/pkg/healthcheck/healthcheck.go
index 2d94000c0..3417d6d69 100644
--- a/viz/pkg/healthcheck/healthcheck.go
+++ b/viz/pkg/healthcheck/healthcheck.go
@@ -212,7 +212,7 @@ func (hc *HealthChecker) VizCategory() healthcheck.Category {
 				return
 			}),
 		*healthcheck.NewChecker("viz extension self-check").
-			WithHintAnchor("l5d-api-control-api").
+			WithHintAnchor("l5d-viz-metrics-api").
 			Fatal().
 			// to avoid confusing users with a prometheus readiness error, we only show
 			// "waiting for check to complete" while things converge. If after the timeout
diff --git a/viz/tap/Dockerfile b/viz/tap/Dockerfile
index 456e5e6b7..3901ab6e2 100644
--- a/viz/tap/Dockerfile
+++ b/viz/tap/Dockerfile
@@ -2,8 +2,8 @@ ARG BUILDPLATFORM=linux/amd64
 
 FROM --platform=$BUILDPLATFORM docker.io/curlimages/curl:7.75.0  as await
 WORKDIR /tmp
-ARG TARGETARCH=amd64
 ARG LINKERD_AWAIT_VERSION=v0.2.1
+ARG TARGETARCH
 RUN curl -fsSvLo linkerd-await https://github.com/olix0r/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await
 
 # Precompile key slow-to-build dependencies
diff --git a/web/Dockerfile b/web/Dockerfile
index 288ea26b4..915c52059 100644
--- a/web/Dockerfile
+++ b/web/Dockerfile
@@ -2,8 +2,8 @@ ARG BUILDPLATFORM=linux/amd64
 
 FROM --platform=$BUILDPLATFORM docker.io/curlimages/curl:7.75.0  as await
 WORKDIR /tmp
-ARG TARGETARCH=amd64
 ARG LINKERD_AWAIT_VERSION=v0.2.1
+ARG TARGETARCH
 RUN curl -fsSvLo linkerd-await https://github.com/olix0r/linkerd-await/releases/download/release%2F${LINKERD_AWAIT_VERSION}/linkerd-await-${LINKERD_AWAIT_VERSION}-${TARGETARCH} && chmod +x linkerd-await
 
 # Precompile key slow-to-build dependencies


Test patch:
diff --git a/cli/cmd/install_test.go b/cli/cmd/install_test.go
index f4c790c3a..5ee4f0978 100644
--- a/cli/cmd/install_test.go
+++ b/cli/cmd/install_test.go
@@ -78,7 +78,8 @@ func TestRender(t *testing.T) {
 				Inbound:  4143,
 				Outbound: 4140,
 			},
-			UID: 2102,
+			UID:         2102,
+			OpaquePorts: "25,443,587,3306,5432,11211",
 		},
 		ProxyInit: &charts.ProxyInit{
 			Image: &charts.Image{
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index b67acbe96..deb04f448 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -1619,6 +1619,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         - -trace-collector=collector.linkerd-jaeger.svc.cluster.local:55678
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
diff --git a/cli/cmd/testdata/install_custom_domain.golden b/cli/cmd/testdata/install_custom_domain.golden
index 6da98a44a..18524aa90 100644
--- a/cli/cmd/testdata/install_custom_domain.golden
+++ b/cli/cmd/testdata/install_custom_domain.golden
@@ -1617,6 +1617,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index 71e1e2718..104711aa4 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -1617,6 +1617,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: my.custom.registry/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden
index 811a82bc9..94649d6d9 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -1617,6 +1617,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-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 37c2b3bfa..93d971b1b 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -1617,6 +1617,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_ha_output.golden b/cli/cmd/testdata/install_ha_output.golden
index 943372ff1..6ea3bd78a 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -1792,6 +1792,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-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 7f21547f3..e457b3ce5 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -1792,6 +1792,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-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 991aa9d41..3ac825c96 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -1545,6 +1545,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output.golden b/cli/cmd/testdata/install_helm_output.golden
index 923177b0c..4c3c01cc6 100644
--- a/cli/cmd/testdata/install_helm_output.golden
+++ b/cli/cmd/testdata/install_helm_output.golden
@@ -1603,6 +1603,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=test.trust.domain
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:linkerd-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 8135f2b5f..dcd81da22 100644
--- a/cli/cmd/testdata/install_helm_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_output_ha.golden
@@ -1778,6 +1778,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=test.trust.domain
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:linkerd-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 c053d9d61..75cc3ead9 100644
--- a/cli/cmd/testdata/install_helm_output_ha_labels.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_labels.golden
@@ -1794,6 +1794,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=test.trust.domain
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:linkerd-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 03682e791..47dd4fc3c 100644
--- a/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
+++ b/cli/cmd/testdata/install_helm_output_ha_namespace_selector.golden
@@ -1778,6 +1778,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=test.trust.domain
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:linkerd-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 7345bccd2..95bb08791 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -1538,6 +1538,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden
index 2a02b20cd..e79feddbb 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -966,7 +966,7 @@ data:
       isIngress: false
       logFormat: plain
       logLevel: warn,linkerd=info
-      opaquePorts: ""
+      opaquePorts: 25,443,587,3306,5432,11211
       outboundConnectTimeout: ""
       ports:
         admin: 4191
@@ -1177,6 +1177,8 @@ spec:
           value: 10000ms
         - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE
           value: 10000ms
+        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION
+          value: "25,443,587,3306,5432,11211"
         - name: _pod_ns
           valueFrom:
             fieldRef:
@@ -1409,6 +1411,8 @@ spec:
           value: 10000ms
         - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE
           value: 10000ms
+        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION
+          value: "25,443,587,3306,5432,11211"
         - name: _pod_ns
           valueFrom:
             fieldRef:
@@ -1617,6 +1621,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: ControllerImage:ControllerImageVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
@@ -1660,6 +1665,8 @@ spec:
           value: 10000ms
         - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE
           value: 10000ms
+        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION
+          value: "25,443,587,3306,5432,11211"
         - name: _pod_ns
           valueFrom:
             fieldRef:
@@ -1921,6 +1928,8 @@ spec:
           value: 10000ms
         - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE
           value: 10000ms
+        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION
+          value: "25,443,587,3306,5432,11211"
         - name: _pod_ns
           valueFrom:
             fieldRef:
@@ -2175,6 +2184,8 @@ spec:
           value: 10000ms
         - name: LINKERD2_PROXY_OUTBOUND_CONNECT_KEEPALIVE
           value: 10000ms
+        - name: LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION
+          value: "25,443,587,3306,5432,11211"
         - name: _pod_ns
           valueFrom:
             fieldRef:
@@ -2302,7 +2313,7 @@ spec:
 ---
 apiVersion: v1
 data:
-  linkerd-config-overrides: Y2xpVmVyc2lvbjogQ2xpVmVyc2lvbgpjbHVzdGVyTmV0d29ya3M6IENsdXN0ZXJOZXR3b3Jrcwpjb250cm9sUGxhbmVUcmFjaW5nTmFtZXNwYWNlOiAiIgpjb250cm9sbGVySW1hZ2U6IENvbnRyb2xsZXJJbWFnZQpjb250cm9sbGVySW1hZ2VWZXJzaW9uOiBDb250cm9sbGVySW1hZ2VWZXJzaW9uCmNvbnRyb2xsZXJMb2dGb3JtYXQ6IENvbnRyb2xsZXJMb2dGb3JtYXQKY29udHJvbGxlckxvZ0xldmVsOiBDb250cm9sbGVyTG9nTGV2ZWwKZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICBuYW1lOiBEZWJ1Z0ltYWdlTmFtZQogICAgcHVsbFBvbGljeTogRGVidWdJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IERlYnVnVmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogIiIKaWRlbnRpdHk6CiAgaXNzdWVyOgogICAgY3J0RXhwaXJ5OiAiMjAzMC0wOC0yNlQwNzoxMzo0N1oiCiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmltYWdlUHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CmltYWdlUHVsbFNlY3JldHM6IG51bGwKbGlua2VyZFZlcnNpb246ICIiCnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJvZmlsZSB2YWxpZGF0b3IgY3J0CiAga2V5UEVNOiBwcm9maWxlIHZhbGlkYXRvciBrZXkKcHJveHk6CiAgaW1hZ2U6CiAgICBuYW1lOiBQcm94eUltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQcm94eVZlcnNpb24KICBpbmJvdW5kQ29ubmVjdFRpbWVvdXQ6ICIiCiAgb3BhcXVlUG9ydHM6ICIiCiAgb3V0Ym91bmRDb25uZWN0VGltZW91dDogIiIKICByZXNvdXJjZXM6CiAgICBjcHU6CiAgICAgIGxpbWl0OiBjcHUtbGltaXQKICAgICAgcmVxdWVzdDogY3B1LXJlcXVlc3QKICAgIG1lbW9yeToKICAgICAgbGltaXQ6IG1lbW9yeS1saW1pdAogICAgICByZXF1ZXN0OiBtZW1vcnktcmVxdWVzdApwcm94eUNvbnRhaW5lck5hbWU6IFByb3h5Q29udGFpbmVyTmFtZQpwcm94eUluaXQ6CiAgaWdub3JlT3V0Ym91bmRQb3J0czogIjQ0MyIKICBpbWFnZToKICAgIG5hbWU6IFByb3h5SW5pdEltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQcm94eUluaXRWZXJzaW9uCnByb3h5SW5qZWN0b3I6CiAgY2FCdW5kbGU6IHByb3h5IGluamVjdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJveHkgaW5qZWN0b3IgY3J0CiAga2V5UEVNOiBwcm94eSBpbmplY3RvciBrZXkKd2ViaG9va0ZhaWx1cmVQb2xpY3k6IFdlYmhvb2tGYWlsdXJlUG9saWN5Cg==
+  linkerd-config-overrides: Y2xpVmVyc2lvbjogQ2xpVmVyc2lvbgpjbHVzdGVyTmV0d29ya3M6IENsdXN0ZXJOZXR3b3Jrcwpjb250cm9sUGxhbmVUcmFjaW5nTmFtZXNwYWNlOiAiIgpjb250cm9sbGVySW1hZ2U6IENvbnRyb2xsZXJJbWFnZQpjb250cm9sbGVySW1hZ2VWZXJzaW9uOiBDb250cm9sbGVySW1hZ2VWZXJzaW9uCmNvbnRyb2xsZXJMb2dGb3JtYXQ6IENvbnRyb2xsZXJMb2dGb3JtYXQKY29udHJvbGxlckxvZ0xldmVsOiBDb250cm9sbGVyTG9nTGV2ZWwKZGVidWdDb250YWluZXI6CiAgaW1hZ2U6CiAgICBuYW1lOiBEZWJ1Z0ltYWdlTmFtZQogICAgcHVsbFBvbGljeTogRGVidWdJbWFnZVB1bGxQb2xpY3kKICAgIHZlcnNpb246IERlYnVnVmVyc2lvbgpoZWFydGJlYXRTY2hlZHVsZTogIiIKaWRlbnRpdHk6CiAgaXNzdWVyOgogICAgY3J0RXhwaXJ5OiAiMjAzMC0wOC0yNlQwNzoxMzo0N1oiCiAgICB0bHM6CiAgICAgIGNydFBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogICAgICAgIE1JSUJ3RENDQVdlZ0F3SUJBZ0lSQUpSSWdaOFJ0TzhFd2cxWGVwZjhUNDR3Q2dZSUtvWkl6ajBFQXdJd0tURW4KICAgICAgICBNQ1VHQTFVRUF4TWVhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUI0WERUSXdNRGd5CiAgICAgICAgT0RBM01UTTBOMW9YRFRNd01EZ3lOakEzTVRNME4xb3dLVEVuTUNVR0ExVUVBeE1lYVdSbGJuUnBkSGt1YkdsdQogICAgICAgIGEyVnlaQzVqYkhWemRHVnlMbXh2WTJGc01Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTEvRnAKICAgICAgICBmY1JuRGNlZEw2QWpVYVhZUHY0RElNQmFKdWZPSTVOV3R5K1hTWDdKalhnWnRNNzJkUXZSYVlhbnV4RDM2RHQxCiAgICAgICAgMi9KeHlpU2d4S1dSZG9heSthTndNRzR3RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQgogICAgICAgIEFmOENBUUF3SFFZRFZSME9CQllFRkkxV25ycU1ZS2FISE9vK3pweWlpRHEycE8wS01Da0dBMVVkRVFRaU1DQ0MKICAgICAgICBIbWxrWlc1MGFYUjVMbXhwYm10bGNtUXVZMngxYzNSbGNpNXNiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkhBREJFCiAgICAgICAgQWlBdHVvSTVYdUN0ckdWUnpTbVJUbDJyYTI4YVY5TXlUVTdkNXFuVEFGSEtTZ0lnUktDdmx1T1NnQTVPMjFwNQogICAgICAgIDUxdGRybWtIRVpScjBxbExTSmRIWWdFZk16az0KICAgICAgICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCiAgICAgIGtleVBFTTogfAogICAgICAgIC0tLS0tQkVHSU4gRUMgUFJJVkFURSBLRVktLS0tLQogICAgICAgIE1IY0NBUUVFSUFBZThuZmJ6WnU5Yy9PQjIrOHhKTTBGejdOVXdUUWF6dWxrRk5zNFRJNStvQW9HQ0NxR1NNNDkKICAgICAgICBBd0VIb1VRRFFnQUUxL0ZwZmNSbkRjZWRMNkFqVWFYWVB2NERJTUJhSnVmT0k1Tld0eStYU1g3SmpYZ1p0TTcyCiAgICAgICAgZFF2UmFZYW51eEQzNkR0MTIvSnh5aVNneEtXUmRvYXkrUT09CiAgICAgICAgLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQppZGVudGl0eVRydXN0QW5jaG9yc1BFTTogfAogIC0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQogIE1JSUJ3VENDQVdhZ0F3SUJBZ0lRZURacDVsRGFJeWdRNVVmTUtackZBVEFLQmdncWhrak9QUVFEQWpBcE1TY3cKICBKUVlEVlFRREV4NXBaR1Z1ZEdsMGVTNXNhVzVyWlhKa0xtTnNkWE4wWlhJdWJHOWpZV3d3SGhjTk1qQXdPREk0CiAgTURjeE1qUTNXaGNOTXpBd09ESTJNRGN4TWpRM1dqQXBNU2N3SlFZRFZRUURFeDVwWkdWdWRHbDBlUzVzYVc1cgogIFpYSmtMbU5zZFhOMFpYSXViRzlqWVd3d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFScWM3MFoKICBsMXZndzc5cmpCNXVTSVRJQ1VBNkd5ZnZTRmZjdUlpczdCL1hGU2trd0FIVTVTL3MxQUFQK1IwVFg3SEJXVUM0CiAgdWFHNFdXc2l3SktObjdtZ28zQXdiakFPQmdOVkhROEJBZjhFQkFNQ0FRWXdFZ1lEVlIwVEFRSC9CQWd3QmdFQgogIC93SUJBVEFkQmdOVkhRNEVGZ1FVNVl0alZWUGZkN0k3TkxIc24yQzI2RUJ5R1Ywd0tRWURWUjBSQkNJd0lJSWUKICBhV1JsYm5ScGRIa3ViR2x1YTJWeVpDNWpiSFZ6ZEdWeUxteHZZMkZzTUFvR0NDcUdTTTQ5QkFNQ0Ewa0FNRVlDCiAgSVFDTjdsQkZMRER2ang2VjArWGtqcEtFUlJzSllmNWFkTXZubG9GbDQ4aWxKZ0loQU50eGhuZGNyK1FKUHVDOAogIHZnVUMwZDIvOUZNdWVJVk1iKzQ2V1RDT2pzcXIKICAtLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCmltYWdlUHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CmltYWdlUHVsbFNlY3JldHM6IG51bGwKbGlua2VyZFZlcnNpb246ICIiCnByb2ZpbGVWYWxpZGF0b3I6CiAgY2FCdW5kbGU6IHByb2ZpbGUgdmFsaWRhdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJvZmlsZSB2YWxpZGF0b3IgY3J0CiAga2V5UEVNOiBwcm9maWxlIHZhbGlkYXRvciBrZXkKcHJveHk6CiAgaW1hZ2U6CiAgICBuYW1lOiBQcm94eUltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQcm94eVZlcnNpb24KICBpbmJvdW5kQ29ubmVjdFRpbWVvdXQ6ICIiCiAgb3V0Ym91bmRDb25uZWN0VGltZW91dDogIiIKICByZXNvdXJjZXM6CiAgICBjcHU6CiAgICAgIGxpbWl0OiBjcHUtbGltaXQKICAgICAgcmVxdWVzdDogY3B1LXJlcXVlc3QKICAgIG1lbW9yeToKICAgICAgbGltaXQ6IG1lbW9yeS1saW1pdAogICAgICByZXF1ZXN0OiBtZW1vcnktcmVxdWVzdApwcm94eUNvbnRhaW5lck5hbWU6IFByb3h5Q29udGFpbmVyTmFtZQpwcm94eUluaXQ6CiAgaWdub3JlT3V0Ym91bmRQb3J0czogIjQ0MyIKICBpbWFnZToKICAgIG5hbWU6IFByb3h5SW5pdEltYWdlTmFtZQogICAgcHVsbFBvbGljeTogSW1hZ2VQdWxsUG9saWN5CiAgICB2ZXJzaW9uOiBQcm94eUluaXRWZXJzaW9uCnByb3h5SW5qZWN0b3I6CiAgY2FCdW5kbGU6IHByb3h5IGluamVjdG9yIENBIGJ1bmRsZQogIGNydFBFTTogcHJveHkgaW5qZWN0b3IgY3J0CiAga2V5UEVNOiBwcm94eSBpbmplY3RvciBrZXkKd2ViaG9va0ZhaWx1cmVQb2xpY3k6IFdlYmhvb2tGYWlsdXJlUG9saWN5Cg==
 kind: Secret
 metadata:
   creationTimestamp: null
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index bd162b8f6..787e1eeb0 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -1617,6 +1617,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=cluster.local
         - -identity-trust-domain=cluster.local
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_values_file.golden b/cli/cmd/testdata/install_values_file.golden
index 92464eb57..128ceb248 100644
--- a/cli/cmd/testdata/install_values_file.golden
+++ b/cli/cmd/testdata/install_values_file.golden
@@ -1603,6 +1603,7 @@ spec:
         - -enable-endpoint-slices=false
         - -cluster-domain=example.com
         - -identity-trust-domain=example.com
+        - -default-opaque-ports=25,443,587,3306,5432,11211
         image: cr.l5d.io/linkerd/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/controller/api/destination/endpoint_translator_test.go b/controller/api/destination/endpoint_translator_test.go
index 77be5e817..24a5b74dc 100644
--- a/controller/api/destination/endpoint_translator_test.go
+++ b/controller/api/destination/endpoint_translator_test.go
@@ -146,6 +146,7 @@ metadata:
 		true,
 		"service-name.service-ns",
 		"test-123",
+		map[uint32]struct{}{},
 		k8sAPI.Node(),
 		mockGetServer,
 		logging.WithField("test", t.Name()),
diff --git a/controller/api/destination/server_test.go b/controller/api/destination/server_test.go
index e5d994151..d94f94a50 100644
--- a/controller/api/destination/server_test.go
+++ b/controller/api/destination/server_test.go
@@ -217,9 +217,17 @@ metadata:
 		t.Fatalf("NewFakeAPI returned an error: %s", err)
 	}
 	log := logging.WithField("test", t.Name())
+	defaultOpaquePorts := map[uint32]struct{}{
+		25:    {},
+		443:   {},
+		587:   {},
+		3306:  {},
+		5432:  {},
+		11211: {},
+	}
 
 	endpoints := watcher.NewEndpointsWatcher(k8sAPI, log, false)
-	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log)
+	opaquePorts := watcher.NewOpaquePortsWatcher(k8sAPI, log, defaultOpaquePorts)
 	profiles := watcher.NewProfileWatcher(k8sAPI, log)
 	trafficSplits := watcher.NewTrafficSplitWatcher(k8sAPI, log)
 	ips := watcher.NewIPWatcher(k8sAPI, endpoints, log)
@@ -239,6 +247,7 @@ metadata:
 		"linkerd",
 		"trust.domain",
 		"mycluster.local",
+		defaultOpaquePorts,
 		k8sAPI,
 		log,
 		make(<-chan struct{}),
diff --git a/controller/api/destination/watcher/opaque_ports_watcher_test.go b/controller/api/destination/watcher/opaque_ports_watcher_test.go
index 30dfab373..caa165c62 100644
--- a/controller/api/destination/watcher/opaque_ports_watcher_test.go
+++ b/controller/api/destination/watcher/opaque_ports_watcher_test.go
@@ -88,6 +88,15 @@ func (bopl *testOpaquePortsListener) UpdateService(ports map[uint32]struct{}) {
 }
 
 func TestOpaquePortsWatcher(t *testing.T) {
+	defaultOpaquePorts := map[uint32]struct{}{
+		25:    {},
+		443:   {},
+		587:   {},
+		3306:  {},
+		5432:  {},
+		11211: {},
+	}
+
 	for _, tt := range []struct {
 		name                string
 		initialState        []string
@@ -176,7 +185,7 @@ func TestOpaquePortsWatcher(t *testing.T) {
 		if err != nil {
 			t.Fatalf("NewFakeAPI returned an error: %s", err)
 		}
-		watcher := NewOpaquePortsWatcher(k8sAPI, logging.WithField("test", t.Name()))
+		watcher := NewOpaquePortsWatcher(k8sAPI, logging.WithField("test", t.Name()), defaultOpaquePorts)
 		k8sAPI.Sync(nil)
 		listener := newTestOpaquePortsListener()
 		watcher.Subscribe(tt.service, listener)
diff --git a/pkg/util/parsing_test.go b/pkg/util/parsing_test.go
new file mode 100644
index 000000000..ee9c365d3
--- /dev/null
+++ b/pkg/util/parsing_test.go
@@ -0,0 +1,57 @@
+package util
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+)
+
+func TestParsePorts(t *testing.T) {
+	testCases := []struct {
+		ports  string
+		result map[uint32]struct{}
+	}{
+		{
+			"25,443,587,3306,5432,11211",
+			map[uint32]struct{}{
+				25:    {},
+				443:   {},
+				587:   {},
+				3306:  {},
+				5432:  {},
+				11211: {},
+			},
+		},
+		{
+			"25,443-447,3306,5432-5435,11211",
+			map[uint32]struct{}{
+				25:    {},
+				443:   {},
+				444:   {},
+				445:   {},
+				446:   {},
+				447:   {},
+				3306:  {},
+				5432:  {},
+				5433:  {},
+				5434:  {},
+				5435:  {},
+				11211: {},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc // pin
+		t.Run(fmt.Sprintf("test %s", tc.ports), func(t *testing.T) {
+			ports, err := ParsePorts(tc.ports)
+			if err != nil {
+				t.Fatalf("could not parse ports: %v", err)
+			}
+
+			if !reflect.DeepEqual(ports, tc.result) {
+				t.Fatalf("Expected output: \"%v\", got: \"%v\"", tc.result, ports)
+			}
+		})
+	}
+}
diff --git a/test/integration/install_test.go b/test/integration/install_test.go
index 180e0cc2a..16e49e76f 100644
--- a/test/integration/install_test.go
+++ b/test/integration/install_test.go
@@ -466,7 +466,6 @@ func helmOverridesEdge(root *tls.CA) []string {
 	return []string{
 		"--set", "controllerLogLevel=debug",
 		"--set", "linkerdVersion=" + TestHelper.GetVersion(),
-		"--set", "proxy.image.version=" + TestHelper.GetVersion(),
 		// these ports will get verified in test/integration/inject
 		"--set", "proxyInit.ignoreInboundPorts=" + skippedInboundPortsEscaped,
 		"--set", "identityTrustDomain=cluster.local",