Repository: linkerd/linkerd2
Pull request #4864 (2020-08-12 13:11:30)

Enable endpoint slices when upgrading through CLI
## What/How
@adleong  pointed out in #4780 that when enabling slices during an upgrade, the new value does not persist in the `linkerd-config` ConfigMap. I took a closer look and it seems that we were never overwriting the values in case they were different.

* To fix this, I added an if block when validating and building the upgrade options -- if the current flag value differs from what we have in the ConfigMap, then change the ConfigMap value.
* When doing so, I made sure to check that if the cluster does not support `EndpointSlices` yet the flag is set to true, we will error out. This is done similarly (copy&paste similarily) to what's in the install part.
* Additionally, I have noticed that the helm ConfigMap template stored the flag value under `enableEndpointSlices` field name. I assume this was not changed in the initial PR to reflect the changes made in the protocol buffer. The API (and thus the CLI) uses the field name `endpointSliceEnabled` instead. I have changed the config template so that helm installations will use the same field, which can then be used in the destination service or other components that may implement slice support in the future.

## Tests

#### Install w/ slices off and upgrade w/ slices on
---
```sh
# install control plane through cli
$ target/cli/darwin/linkerd install --registry=docker.io/matei207 | k apply -f -
$ k get cm linkerd-config -n linkerd -o yaml | grep "endpointSliceEnabled"
    {"linkerdNamespace":"linkerd", ..., "endpointSliceEnabled":false}

# upgrade with slices enabled
$ target/cli/darwin/linkerd upgrade --enable-endpoint-slices=true | kubectl apply --prune -l linkerd.io/control-plane-ns=linkerd -f -
$ k get cm linkerd-config -n linkerd -o yaml | grep "endpointSliceEnabled"
    {"linkerdNamespace":"linkerd", ..., "endpointSliceEnabled":true}

# test that destination svc watches slices and not endpoints
$ k logs linkerd-destination-85bf58865d-fjb67 -n linkerd destination | grep "Watching EndpointSlice"
time="2020-08-12T11:09:50Z" level=debug msg="Watching EndpointSlice resources" addr=":8086" component=endpoints-watcher
```

#### Install w/ slices on and upgrade w/ slices off
---
```sh
# install control plane through cli
$ target/cli/darwin/linkerd install --registry=docker.io/matei207 --enable-endpoint-slices | k apply -f -
$ k get cm -n linkerd linkerd-config -o yaml | grep endpointSliceEnabled
    {"linkerdNamespace":"linkerd", ..., "endpointSliceEnabled":true

# upgrade with slices disabled
$ target/cli/darwin/linkerd install --registry=docker.io/matei207 --enable-endpoint-slices=false | k apply -f -
$ k get cm -n linkerd linkerd-config -o yaml | grep endpointSliceEnabled
    {"linkerdNamespace":"linkerd", ..., "endpointSliceEnabled":false}

# test that destination svc watches endpoints
$ k logs linkerd-destination-7c47f8f696-ddnpc -n linkerd destination | grep "Watching Endpoints"
time="2020-08-12T12:42:22Z" level=debug msg="Watching Endpoints resources" addr=":8086" component=endpoints-watcher
```

#### Helm install
---
Helm:
```sh
$ bin/helm install linkerd2 \
  --set-file global.identityTrustAnchorsPEM=ca.crt \
  --set-file identity.issuer.tls.crtPEM=issuer.crt \
  --set-file identity.issuer.tls.keyPEM=issuer.key \
  --set identity.issuer.crtExpiry=$exp \
  charts/linkerd2

# previously we would have had "enableEndpointSlices": false
$ k get cm -n linkerd linkerd-config -o yaml | grep endpointSliceEnabled
      "endpointSliceEnabled": "false"

```
Signed-off-by: Matei David <matei.david.35@gmail.com>


Matei207: > I'm struggling to wrap my head around the interaction of flags and configs but I _thiiiiiiiiiiink_ what we need to do here is to add the endpoint slice flag to the list of flags in `installOptions.recordFlags`. I believe that this will cause `linkerd upgrade` to save the value of the endpoint slice flag into the upgrade options. @alpeb does this sound correct to you?

Hmmm, I am struggling a bit myself with flags and configs 😅. I thought that was already happening. I remember I specifically looked at which flag set carries on to `linkerd upgrade` when I first implemented this. At the moment, `enable-endpoint-slices` is initialised as a flag in `recordableFlagSet()`. 

My assumption was that initially when using the recordable flags we render out the values (including `linkerd-config` cm) and also persist the value of the flags. When we upgrade, we re-use the same flags as the install (through the recordable flags) so that there is no mismatch in what is passed. Because simply rendering the values is not enough when upgrading, we also have to overwrite the values in `linkerd-config`. I assumed that would already be done though.

Again, not sure if I misunderstood something along the way.

alpeb: Yep, flag handling is currently some convoluted business :-(
Alex' [comment](https://github.com/linkerd/linkerd2/pull/4864#discussion_r470178470) should do the trick. If you do wanna keep the value in configs.Global and change the name, make sure you also update the corresponding entry in `./pkg/charts/linkerd2/values.go`
Matei207: Hiya @alpeb and @adleong thanks for the comments!

@adleong 
I have moved up the logic to be closer to `OmitWebhooks` and removed the unnecessary check. It makes sense based on what Alex said so thanks for pointing that out! 

I see your point about the upgrade story... I'd say optimistically adopters would still roll their pods even though the manifest has not changed? Either way, I'd be more than willing to revisit this and do the required work, however, would deprecating the `endpointSliceEnabled` field in the protocol buffer be straightforward? We would have to do that if we wanted to switch to a destination service-specific flag, right? I suppose it depends on whether we will certainly want to do that in the future, if you think there is a strong chance we will do it might as well do it sooner rather than later? Especially since the slice feature barely has any traction, it's easier to change it? 

@alpeb 
>  If you do wanna keep the value in configs.Global and change the name, make sure you also update the corresponding entry in ./pkg/charts/linkerd2/values.go

makes sense! don't think at the moment it needs to be changed, because the flag name is still the same but the name of the feature in the configmap is different. The reason for this difference in naming is because at the time I thought `endpointSliceEnabled` (i.e the cm value) sounds better as a boolean variable name but `enableEndpointSlices` sounds better as a flag name o.o. Think I accidentally made a mess out of it because of this. Because of this and my weak memory, in the `_config.tpl` I put the cm field name as `enableEndpointSlices` when it was supposed to be `endpointSliceEnabled` (the same as the protobuf).

Would you like me to rename the flag to be the same as the cm field -- so both would be `enableEndpointSlices` or `endpointSliceEnabled`? 

Patch:
diff --git a/.github/workflows/cloud_integration.yml b/.github/workflows/cloud_integration.yml
index 7dc835aef..633e15d68 100644
--- a/.github/workflows/cloud_integration.yml
+++ b/.github/workflows/cloud_integration.yml
@@ -100,11 +100,7 @@ jobs:
         name: testing-${{ env.TAG }}-${{ github.run_id }}
         num_nodes: 2
     - name: Run integration tests
-      env:
-        GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
-      run: |
-        echo "$GITCOOKIE_SH" | bash
-        bin/tests --skip-kind-create "$CMD"
+      run: bin/tests --skip-kind-create "$CMD"
     - name: CNI tests
       run: |
         export TAG="$($CMD version --client --short)"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c8e191012..d6e51c8d3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -183,11 +183,7 @@ jobs:
         name: testing-${{ steps.install_cli.outputs.tag }}-${{ github.run_id }}
         num_nodes: 2
     - name: Run integration tests
-      env:
-        GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
-      run: |
-        echo "$GITCOOKIE_SH" | bash
-        bin/tests --skip-kind-create "$CMD"
+      run: bin/tests --skip-kind-create "$CMD"
     - name: CNI tests
       run: |
         export TAG="$($CMD version --client --short)"
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml
index f4867b521..1bfc23277 100644
--- a/.github/workflows/static_checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -18,11 +18,7 @@ jobs:
       # actions/checkout@v2
       uses: actions/checkout@722adc6
     - name: Go lint
-      env:
-        GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
-      run: |
-        echo "$GITCOOKIE_SH" | bash
-        bin/lint --verbose
+      run: bin/lint --verbose
   go_format:
     name: Go format
     runs-on: ubuntu-18.04
@@ -33,11 +29,18 @@ jobs:
       # actions/checkout@v2
       uses: actions/checkout@722adc6
     - name: Format
-      env:
-        GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
-      run: |
-        echo "$GITCOOKIE_SH" | bash
-        bin/fmt
+      run: bin/fmt
+  proto_diff:
+    name: Proto diff
+    runs-on: ubuntu-18.04
+    container:
+      image: golang:1.13.4
+    steps:
+      - name: Checkout code
+        # actions/checkout@v2
+        uses: actions/checkout@722adc6
+      - name: Diff proto files
+        run: bin/protoc-diff
   shellcheck:
     name: shellcheck
     runs-on: ubuntu-18.04
diff --git a/BUILD.md b/BUILD.md
index 57565c725..a01ca399f 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -340,6 +340,24 @@ cd web/app
 yarn add [dep]
 ```
 
+#### Translations
+
+To add a locale:
+
+```bash
+cd web/app
+yarn lingui add-locale [locales...] # will create a messages.json file for new locale(s)
+```
+
+To extract message keys from existing components:
+
+```bash
+cd web/app
+yarn lingui extract
+...
+yarn lingui compile # done automatically in bin/web run
+```
+
 ### Rust
 
 All Rust development happens in the
diff --git a/CHANGES.md b/CHANGES.md
index 16633a5a7..aa7c16cff 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,41 @@
 # Changes
 
+## edge-20.8.3
+
+This edge release adds support for [topology-aware service routing][topology] to
+the Destination controller. When providing service discovery updates to proxies,
+the Destination controller will now filter endpoints based on the service's
+topology preferences. Additionally, this release includes bug fixes for the
+`linkerd check` CLI command and web dashboard.
+
+* CLI
+  * `linkerd check` will no longer warn about a looser webhook failure policy in
+    HA mode
+* Controller
+  * Added support for [topology-aware service routing][topology] to the Destination
+    controller (thanks @Matei207)
+  * Changed the Destination controller to always return destination overrides
+    for service profiles when no traffic split is present
+* Web UI
+  * Fixed Tap `Authority` dropdown not being populated (thanks to @tharun208!)
+  
+[topology]: https://kubernetes.io/docs/concepts/services-networking/service-topology/
+
+## edge-20.8.2
+
+This edge release adds an internationalization framework to the dashboard,
+Spanish translations to the dashboard UI, and a `linkerd multicluster uninstall`
+command for graceful removal of the multicluster components.
+
+* Web UI
+  * Added Spanish translations to the dashboard
+  * Added a framework and documentation to simplify creation of new
+    translations
+* Multicluster
+  * Added a multicluster uninstall command
+  * Added a warning from `linkerd check --multicluster` if the multicluster
+    support is not installed
+
 ## edge-20.8.1
 
 This edge adds multi-arch support to Linkerd! Our docker images and CLI now
diff --git a/bin/protoc-diff b/bin/protoc-diff
new file mode 100755
index 000000000..e75a0a8e0
--- /dev/null
+++ b/bin/protoc-diff
@@ -0,0 +1,22 @@
+#!/usr/bin/env sh
+
+set -eu
+
+# Install unzip if not already installed (Linux only)
+system=$(uname -s)
+if [ "$system" = Linux ] && ! [ -x "$(command -v unzip)" ]; then
+  apt-get update
+  apt-get install unzip
+fi
+
+bin/protoc-go.sh
+
+dir_dirty=$(git diff HEAD)
+if [ -z "$dir_dirty" ]; then
+  echo "Protobuf definitions match generated code"
+  exit 0
+else
+  echo "Protobuf definitions diverge from generated code:"
+  echo "$(git status)"
+  exit 64
+fi
diff --git a/charts/linkerd2/templates/_config.tpl b/charts/linkerd2/templates/_config.tpl
index bcac0a960..39f3ce805 100644
--- a/charts/linkerd2/templates/_config.tpl
+++ b/charts/linkerd2/templates/_config.tpl
@@ -12,8 +12,7 @@
   },
   "autoInjectContext": null,
   "omitWebhookSideEffects": {{.Values.omitWebhookSideEffects}},
-  "clusterDomain": "{{.Values.global.clusterDomain}}",
-  "enableEndpointSlices": "{{.Values.global.enableEndpointSlices}}"
+  "clusterDomain": "{{.Values.global.clusterDomain}}"
 }
 {{- end -}}
 
diff --git a/charts/linkerd2/templates/destination-rbac.yaml b/charts/linkerd2/templates/destination-rbac.yaml
index 3bab38e49..817488123 100644
--- a/charts/linkerd2/templates/destination-rbac.yaml
+++ b/charts/linkerd2/templates/destination-rbac.yaml
@@ -18,7 +18,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
diff --git a/charts/linkerd2/templates/destination.yaml b/charts/linkerd2/templates/destination.yaml
index f418318cc..d39415452 100644
--- a/charts/linkerd2/templates/destination.yaml
+++ b/charts/linkerd2/templates/destination.yaml
@@ -76,6 +76,7 @@ spec:
         - -controller-namespace={{.Values.global.namespace}}
         - -enable-h2-upgrade={{.Values.enableH2Upgrade}}
         - -log-level={{.Values.global.controllerLogLevel}}
+        - -enable-endpoint-slices={{.Values.global.enableEndpointSlices}}
         {{- include "partials.linkerd.trace" . | nindent 8 -}}
         image: {{.Values.controllerImage}}:{{default .Values.global.linkerdVersion .Values.global.controllerImageVersion}}
         imagePullPolicy: {{.Values.global.imagePullPolicy}}
@@ -103,7 +104,7 @@ spec:
         volumeMounts:
         - mountPath: /var/run/linkerd/config
           name: config
-      {{- $tree := deepCopy . }}      
+      {{- $tree := deepCopy . }}
       {{- if not (empty .Values.destinationProxyResources) }}
       {{- $r := merge .Values.destinationProxyResources .Values.global.proxy.resources }}
       {{- $_ := set $tree.Values.global.proxy "resources" $r }}
diff --git a/cli/cmd/install.go b/cli/cmd/install.go
index 605bacda3..980c28088 100644
--- a/cli/cmd/install.go
+++ b/cli/cmd/install.go
@@ -956,7 +956,6 @@ func (options *installOptions) globalConfig(identity *pb.IdentityContext) *pb.Gl
 	return &pb.Global{
 		LinkerdNamespace:       controlPlaneNamespace,
 		CniEnabled:             options.cniEnabled,
-		EndpointSliceEnabled:   options.enableEndpointSlices,
 		Version:                options.controlPlaneVersion,
 		IdentityContext:        identity,
 		OmitWebhookSideEffects: options.omitWebhookSideEffects,
diff --git a/cli/cmd/upgrade.go b/cli/cmd/upgrade.go
index b771d6b1c..30839fb03 100644
--- a/cli/cmd/upgrade.go
+++ b/cli/cmd/upgrade.go
@@ -242,11 +242,19 @@ func (options *upgradeOptions) validateAndBuild(stage string, k *k8s.KubernetesA
 	// The overrideConfigs() is used to override proxy configs only.
 	options.overrideConfigs(configs, map[string]string{})
 
+	if options.enableEndpointSlices {
+		if err = validateEndpointSlicesFeature(); err != nil {
+			return nil, fmt.Errorf("--enableEndpointSlice=true not supported: %s", err)
+		}
+	}
+
 	// Override configs with upgrade CLI options.
 	if options.controlPlaneVersion != "" {
 		configs.GetGlobal().Version = options.controlPlaneVersion
 	}
+
 	configs.GetInstall().Flags = options.recordedFlags
+
 	configs.GetGlobal().OmitWebhookSideEffects = options.omitWebhookSideEffects
 	if configs.GetGlobal().GetClusterDomain() == "" {
 		configs.GetGlobal().ClusterDomain = defaultClusterDomain
@@ -389,7 +397,6 @@ func (options *upgradeOptions) validateAndBuild(stage string, k *k8s.KubernetesA
 func setFlagsFromInstall(flags *pflag.FlagSet, installFlags []*pb.Install_Flag) {
 	for _, i := range installFlags {
 		if f := flags.Lookup(i.GetName()); f != nil && !f.Changed {
-
 			// The function recordFlags() stores the string representation of flags in the ConfigMap
 			// so a stringSlice is stored e.g. as [a,b].
 			// To avoid having f.Value.Set() interpreting that as a string we need to remove
diff --git a/controller/api/destination/endpoint_translator.go b/controller/api/destination/endpoint_translator.go
index 7a1811699..a25682da8 100644
--- a/controller/api/destination/endpoint_translator.go
+++ b/controller/api/destination/endpoint_translator.go
@@ -9,6 +9,9 @@ import (
 	"github.com/linkerd/linkerd2/pkg/addr"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	logging "github.com/sirupsen/logrus"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
 )
 
 const defaultWeight uint32 = 10000
@@ -19,8 +22,12 @@ type endpointTranslator struct {
 	controllerNS        string
 	identityTrustDomain string
 	enableH2Upgrade     bool
-	stream              pb.Destination_GetServer
-	log                 *logging.Entry
+	nodeTopologyLabels  map[string]string
+
+	availableEndpoints watcher.AddressSet
+	filteredSnapshot   watcher.AddressSet
+	stream             pb.Destination_GetServer
+	log                *logging.Entry
 }
 
 func newEndpointTranslator(
@@ -28,6 +35,8 @@ func newEndpointTranslator(
 	identityTrustDomain string,
 	enableH2Upgrade bool,
 	service string,
+	srcNodeName string,
+	k8sClient kubernetes.Interface,
 	stream pb.Destination_GetServer,
 	log *logging.Entry,
 ) *endpointTranslator {
@@ -36,10 +45,158 @@ func newEndpointTranslator(
 		"service":   service,
 	})
 
-	return &endpointTranslator{controllerNS, identityTrustDomain, enableH2Upgrade, stream, log}
+	nodeTopologyLabels, err := getK8sNodeTopology(k8sClient, srcNodeName)
+	if err != nil {
+		log.Errorf("Failed to get node topology for node %s: %s", srcNodeName, err)
+	}
+	availableEndpoints := newEmptyAddressSet()
+
+	filteredSnapshot := newEmptyAddressSet()
+
+	return &endpointTranslator{
+		controllerNS,
+		identityTrustDomain,
+		enableH2Upgrade,
+		nodeTopologyLabels,
+		availableEndpoints,
+		filteredSnapshot,
+		stream,
+		log,
+	}
 }
 
 func (et *endpointTranslator) Add(set watcher.AddressSet) {
+	for id, address := range set.Addresses {
+		et.availableEndpoints.Addresses[id] = address
+	}
+
+	et.sendFilteredUpdate(set)
+}
+
+func (et *endpointTranslator) Remove(set watcher.AddressSet) {
+	for id := range set.Addresses {
+		delete(et.availableEndpoints.Addresses, id)
+	}
+
+	et.sendFilteredUpdate(set)
+}
+
+func (et *endpointTranslator) sendFilteredUpdate(set watcher.AddressSet) {
+	et.availableEndpoints = watcher.AddressSet{
+		Addresses:       et.availableEndpoints.Addresses,
+		Labels:          set.Labels,
+		TopologicalPref: set.TopologicalPref,
+	}
+
+	filtered := et.filterAddresses()
+	diffAdd, diffRemove := et.diffEndpoints(filtered)
+
+	if len(diffAdd.Addresses) > 0 {
+		et.sendClientAdd(diffAdd)
+	}
+	if len(diffRemove.Addresses) > 0 {
+		et.sendClientRemove(diffRemove)
+	}
+
+	et.filteredSnapshot = filtered
+}
+
+// filterAddresses is responsible for filtering endpoints based on service topology preference.
+// The client will receive only endpoints with the same topology label value as the source node,
+// the order of labels is based on the topological preference elicited from the K8s service.
+func (et *endpointTranslator) filterAddresses() watcher.AddressSet {
+	if len(et.availableEndpoints.TopologicalPref) == 0 {
+		allAvailEndpoints := make(map[watcher.ID]watcher.Address)
+		for k, v := range et.availableEndpoints.Addresses {
+			allAvailEndpoints[k] = v
+		}
+		return watcher.AddressSet{
+			Addresses: allAvailEndpoints,
+			Labels:    et.availableEndpoints.Labels,
+		}
+	}
+
+	et.log.Debugf("Filtering through address set with preference %v", et.availableEndpoints.TopologicalPref)
+	filtered := make(map[watcher.ID]watcher.Address)
+	for _, pref := range et.availableEndpoints.TopologicalPref {
+		// '*' as a topology preference means all endpoints
+		if pref == "*" {
+			return et.availableEndpoints
+		}
+
+		srcLocality, ok := et.nodeTopologyLabels[pref]
+		if !ok {
+			continue
+		}
+
+		for id, address := range et.availableEndpoints.Addresses {
+			addrLocality := address.TopologyLabels[pref]
+			if addrLocality == srcLocality {
+				filtered[id] = address
+			}
+		}
+
+		// if we filtered at least one endpoint, it means that preference has been satisfied
+		if len(filtered) > 0 {
+			et.log.Debugf("Filtered %d from a total of %d", len(filtered), len(et.availableEndpoints.Addresses))
+			return watcher.AddressSet{
+				Addresses: filtered,
+				Labels:    et.availableEndpoints.Labels,
+			}
+		}
+	}
+
+	// if we have no filtered endpoints or the '*' preference then no topology pref is satisfied
+	return newEmptyAddressSet()
+}
+
+// diffEndpoints calculates the difference between the filtered set of endpoints in the current (Add/Remove) operation
+// and the snapshot of previously filtered endpoints. This diff allows the client to receive only the endpoints that
+// satisfy the topological preference, by adding new endpoints and removing stale ones.
+func (et *endpointTranslator) diffEndpoints(filtered watcher.AddressSet) (watcher.AddressSet, watcher.AddressSet) {
+	add := make(map[watcher.ID]watcher.Address)
+	remove := make(map[watcher.ID]watcher.Address)
+
+	for id, address := range filtered.Addresses {
+		if _, ok := et.filteredSnapshot.Addresses[id]; !ok {
+			add[id] = address
+		}
+	}
+
+	for id, address := range et.filteredSnapshot.Addresses {
+		if _, ok := filtered.Addresses[id]; !ok {
+			remove[id] = address
+		}
+	}
+
+	return watcher.AddressSet{
+			Addresses: add,
+			Labels:    filtered.Labels,
+		},
+		watcher.AddressSet{
+			Addresses: remove,
+			Labels:    filtered.Labels,
+		}
+}
+
+func (et *endpointTranslator) NoEndpoints(exists bool) {
+	et.log.Debugf("NoEndpoints(%+v)", exists)
+
+	u := &pb.Update{
+		Update: &pb.Update_NoEndpoints{
+			NoEndpoints: &pb.NoEndpoints{
+				Exists: exists,
+			},
+		},
+	}
+
+	et.log.Debugf("Sending destination no endpoints: %+v", u)
+	if err := et.stream.Send(u); err != nil {
+		et.log.Errorf("Failed to send address update: %s", err)
+	}
+}
+
+func (et *endpointTranslator) sendClientAdd(set watcher.AddressSet) {
 	addrs := []*pb.WeightedAddr{}
 	for _, address := range set.Addresses {
 		var (
@@ -103,7 +260,7 @@ func (et *endpointTranslator) Add(set watcher.AddressSet) {
 	}
 }
 
-func (et *endpointTranslator) Remove(set watcher.AddressSet) {
+func (et *endpointTranslator) sendClientRemove(set watcher.AddressSet) {
 	addrs := []*net.TcpAddress{}
 	for _, address := range set.Addresses {
 		tcpAddr, err := et.toAddr(address)
@@ -126,23 +283,6 @@ func (et *endpointTranslator) Remove(set watcher.AddressSet) {
 	}
 }
 
-func (et *endpointTranslator) NoEndpoints(exists bool) {
-	et.log.Debugf("NoEndpoints(%+v)", exists)
-
-	u := &pb.Update{
-		Update: &pb.Update_NoEndpoints{
-			NoEndpoints: &pb.NoEndpoints{
-				Exists: exists,
-			},
-		},
-	}
-
-	et.log.Debugf("Sending destination no endpoints: %+v", u)
-	if err := et.stream.Send(u); err != nil {
-		et.log.Errorf("Failed to send address update: %s", err)
-	}
-}
-
 func (et *endpointTranslator) toAddr(address watcher.Address) (*net.TcpAddress, error) {
 	ip, err := addr.ParseProxyIPV4(address.IP)
 	if err != nil {
@@ -203,3 +343,29 @@ func (et *endpointTranslator) toWeightedAddr(address watcher.Address) (*pb.Weigh
 		ProtocolHint: hint,
 	}, nil
 }
+
+func getK8sNodeTopology(k8sClient kubernetes.Interface, srcNode string) (map[string]string, error) {
+	nodeTopology := make(map[string]string)
+	node, err := k8sClient.CoreV1().Nodes().Get(srcNode, metav1.GetOptions{})
+	if err != nil {
+		return nodeTopology, err
+	}
+
+	for k, v := range node.Labels {
+		if k == corev1.LabelHostname ||
+			k == corev1.LabelZoneFailureDomainStable ||
+			k == corev1.LabelZoneRegionStable {
+			nodeTopology[k] = v
+		}
+	}
+
+	return nodeTopology, nil
+}
+
+func newEmptyAddressSet() watcher.AddressSet {
+	return watcher.AddressSet{
+		Addresses:       make(map[watcher.ID]watcher.Address),
+		Labels:          make(map[string]string),
+		TopologicalPref: []string{},
+	}
+}
diff --git a/controller/api/destination/server.go b/controller/api/destination/server.go
index 1896b4ced..0b789896d 100644
--- a/controller/api/destination/server.go
+++ b/controller/api/destination/server.go
@@ -30,6 +30,7 @@ type (
 		identityTrustDomain string
 		clusterDomain       string
 
+		k8sAPI   *k8s.API
 		log      *logging.Entry
 		shutdown <-chan struct{}
 	}
@@ -75,6 +76,7 @@ func NewServer(
 		controllerNS,
 		identityTrustDomain,
 		clusterDomain,
+		k8sAPI,
 		log,
 		shutdown,
 	}
@@ -93,11 +95,19 @@ func (s *server) Get(dest *pb.GetDestination, stream pb.Destination_GetServer) e
 	}
 	log.Debugf("Get %s", dest.GetPath())
 
+	var token contextToken
+	if dest.GetContextToken() != "" {
+		token = s.parseContextToken(dest.GetContextToken())
+		log.Debugf("Dest token: %v", token)
+	}
+
 	translator := newEndpointTranslator(
 		s.controllerNS,
 		s.identityTrustDomain,
 		s.enableH2Upgrade,
 		dest.GetPath(),
+		token.NodeName,
+		s.k8sAPI.Client,
 		stream,
 		log,
 	)
diff --git a/controller/api/destination/traffic_split_adaptor.go b/controller/api/destination/traffic_split_adaptor.go
index aa6ab240e..88456d2eb 100644
--- a/controller/api/destination/traffic_split_adaptor.go
+++ b/controller/api/destination/traffic_split_adaptor.go
@@ -6,6 +6,7 @@ import (
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
 	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	ts "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/split/v1alpha1"
+	"k8s.io/apimachinery/pkg/api/resource"
 )
 
 // trafficSplitAdaptor merges traffic splits into service profiles, encoding
@@ -64,11 +65,15 @@ func (tsa *trafficSplitAdaptor) publish() {
 			overrides = append(overrides, dst)
 		}
 		merged.Spec.DstOverrides = overrides
-	}
-
-	if tsa.profile == nil && tsa.split == nil {
-		tsa.listener.Update(nil)
 	} else {
-		tsa.listener.Update(&merged)
+		// If there is no traffic split, always return a destination override
+		// so that it's known the host is a service.
+		dst := &sp.WeightedDst{
+			Authority: fmt.Sprintf("%s.%s.svc.%s.:%d", tsa.id.Name, tsa.id.Namespace, tsa.clusterDomain, tsa.port),
+			Weight:    resource.MustParse("1"),
+		}
+		merged.Spec.DstOverrides = []*sp.WeightedDst{dst}
 	}
+
+	tsa.listener.Update(&merged)
 }
diff --git a/controller/api/destination/watcher/endpoints_watcher.go b/controller/api/destination/watcher/endpoints_watcher.go
index 225aff435..90db0c044 100644
--- a/controller/api/destination/watcher/endpoints_watcher.go
+++ b/controller/api/destination/watcher/endpoints_watcher.go
@@ -47,12 +47,14 @@ type (
 		OwnerKind         string
 		Identity          string
 		AuthorityOverride string
+		TopologyLabels    map[string]string
 	}
 
 	// AddressSet is a set of Address, indexed by ID.
 	AddressSet struct {
-		Addresses map[ID]Address
-		Labels    map[string]string
+		Addresses       map[ID]Address
+		Labels          map[string]string
+		TopologicalPref []string
 	}
 
 	portAndHostname struct {
@@ -88,7 +90,8 @@ type (
 		k8sAPI               *k8s.API
 		enableEndpointSlices bool
 
-		ports map[portAndHostname]*portPublisher
+		TopologyPref []string
+		ports        map[portAndHostname]*portPublisher
 		// All access to the servicePublisher and its portPublishers is explicitly synchronized by
 		// this mutex.
 		sync.Mutex
@@ -107,6 +110,7 @@ type (
 		log                  *logging.Entry
 		k8sAPI               *k8s.API
 		enableEndpointSlices bool
+		TopologyPref         []string
 
 		exists    bool
 		addresses AddressSet
@@ -393,6 +397,7 @@ func (ew *EndpointsWatcher) getOrNewServicePublisher(id ServiceID) *servicePubli
 				"svc":       id.Name,
 			}),
 			k8sAPI:               ew.k8sAPI,
+			TopologyPref:         make([]string, 0),
 			ports:                make(map[portAndHostname]*portPublisher),
 			enableEndpointSlices: ew.enableEndpointSlices,
 		}
@@ -462,12 +467,23 @@ func (sp *servicePublisher) updateService(newService *corev1.Service) {
 	defer sp.Unlock()
 	sp.log.Debugf("Updating service for %s", sp.id)
 
+	if sp.enableEndpointSlices {
+		sp.TopologyPref = make([]string, len(newService.Spec.TopologyKeys))
+		copy(sp.TopologyPref, newService.Spec.TopologyKeys)
+	}
+
 	for key, port := range sp.ports {
+		if sp.enableEndpointSlices {
+			port.TopologyPref = sp.TopologyPref
+			port.updateTopologyPreference()
+		}
+
 		newTargetPort := getTargetPort(newService, key.port)
 		if newTargetPort != port.targetPort {
 			port.updatePort(newTargetPort)
 		}
 	}
+
 }
 
 func (sp *servicePublisher) subscribe(srcPort Port, hostname string, listener EndpointUpdateListener) {
@@ -528,6 +544,7 @@ func (sp *servicePublisher) newPortPublisher(srcPort Port, hostname string) *por
 		log:                  log,
 		metrics:              endpointsVecs.newEndpointsMetrics(sp.metricsLabels(srcPort, hostname)),
 		enableEndpointSlices: sp.enableEndpointSlices,
+		TopologyPref:         sp.TopologyPref,
 	}
 
 	if port.enableEndpointSlices {
@@ -614,8 +631,9 @@ func (pp *portPublisher) addEndpointSlice(slice *discovery.EndpointSlice) {
 
 func (pp *portPublisher) updateEndpointSlice(oldSlice *discovery.EndpointSlice, newSlice *discovery.EndpointSlice) {
 	updatedAddressSet := AddressSet{
-		Addresses: make(map[ID]Address),
-		Labels:    pp.addresses.Labels,
+		Addresses:       make(map[ID]Address),
+		Labels:          pp.addresses.Labels,
+		TopologicalPref: pp.TopologyPref,
 	}
 
 	for id, address := range pp.addresses.Addresses {
@@ -715,6 +733,11 @@ func (pp *portPublisher) endpointSliceToAddresses(es *discovery.EndpointSlice) A
 				identity := es.Annotations[consts.RemoteGatewayIdentity]
 				address, id := pp.newServiceRefAddress(resolvedPort, IPAddr, serviceID.Name, es.Namespace)
 				address.Identity, address.AuthorityOverride = authorityOverride, identity
+
+				for k, v := range endpoint.Topology {
+					address.TopologyLabels[k] = v
+				}
+
 				addresses[id] = address
 			}
 
@@ -728,14 +751,21 @@ func (pp *portPublisher) endpointSliceToAddresses(es *discovery.EndpointSlice) A
 					pp.log.Errorf("Unable to create new address:%v", err)
 					continue
 				}
+
+				for k, v := range endpoint.Topology {
+					address.TopologyLabels[k] = v
+				}
+
 				addresses[id] = address
 			}
 		}
 
 	}
+
 	return AddressSet{
-		Addresses: addresses,
-		Labels:    metricLabels(es),
+		Addresses:       addresses,
+		Labels:          metricLabels(es),
+		TopologicalPref: pp.TopologyPref,
 	}
 }
 
@@ -773,8 +803,9 @@ func (pp *portPublisher) endpointsToAddresses(endpoints *corev1.Endpoints) Addre
 		}
 	}
 	return AddressSet{
-		Addresses: addresses,
-		Labels:    metricLabels(endpoints),
+		Addresses:       addresses,
+		Labels:          metricLabels(endpoints),
+		TopologicalPref: []string{},
 	}
 }
 
@@ -788,7 +819,7 @@ func (pp *portPublisher) newServiceRefAddress(endpointPort Port, endpointIP, ser
 		Namespace: serviceNamespace,
 	}
 
-	return Address{IP: endpointIP, Port: endpointPort}, id
+	return Address{IP: endpointIP, Port: endpointPort, TopologyLabels: make(map[string]string)}, id
 }
 
 func (pp *portPublisher) newPodRefAddress(endpointPort Port, endpointIP, podName, podNamespace string) (Address, PodID, error) {
@@ -802,11 +833,12 @@ func (pp *portPublisher) newPodRefAddress(endpointPort Port, endpointIP, podName
 	}
 	ownerKind, ownerName := pp.k8sAPI.GetOwnerKindAndName(pod, false)
 	addr := Address{
-		IP:        endpointIP,
-		Port:      endpointPort,
-		Pod:       pod,
-		OwnerName: ownerName,
-		OwnerKind: ownerKind,
+		IP:             endpointIP,
+		Port:           endpointPort,
+		Pod:            pod,
+		TopologyLabels: make(map[string]string),
+		OwnerName:      ownerName,
+		OwnerKind:      ownerKind,
 	}
 
 	return addr, id, nil
@@ -870,6 +902,22 @@ func (pp *portPublisher) updatePort(targetPort namedPort) {
 	}
 }
 
+// updateTopologyPreference is used when a service's topology preference changes. This method
+// propagates the changes to the portPublisher, the portPublisher's AddressSet and triggers
+// an (empty) update for all of its listeners to reflect the new preference changes.
+func (pp *portPublisher) updateTopologyPreference() {
+	pp.addresses.TopologicalPref = pp.TopologyPref
+
+	updatedAddrSet := AddressSet{
+		Addresses:       make(map[ID]Address),
+		Labels:          make(map[string]string),
+		TopologicalPref: pp.TopologyPref,
+	}
+	for _, listener := range pp.listeners {
+		listener.Add(updatedAddrSet)
+	}
+}
+
 func (pp *portPublisher) deleteEndpointSlice(es *discovery.EndpointSlice) {
 	addrSet := pp.endpointSliceToAddresses(es)
 	for id := range addrSet.Addresses {
@@ -993,13 +1041,15 @@ func diffAddresses(oldAddresses, newAddresses AddressSet) (add, remove AddressSe
 		}
 	}
 	add = AddressSet{
-		Addresses: addAddresses,
-		Labels:    newAddresses.Labels,
+		Addresses:       addAddresses,
+		Labels:          newAddresses.Labels,
+		TopologicalPref: newAddresses.TopologicalPref,
 	}
 	remove = AddressSet{
-		Addresses: removeAddresses,
+		Addresses:       removeAddresses,
+		TopologicalPref: newAddresses.TopologicalPref,
 	}
-	return
+	return add, remove
 }
 
 func getEndpointSliceServiceID(es *discovery.EndpointSlice) (ServiceID, error) {
diff --git a/controller/cmd/destination/main.go b/controller/cmd/destination/main.go
index 25e638bcf..13366bf27 100644
--- a/controller/cmd/destination/main.go
+++ b/controller/cmd/destination/main.go
@@ -27,6 +27,7 @@ func Main(args []string) {
 	enableH2Upgrade := cmd.Bool("enable-h2-upgrade", true, "Enable transparently upgraded HTTP2 connections among pods in the service mesh")
 	disableIdentity := cmd.Bool("disable-identity", false, "Disable identity configuration")
 	controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
+	enableEndpointSlices := cmd.Bool("enable-endpoint-slices", false, "Enable the usage of EndpointSlice informers and resources")
 
 	traceCollector := flags.AddTraceFlags(cmd)
 
@@ -73,14 +74,14 @@ func Main(args []string) {
 	if err != nil {
 		log.Fatalf("Failed to initialize K8s API Client: %s", err)
 	}
-	enableEndpointSlices := global.GetEndpointSliceEnabled()
+
 	err = pkgK8s.EndpointSliceAccess(k8Client)
-	if enableEndpointSlices && err != nil {
+	if *enableEndpointSlices && err != nil {
 		log.Fatalf("Failed to start with EndpointSlices enabled: %s", err)
 	}
 
 	var k8sAPI *k8s.API
-	if enableEndpointSlices {
+	if *enableEndpointSlices {
 		k8sAPI, err = k8s.InitializeAPI(
 			*kubeConfigPath, true,
 			k8s.Endpoint, k8s.ES, k8s.Pod, k8s.RS, k8s.Svc, k8s.SP, k8s.TS, k8s.Job,
@@ -100,7 +101,7 @@ func Main(args []string) {
 		*controllerNamespace,
 		trustDomain,
 		*enableH2Upgrade,
-		enableEndpointSlices,
+		*enableEndpointSlices,
 		k8sAPI,
 		clusterDomain,
 		done,
diff --git a/controller/gen/config/config.pb.go b/controller/gen/config/config.pb.go
index 6eb8d41b9..c12657d1e 100644
--- a/controller/gen/config/config.pb.go
+++ b/controller/gen/config/config.pb.go
@@ -104,8 +104,7 @@ type Global struct {
 	AutoInjectContext      *AutoInjectContext `protobuf:"bytes,6,opt,name=auto_inject_context,json=autoInjectContext,proto3" json:"auto_inject_context,omitempty"`
 	OmitWebhookSideEffects bool               `protobuf:"varint,7,opt,name=omitWebhookSideEffects,proto3" json:"omitWebhookSideEffects,omitempty"`
 	// Override default `cluster.local`
-	ClusterDomain        string `protobuf:"bytes,8,opt,name=cluster_domain,json=clusterDomain,proto3" json:"cluster_domain,omitempty"`
-	EndpointSliceEnabled bool   `protobuf:"varint,9,opt,name=endpoint_slice_enabled,json=endpointSliceEnabled,proto3" json:"endpoint_slice_enabled,omitempty"`
+	ClusterDomain string `protobuf:"bytes,8,opt,name=cluster_domain,json=clusterDomain,proto3" json:"cluster_domain,omitempty"`
 }
 
 func (x *Global) Reset() {
@@ -190,13 +189,6 @@ func (x *Global) GetClusterDomain() string {
 	return ""
 }
 
-func (x *Global) GetEndpointSliceEnabled() bool {
-	if x != nil {
-		return x.EndpointSliceEnabled
-	}
-	return false
-}
-
 type Proxy struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -914,7 +906,7 @@ var file_config_config_proto_rawDesc = []byte{
 	0x07, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
 	0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
 	0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x07, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c,
-	0x6c, 0x22, 0xaa, 0x03, 0x0a, 0x06, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x2b, 0x0a, 0x11,
+	0x6c, 0x22, 0xf4, 0x02, 0x0a, 0x06, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x12, 0x2b, 0x0a, 0x11,
 	0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63,
 	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
 	0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6e, 0x69,
@@ -937,135 +929,131 @@ var file_config_config_proto_rawDesc = []byte{
 	0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x53, 0x69, 0x64, 0x65, 0x45, 0x66, 0x66, 0x65, 0x63, 0x74,
 	0x73, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x64, 0x6f, 0x6d,
 	0x61, 0x69, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6c, 0x75, 0x73, 0x74,
-	0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x16, 0x65, 0x6e, 0x64, 0x70,
-	0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c,
-	0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,
-	0x6e, 0x74, 0x53, 0x6c, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0xec,
-	0x08, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x12, 0x37, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x78,
-	0x79, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,
-	0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
-	0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6d, 0x61, 0x67,
-	0x65, 0x12, 0x40, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x5f,
-	0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69,
-	0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6d,
-	0x61, 0x67, 0x65, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x69, 0x74, 0x49, 0x6d,
-	0x61, 0x67, 0x65, 0x12, 0x38, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70,
-	0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
-	0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74,
-	0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x4c, 0x0a,
-	0x14, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f,
-	0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69,
+	0x65, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0xec, 0x08, 0x0a, 0x05, 0x50, 0x72, 0x6f,
+	0x78, 0x79, 0x12, 0x37, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6d, 0x61, 0x67,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72,
+	0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52,
+	0x0a, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x40, 0x0a, 0x10, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,
+	0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0e, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x69, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x38, 0x0a,
+	0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74,
+	0x72, 0x6f, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x4c, 0x0a, 0x14, 0x69, 0x67, 0x6e, 0x6f, 0x72,
+	0x65, 0x5f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18,
+	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32,
+	0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67,
+	0x65, 0x52, 0x12, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
+	0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x4e, 0x0a, 0x15, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f,
+	0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x05,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65,
+	0x52, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64,
+	0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x0c, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
+	0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69,
 	0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f,
-	0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x12, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x49,
-	0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x4e, 0x0a, 0x15, 0x69,
-	0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70,
-	0x6f, 0x72, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6c, 0x69, 0x6e,
-	0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72,
-	0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x13, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4f, 0x75,
-	0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x12, 0x38, 0x0a, 0x0c, 0x69,
-	0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
-	0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x34, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x70,
-	0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
-	0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74,
-	0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x3a, 0x0a, 0x0d, 0x6f,
-	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f,
-	0x75, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x41, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x69, 0x6e, 0x6b,
-	0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x65, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73,
-	0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72,
-	0x6f, 0x78, 0x79, 0x5f, 0x75, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70,
-	0x72, 0x6f, 0x78, 0x79, 0x55, 0x69, 0x64, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c,
-	0x65, 0x76, 0x65, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e,
-	0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x6f, 0x67,
-	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12,
-	0x3a, 0x0a, 0x19, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x17, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70,
-	0x72, 0x6f, 0x78, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
-	0x12, 0x37, 0x0a, 0x18, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x5f, 0x69,
-	0x6d, 0x61, 0x67, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x49, 0x6e, 0x69, 0x74, 0x49, 0x6d, 0x61,
-	0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x0b, 0x64, 0x65, 0x62,
-	0x75, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
-	0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6d, 0x61,
-	0x67, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67,
-	0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x11, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69,
-	0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x11,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
-	0x6c, 0x6f, 0x67, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x09, 0x6c, 0x6f, 0x67, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x6f,
-	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6f,
-	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69,
-	0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x36, 0x0a, 0x17, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
-	0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74,
-	0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43,
-	0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x47, 0x0a,
-	0x05, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67,
-	0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x6f,
-	0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c,
-	0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x1a, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12,
-	0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f,
-	0x72, 0x74, 0x22, 0x2a, 0x0a, 0x09, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12,
-	0x1d, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x9e,
-	0x01, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x69,
-	0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x70, 0x75, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0d, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12,
-	0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x08, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x43, 0x70, 0x75, 0x12, 0x21, 0x0a, 0x0c,
-	0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22,
-	0x17, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e,
-	0x74, 0x65, 0x78, 0x74, 0x3a, 0x02, 0x18, 0x01, 0x22, 0x8d, 0x02, 0x0a, 0x0f, 0x49, 0x64, 0x65,
-	0x6e, 0x74, 0x69, 0x74, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x21, 0x0a, 0x0c,
-	0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x74, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
-	0x2a, 0x0a, 0x11, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x73,
-	0x5f, 0x70, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x72, 0x75, 0x73,
-	0x74, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x73, 0x50, 0x65, 0x6d, 0x12, 0x46, 0x0a, 0x11, 0x69,
-	0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x52, 0x10, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x74,
-	0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x14, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x6b, 0x65,
-	0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x63, 0x6c,
-	0x6f, 0x63, 0x6b, 0x53, 0x6b, 0x65, 0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x61, 0x6e, 0x63, 0x65,
-	0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c,
-	0x65, 0x76, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x97, 0x01, 0x0a, 0x07, 0x49,
-	0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x5f, 0x76, 0x65,
-	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69,
-	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73,
-	0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64,
-	0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
-	0x2e, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x1a, 0x30, 0x0a, 0x04,
-	0x46, 0x6c, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4a, 0x04,
-	0x08, 0x01, 0x10, 0x02, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
-	0x72, 0x64, 0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x67,
-	0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x72, 0x74, 0x52, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12,
+	0x34, 0x0a, 0x0a, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69,
+	0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x3a, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
+	0x64, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x6c,
+	0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50,
+	0x6f, 0x72, 0x74, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x6f, 0x72,
+	0x74, 0x12, 0x41, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x09, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65,
+	0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x75, 0x69,
+	0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x55, 0x69,
+	0x64, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0b,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52,
+	0x08, 0x6c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x3a, 0x0a, 0x19, 0x64, 0x69, 0x73,
+	0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72,
+	0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x64, 0x69,
+	0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x50, 0x72, 0x6f,
+	0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x76,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x18, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x76,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x49, 0x6e, 0x69, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x0b, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6d, 0x61,
+	0x67, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65,
+	0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65,
+	0x52, 0x0a, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x13,
+	0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x65, 0x62, 0x75, 0x67,
+	0x49, 0x6d, 0x61, 0x67, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x18,
+	0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x65, 0x74, 0x5f,
+	0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16,
+	0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x4e, 0x65,
+	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x5f, 0x66, 0x6f,
+	0x72, 0x6d, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x46,
+	0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x38, 0x0a, 0x18, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
+	0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75,
+	0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
+	0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12,
+	0x36, 0x0a, 0x17, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
+	0x63, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x15, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74,
+	0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x22, 0x47, 0x0a, 0x05, 0x49, 0x6d, 0x61, 0x67, 0x65,
+	0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12,
+	0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+	0x22, 0x1a, 0x0a, 0x04, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x2a, 0x0a, 0x09,
+	0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x6f, 0x72,
+	0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70,
+	0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x9e, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74,
+	0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x70, 0x75,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43,
+	0x70, 0x75, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6d, 0x65,
+	0x6d, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x69, 0x6d,
+	0x69, 0x74, 0x5f, 0x63, 0x70, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x69,
+	0x6d, 0x69, 0x74, 0x43, 0x70, 0x75, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f,
+	0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6c, 0x69,
+	0x6d, 0x69, 0x74, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x22, 0x17, 0x0a, 0x11, 0x41, 0x75, 0x74,
+	0x6f, 0x49, 0x6e, 0x6a, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x3a, 0x02,
+	0x18, 0x01, 0x22, 0x8d, 0x02, 0x0a, 0x0f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x43,
+	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f,
+	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x72,
+	0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x72, 0x75,
+	0x73, 0x74, 0x5f, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x73, 0x5f, 0x70, 0x65, 0x6d, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x72, 0x75, 0x73, 0x74, 0x41, 0x6e, 0x63, 0x68, 0x6f,
+	0x72, 0x73, 0x50, 0x65, 0x6d, 0x12, 0x46, 0x0a, 0x11, 0x69, 0x73, 0x73, 0x75, 0x61, 0x6e, 0x63,
+	0x65, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x69, 0x73, 0x73,
+	0x75, 0x61, 0x6e, 0x63, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a,
+	0x14, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x6b, 0x65, 0x77, 0x5f, 0x61, 0x6c, 0x6c, 0x6f,
+	0x77, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x12, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x6b, 0x65,
+	0x77, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63,
+	0x68, 0x65, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65,
+	0x6d, 0x65, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x14,
+	0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
+	0x65, 0x76, 0x65, 0x6c, 0x22, 0x97, 0x01, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
+	0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x12, 0x33, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1d, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x52,
+	0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x1a, 0x30, 0x0a, 0x04, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x42, 0x33,
+	0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x6e,
+	0x6b, 0x65, 0x72, 0x64, 0x2f, 0x6c, 0x69, 0x6e, 0x6b, 0x65, 0x72, 0x64, 0x32, 0x2f, 0x63, 0x6f,
+	0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index 02f2b3280..ae362f65c 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -23,6 +23,7 @@ import (
 	"github.com/linkerd/linkerd2/pkg/tls"
 	"github.com/linkerd/linkerd2/pkg/version"
 	log "github.com/sirupsen/logrus"
+	admissionRegistration "k8s.io/api/admissionregistration/v1beta1"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
@@ -1208,7 +1209,11 @@ func (hc *HealthChecker) allCategories() []category {
 					hintAnchor:  "l5d-injection-disabled",
 					warning:     true,
 					check: func(context.Context) error {
-						if hc.isHA() {
+						policy, err := hc.getMutatingWebhookFailurePolicy()
+						if err != nil {
+							return err
+						}
+						if policy != nil && *policy == admissionRegistration.Fail {
 							return hc.checkHAMetadataPresentOnKubeSystemNamespace()
 						}
 						return &SkipError{Reason: "not run for non HA installs"}
@@ -1667,6 +1672,17 @@ func (hc *HealthChecker) checkCustomResourceDefinitions(shouldExist bool) error
 	return checkResources("CustomResourceDefinitions", objects, []string{"serviceprofiles.linkerd.io"}, shouldExist)
 }
 
+func (hc *HealthChecker) getMutatingWebhookFailurePolicy() (*admissionRegistration.FailurePolicyType, error) {
+	mwc, err := hc.kubeAPI.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Get(k8s.ProxyInjectorWebhookConfigName, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+	if len(mwc.Webhooks) != 1 {
+		return nil, fmt.Errorf("expected 1 webhooks, found %d", len(mwc.Webhooks))
+	}
+	return mwc.Webhooks[0].FailurePolicy, nil
+}
+
 func (hc *HealthChecker) checkMutatingWebhookConfigurations(shouldExist bool) error {
 	options := metav1.ListOptions{
 		LabelSelector: hc.controlPlaneComponentsSelector(),
@@ -1885,7 +1901,7 @@ func (hc *HealthChecker) checkHAMetadataPresentOnKubeSystemNamespace() error {
 
 	val, ok := ns.Labels[k8s.AdmissionWebhookLabel]
 	if !ok || val != "disabled" {
-		return fmt.Errorf("kube-system namespace needs to have the label %s: disabled if HA mode is enabled", k8s.AdmissionWebhookLabel)
+		return fmt.Errorf("kube-system namespace needs to have the label %s: disabled if injector webhook failure policy is Fail", k8s.AdmissionWebhookLabel)
 	}
 
 	return nil
diff --git a/proto/config/config.proto b/proto/config/config.proto
index 63777abcb..db64e3c6e 100644
--- a/proto/config/config.proto
+++ b/proto/config/config.proto
@@ -28,7 +28,6 @@ message Global {
 
   // Override default `cluster.local`
   string cluster_domain = 8;
-  bool endpoint_slice_enabled = 9;
 }
 
 message Proxy {
diff --git a/web/app/js/components/BaseTable.jsx b/web/app/js/components/BaseTable.jsx
index 33573c46e..1418cd943 100644
--- a/web/app/js/components/BaseTable.jsx
+++ b/web/app/js/components/BaseTable.jsx
@@ -261,10 +261,10 @@ BaseTable.propTypes = {
     isNumeric: PropTypes.bool,
     render: PropTypes.func,
     sorter: PropTypes.func,
-    title: PropTypes.string,
+    title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
   })).isRequired,
   tableRows: PropTypes.arrayOf(PropTypes.shape({})),
-  title: PropTypes.string,
+  title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
 };
 
 BaseTable.defaultProps = {
diff --git a/web/app/js/components/CheckModal.jsx b/web/app/js/components/CheckModal.jsx
index 6edadd8ba..d284b2ac1 100644
--- a/web/app/js/components/CheckModal.jsx
+++ b/web/app/js/components/CheckModal.jsx
@@ -14,6 +14,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import SimpleChip from './util/Chip.jsx';
 import Slide from '@material-ui/core/Slide';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import withMobileDialog from '@material-ui/core/withMobileDialog';
 import { withStyles } from '@material-ui/core/styles';
@@ -254,7 +255,7 @@ class CheckModal extends React.Component {
               color="primary"
               disabled={running}
               onClick={this.runCheck}>
-              Run Linkerd Check
+              <Trans>buttonRunLinkerdCheck</Trans>
             </Button>
           </Grid>
         </Grid>
diff --git a/web/app/js/components/EdgesTable.jsx b/web/app/js/components/EdgesTable.jsx
index 6eb0735b7..10c86d493 100644
--- a/web/app/js/components/EdgesTable.jsx
+++ b/web/app/js/components/EdgesTable.jsx
@@ -3,6 +3,7 @@ import CheckCircleOutline from '@material-ui/icons/CheckCircleOutline';
 import PropTypes from 'prop-types';
 import React from 'react';
 import Tooltip from '@material-ui/core/Tooltip';
+import { Trans } from '@lingui/macro';
 import WarningIcon from '@material-ui/icons/Warning';
 import { directionColumn } from './util/TapUtils.jsx';
 import { processedEdgesPropType } from './util/EdgesUtils.jsx';
@@ -25,7 +26,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
       render: d => directionColumn(d.direction),
     },
     {
-      title: 'Namespace',
+      title: <Trans>columnTitleNamespace</Trans>,
       dataIndex: 'namespace',
       isNumeric: false,
       filter: d => d.namespace,
@@ -37,7 +38,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
       sorter: d => d.namespace,
     },
     {
-      title: 'Name',
+      title: <Trans>columnTitleName</Trans>,
       dataIndex: 'name',
       isNumeric: false,
       filter: d => d.name,
@@ -56,7 +57,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
       sorter: d => d.name,
     },
     {
-      title: 'Identity',
+      title: <Trans>columnTitleIdentity</Trans>,
       dataIndex: 'identity',
       isNumeric: false,
       filter: d => d.identity,
@@ -64,7 +65,7 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
       sorter: d => d.identity,
     },
     {
-      title: 'Secured',
+      title: <Trans>columnTitleSecured</Trans>,
       dataIndex: 'message',
       isNumeric: true,
       render: d => {
@@ -83,12 +84,12 @@ const edgesColumnDefinitions = (PrefixedLink, namespace, type, classes) => {
 };
 
 const generateEdgesTableTitle = edges => {
-  let title = 'Edges';
+  let title = <Trans>tableTitleEdgesEmpty</Trans>;
   if (edges.length > 0) {
     let identity = edges[0].direction === 'INBOUND' ? edges[0].serverId : edges[0].clientId;
     if (identity) {
       identity = `${identity.split('.')[0]}.${identity.split('.')[1]}`;
-      title = `${title} (Identity: ${identity})`;
+      title = <Trans>tableTitleEdgesWithIdentity {identity}</Trans>;
     }
   }
   return title;
diff --git a/web/app/js/components/EmptyCard.jsx b/web/app/js/components/EmptyCard.jsx
index 4adeb7062..b3e7b8fe6 100644
--- a/web/app/js/components/EmptyCard.jsx
+++ b/web/app/js/components/EmptyCard.jsx
@@ -17,7 +17,7 @@ const EmptyCard = ({ classes }) => {
     <Card className={classes.card} elevation={3}>
       <CardContent>
         <Typography>
-          <Trans>No data to display</Trans>
+          <Trans>NoDataToDisplayMsg</Trans>
         </Typography>
       </CardContent>
     </Card>
diff --git a/web/app/js/components/ErrorModal.jsx b/web/app/js/components/ErrorModal.jsx
index c649b7d37..bdf223dbd 100644
--- a/web/app/js/components/ErrorModal.jsx
+++ b/web/app/js/components/ErrorModal.jsx
@@ -10,6 +10,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import Switch from '@material-ui/core/Switch';
 import Tooltip from '@material-ui/core/Tooltip';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _each from 'lodash/each';
 import _get from 'lodash/get';
@@ -150,7 +151,9 @@ class ErrorModal extends React.Component {
 
     if (showInit) {
       return (
-        <Tooltip title="Pods are initializing"><CircularProgress size={20} thickness={4} /></Tooltip>
+        <Tooltip title={<Trans>podsAreInitializingMsg</Trans>}>
+          <CircularProgress size={20} thickness={4} />
+        </Tooltip>
       );
     } else {
       return (
diff --git a/web/app/js/components/ExpandableTable.jsx b/web/app/js/components/ExpandableTable.jsx
index 8244fed82..89af24afe 100644
--- a/web/app/js/components/ExpandableTable.jsx
+++ b/web/app/js/components/ExpandableTable.jsx
@@ -148,7 +148,7 @@ ExpandableTable.propTypes = {
   expandedRowRender: PropTypes.func.isRequired,
   tableClassName: PropTypes.string,
   tableColumns: PropTypes.arrayOf(PropTypes.shape({
-    title: PropTypes.string,
+    title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
     isNumeric: PropTypes.bool,
     render: PropTypes.func,
   })).isRequired,
diff --git a/web/app/js/components/Gateway.jsx b/web/app/js/components/Gateway.jsx
index f41548cb3..ddee3a936 100644
--- a/web/app/js/components/Gateway.jsx
+++ b/web/app/js/components/Gateway.jsx
@@ -4,6 +4,7 @@ import MetricsTable from './MetricsTable.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import Spinner from './util/Spinner.jsx';
+import { Trans } from '@lingui/macro';
 import _isEmpty from 'lodash/isEmpty';
 import { processGatewayResults } from './util/MetricUtils.jsx';
 import { withContext } from './util/AppContext.jsx';
@@ -102,7 +103,7 @@ class Gateways extends React.Component {
             {noMetrics ? null : (
               <div className="page-section">
                 <MetricsTable
-                  title="Gateways"
+                  title={<Trans>tableTitleGateways</Trans>}
                   resource="gateway"
                   metrics={metrics} />
               </div>
diff --git a/web/app/js/components/MeshedStatusTable.jsx b/web/app/js/components/MeshedStatusTable.jsx
index 4fb24b4ab..dcff295df 100644
--- a/web/app/js/components/MeshedStatusTable.jsx
+++ b/web/app/js/components/MeshedStatusTable.jsx
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import { StyledProgress } from './util/Progress.jsx';
 import Tooltip from '@material-ui/core/Tooltip';
+import { Trans } from '@lingui/macro';
 import _isEmpty from 'lodash/isEmpty';
 import { withContext } from './util/AppContext.jsx';
 
@@ -20,7 +21,7 @@ const getClassification = (meshedPodCount, failedPodCount) => {
 
 const namespacesColumns = PrefixedLink => [
   {
-    title: 'Namespace',
+    title: <Trans>columnTitleNamespace</Trans>,
     dataIndex: 'namespace',
     sorter: d => d.namespace,
     render: d => {
@@ -37,12 +38,12 @@ const namespacesColumns = PrefixedLink => [
     },
   },
   {
-    title: 'Meshed pods',
+    title: <Trans>columnTitleMeshedPods</Trans>,
     dataIndex: 'meshedPodsStr',
     isNumeric: true,
   },
   {
-    title: 'Meshed Status',
+    title: <Trans>columnTitleMeshedStatus</Trans>,
     key: 'meshification',
     render: row => {
       const percent = row.meshedPercent.get();
diff --git a/web/app/js/components/MetricsTable.jsx b/web/app/js/components/MetricsTable.jsx
index cf2bf7abd..8c8415023 100644
--- a/web/app/js/components/MetricsTable.jsx
+++ b/web/app/js/components/MetricsTable.jsx
@@ -7,6 +7,7 @@ import JaegerLink from './JaegerLink.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
+import { Trans } from '@lingui/macro';
 import _cloneDeep from 'lodash/cloneDeep';
 import _each from 'lodash/each';
 import _get from 'lodash/get';
@@ -16,21 +17,21 @@ import { withContext } from './util/AppContext.jsx';
 
 const tcpStatColumns = [
   {
-    title: 'Connections',
+    title: <Trans>columnTitleOpenConnections</Trans>,
     dataIndex: 'tcp.openConnections',
     isNumeric: true,
     render: d => metricToFormatter.NO_UNIT(d.tcp.openConnections),
     sorter: d => d.tcp.openConnections,
   },
   {
-    title: 'Read Bytes / sec',
+    title: <Trans>columnTitleReadRate</Trans>,
     dataIndex: 'tcp.readRate',
     isNumeric: true,
     render: d => metricToFormatter.BYTES(d.tcp.readRate),
     sorter: d => d.tcp.readRate,
   },
   {
-    title: 'Write Bytes / sec',
+    title: <Trans>columnTitleWriteRate</Trans>,
     dataIndex: 'tcp.writeRate',
     isNumeric: true,
     render: d => metricToFormatter.BYTES(d.tcp.writeRate),
@@ -40,35 +41,35 @@ const tcpStatColumns = [
 
 const httpStatColumns = [
   {
-    title: 'Success Rate',
+    title: <Trans>columnTitleSuccessRate</Trans>,
     dataIndex: 'successRate',
     isNumeric: true,
     render: d => <SuccessRateMiniChart sr={d.successRate} />,
     sorter: d => d.successRate,
   },
   {
-    title: 'RPS',
+    title: <Trans>columnTitleRPS</Trans>,
     dataIndex: 'requestRate',
     isNumeric: true,
     render: d => metricToFormatter.NO_UNIT(d.requestRate),
     sorter: d => d.requestRate,
   },
   {
-    title: 'P50 Latency',
+    title: <Trans>columnTitleP50Latency</Trans>,
     dataIndex: 'P50',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P50),
     sorter: d => d.P50,
   },
   {
-    title: 'P95 Latency',
+    title: <Trans>columnTitleP95Latency</Trans>,
     dataIndex: 'P95',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P95),
     sorter: d => d.P95,
   },
   {
-    title: 'P99 Latency',
+    title: <Trans>columnTitleP99Latency</Trans>,
     dataIndex: 'P99',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P99),
@@ -79,7 +80,7 @@ const httpStatColumns = [
 
 const trafficSplitDetailColumns = [
   {
-    title: 'Apex Service',
+    title: <Trans>columnTitleApexService</Trans>,
     dataIndex: 'apex',
     isNumeric: false,
     filter: d => !d.tsStats ? null : d.tsStats.apex,
@@ -87,7 +88,7 @@ const trafficSplitDetailColumns = [
     sorter: d => !d.tsStats ? null : d.tsStats.apex,
   },
   {
-    title: 'Leaf Service',
+    title: <Trans>columnTitleLeafService</Trans>,
     dataIndex: 'leaf',
     isNumeric: false,
     filter: d => !d.tsStats ? null : d.tsStats.leaf,
@@ -95,7 +96,7 @@ const trafficSplitDetailColumns = [
     sorter: d => !d.tsStats ? null : d.tsStats.leaf,
   },
   {
-    title: 'Weight',
+    title: <Trans>columnTitleWeight</Trans>,
     dataIndex: 'weight',
     isNumeric: true,
     filter: d => !d.tsStats ? null : d.tsStats.weight,
@@ -113,42 +114,42 @@ const trafficSplitDetailColumns = [
 
 const gatewayColumns = [
   {
-    title: 'ClusterName',
+    title: <Trans>columnTitleClusterName</Trans>,
     dataIndex: 'clusterName',
     isNumeric: false,
     render: d => !d.clusterName ? '---' : d.clusterName,
     sorter: d => !d.clusterName ? '---' : d.clusterName,
   },
   {
-    title: 'Alive',
+    title: <Trans>columnTitleAlive</Trans>,
     dataIndex: 'alive',
     isNumeric: false,
     render: d => !d.alive ? 'FALSE' : 'TRUE',
     sorter: d => d.alive,
   },
   {
-    title: 'Paired Services',
+    title: <Trans>columnTitlePairedServices</Trans>,
     dataIndex: 'pairedServices',
     isNumeric: false,
     render: d => d.pairedServices,
     sorter: d => d.pairedServices,
   },
   {
-    title: 'P50 Latency',
+    title: <Trans>columnTitleP50Latency</Trans>,
     dataIndex: 'P50',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P50),
     sorter: d => d.P50,
   },
   {
-    title: 'P95 Latency',
+    title: <Trans>columnTitleP95Latency</Trans>,
     dataIndex: 'P95',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P95),
     sorter: d => d.P95,
   },
   {
-    title: 'P99 Latency',
+    title: <Trans>columnTitleP99Latency</Trans>,
     dataIndex: 'P99',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.P99),
@@ -165,7 +166,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
 
   const nsColumn = [
     {
-      title: 'Namespace',
+      title: <Trans>columnTitleNamespace</Trans>,
       dataIndex: 'namespace',
       filter: d => d.namespace,
       isNumeric: false,
@@ -175,7 +176,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
   ];
 
   const meshedColumn = {
-    title: 'Meshed',
+    title: <Trans>columnTitleMeshed</Trans>,
     dataIndex: 'pods.totalPods',
     isNumeric: true,
     render: d => !d.pods ? null : `${d.pods.meshedPods}/${d.pods.totalPods}`,
@@ -183,7 +184,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
   };
 
   const grafanaColumn = {
-    title: 'Grafana',
+    title: <Trans>columnTitleGrafana</Trans>,
     key: 'grafanaDashboard',
     isNumeric: true,
     render: row => {
@@ -203,7 +204,7 @@ const columnDefinitions = (resource, showNamespaceColumn, showNameColumn, Prefix
 
 
   const jaegerColumn = {
-    title: 'Jaeger',
+    title: <Trans>columnTitleJaeger</Trans>,
     key: 'JaegerDashboard',
     isNumeric: true,
     render: row => {
@@ -327,7 +328,7 @@ MetricsTable.propTypes = {
   selectedNamespace: PropTypes.string.isRequired,
   showName: PropTypes.bool,
   showNamespaceColumn: PropTypes.bool,
-  title: PropTypes.string,
+  title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
   grafana: PropTypes.string,
   jaeger: PropTypes.string,
 };
diff --git a/web/app/js/components/Namespace.jsx b/web/app/js/components/Namespace.jsx
index 65f32e77c..be6392f80 100644
--- a/web/app/js/components/Namespace.jsx
+++ b/web/app/js/components/Namespace.jsx
@@ -7,6 +7,7 @@ import NetworkGraph from './NetworkGraph.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import Spinner from './util/Spinner.jsx';
+import { Trans } from '@lingui/macro';
 import _filter from 'lodash/filter';
 import _get from 'lodash/get';
 import _isEmpty from 'lodash/isEmpty';
@@ -147,7 +148,7 @@ class Namespaces extends React.Component {
         {!error ? null : <ErrorBanner message={error} />}
         {!loaded ? <Spinner /> : (
           <div>
-            {noMetrics ? <div>No resources detected.</div> : null}
+            {noMetrics ? <div><Trans>noResourcesDetectedMsg</Trans></div> : null}
             {
               _isEmpty(deploymentsWithMetrics) ? null :
               <NetworkGraph namespace={ns} deployments={metrics.deployment} />
@@ -166,7 +167,7 @@ class Namespaces extends React.Component {
               noMetrics ? null :
               <div className="page-section">
                 <MetricsTable
-                  title="TCP"
+                  title={<Trans>tableTitleTCP</Trans>}
                   resource="pod"
                   metrics={metrics.pod}
                   isTcpTable />
diff --git a/web/app/js/components/Navigation.jsx b/web/app/js/components/Navigation.jsx
index 91e0884cb..d6034d770 100644
--- a/web/app/js/components/Navigation.jsx
+++ b/web/app/js/components/Navigation.jsx
@@ -22,6 +22,7 @@ import React from 'react';
 import ReactRouterPropTypes from 'react-router-prop-types';
 import TextField from '@material-ui/core/TextField';
 import Toolbar from '@material-ui/core/Toolbar';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import Version from './Version.jsx';
 import _maxBy from 'lodash/maxBy';
@@ -450,15 +451,15 @@ class NavigationBase extends React.Component {
         <Divider />
         <MenuList>
           <Typography variant="button" component="div" className={classes.sidebarHeading}>
-            Cluster
+            <Trans>sidebarHeadingCluster</Trans>
           </Typography>
-          { this.menuItem('/namespaces', 'Namespaces', namespaceIcon) }
+          { this.menuItem('/namespaces', <Trans>menuItemNamespaces</Trans>, namespaceIcon) }
 
 
-          { this.menuItem('/controlplane', 'Control Plane',
+          { this.menuItem('/controlplane', <Trans>menuItemControlPlane</Trans>,
             <FontAwesomeIcon icon={faCloud} className={classes.shrinkCloudIcon} />) }
 
-          { this.menuItem('/gateways', 'Gateway',
+          { this.menuItem('/gateways', <Trans>menuItemGateway</Trans>,
             <FontAwesomeIcon icon={faDungeon} className={classes.shrinkIcon} />) }
 
         </MenuList>
@@ -500,48 +501,48 @@ class NavigationBase extends React.Component {
 
         <MenuList>
           <Typography variant="button" component="div" className={classes.sidebarHeading}>
-            Workloads
+            <Trans>sidebarHeadingWorkloads</Trans>
           </Typography>
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, 'Cron Jobs', cronJobIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/cronjobs`, <Trans>menuItemCronJobs</Trans>, cronJobIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, 'Daemon Sets', daemonsetIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/daemonsets`, <Trans>menuItemDaemonSets</Trans>, daemonsetIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/deployments`, 'Deployments', deploymentIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/deployments`, <Trans>menuItemDeployments</Trans>, deploymentIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/jobs`, 'Jobs', jobIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/jobs`, <Trans>menuItemJobs</Trans>, jobIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/pods`, 'Pods', podIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/pods`, <Trans>menuItemPods</Trans>, podIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, 'Replica Sets', replicaSetIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/replicasets`, <Trans>menuItemReplicaSets</Trans>, replicaSetIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, 'Replication Controllers', replicaSetIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/replicationcontrollers`, <Trans>menuItemReplicationControllers</Trans>, replicaSetIcon) }
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, 'Stateful Sets', statefulSetIcon) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/statefulsets`, <Trans>menuItemStatefulSets</Trans>, statefulSetIcon) }
         </MenuList>
 
         <MenuList>
           <Typography variant="button" component="div" className={classes.sidebarHeading}>
-            Configuration
+            <Trans>sidebarHeadingConfiguration</Trans>
           </Typography>
 
-          { this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, 'Traffic Splits', <FontAwesomeIcon icon={faFilter} className={classes.shrinkIcon} />) }
+          { this.menuItem(`/namespaces/${selectedNamespace}/trafficsplits`, <Trans>menuItemTrafficSplits</Trans>, <FontAwesomeIcon icon={faFilter} className={classes.shrinkIcon} />) }
 
         </MenuList>
         <Divider />
         <MenuList>
           <Typography variant="button" component="div" className={classes.sidebarHeading}>
-            Tools
+            <Trans>sidebarHeadingTools</Trans>
           </Typography>
 
-          { this.menuItem('/tap', 'Tap', <FontAwesomeIcon icon={faMicroscope} className={classes.shrinkIcon} />) }
-          { this.menuItem('/top', 'Top', <FontAwesomeIcon icon={faStream} className={classes.shrinkIcon} />) }
-          { this.menuItem('/routes', 'Routes', <FontAwesomeIcon icon={faRandom} className={classes.shrinkIcon} />) }
+          { this.menuItem('/tap', <Trans>menuItemTap</Trans>, <FontAwesomeIcon icon={faMicroscope} className={classes.shrinkIcon} />) }
+          { this.menuItem('/top', <Trans>menuItemTop</Trans>, <FontAwesomeIcon icon={faStream} className={classes.shrinkIcon} />) }
+          { this.menuItem('/routes', <Trans>menuItemRoutes</Trans>, <FontAwesomeIcon icon={faRandom} className={classes.shrinkIcon} />) }
 
         </MenuList>
         <Divider />
         <MenuList>
-          { this.menuItem('/community', 'Community',
+          { this.menuItem('/community', <Trans>menuItemCommunity</Trans>,
             <Badge
               classes={{ badge: classes.badge }}
               invisible={hideUpdateBadge}
@@ -551,25 +552,25 @@ class NavigationBase extends React.Component {
 
           <MenuItem component="a" href="https://linkerd.io/2/overview/" target="_blank" className={classes.navMenuItem}>
             <ListItemIcon><LibraryBooksIcon className={classes.shrinkIcon} /></ListItemIcon>
-            <ListItemText primary="Documentation" />
+            <ListItemText primary={<Trans>menuItemDocumentation</Trans>} />
             <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
           </MenuItem>
 
           <MenuItem component="a" href="https://github.com/linkerd/linkerd2/issues/new/choose" target="_blank" className={classes.navMenuItem}>
             <ListItemIcon>{githubIcon}</ListItemIcon>
-            <ListItemText primary="GitHub" />
+            <ListItemText primary={<Trans>menuItemGitHub</Trans>} />
             <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
           </MenuItem>
 
           <MenuItem component="a" href="https://lists.cncf.io/g/cncf-linkerd-users" target="_blank" className={classes.navMenuItem}>
             <ListItemIcon><EmailIcon className={classes.shrinkIcon} /></ListItemIcon>
-            <ListItemText primary="Mailing List" />
+            <ListItemText primary={<Trans>menuItemMailingList</Trans>} />
             <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
           </MenuItem>
 
           <MenuItem component="a" href="https://slack.linkerd.io" target="_blank" className={classes.navMenuItem}>
             <ListItemIcon>{slackIcon}</ListItemIcon>
-            <ListItemText primary="Slack" />
+            <ListItemText primary={<Trans>menuItemSlack</Trans>} />
             <FontAwesomeIcon icon={faExternalLinkAlt} className={classes.externalLinkIcon} size="xs" />
           </MenuItem>
 
diff --git a/web/app/js/components/NoMatch.jsx b/web/app/js/components/NoMatch.jsx
index b68fec913..1e2d0d19c 100644
--- a/web/app/js/components/NoMatch.jsx
+++ b/web/app/js/components/NoMatch.jsx
@@ -1,9 +1,14 @@
 import React from 'react';
+import { Trans } from '@lingui/macro';
 
 const NoMatch = () => (
   <div>
     <h3>404</h3>
-    <div>Page not found.</div>
+    <div>
+      <Trans>
+        404Msg
+      </Trans>
+    </div>
   </div>
 );
 
diff --git a/web/app/js/components/ResourceDetail.jsx b/web/app/js/components/ResourceDetail.jsx
index 24fe481a4..3b0edeb9e 100644
--- a/web/app/js/components/ResourceDetail.jsx
+++ b/web/app/js/components/ResourceDetail.jsx
@@ -14,6 +14,7 @@ import SimpleChip from './util/Chip.jsx';
 import Spinner from './util/Spinner.jsx';
 import TopRoutesTabs from './TopRoutesTabs.jsx';
 import TrafficSplitDetail from './TrafficSplitDetail.jsx';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _filter from 'lodash/filter';
 import _get from 'lodash/get';
@@ -402,14 +403,14 @@ export class ResourceDetailBase extends React.Component {
         {_isEmpty(upstreams) ? null :
         <MetricsTable
           resource="multi_resource"
-          title="Inbound"
+          title={<Trans>tableTitleInbound</Trans>}
           metrics={upstreamDisplayMetrics} />
         }
 
         {_isEmpty(downstreamDisplayMetrics) ? null :
         <MetricsTable
           resource="multi_resource"
-          title="Outbound"
+          title={<Trans>tableTitleOutbound</Trans>}
           metrics={downstreamDisplayMetrics} />
         }
 
@@ -417,13 +418,13 @@ export class ResourceDetailBase extends React.Component {
           resourceType === 'pod' || isTcpOnly ? null :
           <MetricsTable
             resource="pod"
-            title="Pods"
+            title={<Trans>tableTitlePods</Trans>}
             metrics={podMetrics} />
         }
 
         <MetricsTable
           resource="pod"
-          title="TCP"
+          title={<Trans>tableTitleTCP</Trans>}
           isTcpTable
           metrics={podMetrics} />
 
diff --git a/web/app/js/components/ResourceList.jsx b/web/app/js/components/ResourceList.jsx
index 89545d7b0..c1e48aacb 100644
--- a/web/app/js/components/ResourceList.jsx
+++ b/web/app/js/components/ResourceList.jsx
@@ -7,6 +7,7 @@ import MetricsTable from './MetricsTable.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import Spinner from './util/Spinner.jsx';
+import { Trans } from '@lingui/macro';
 import { apiErrorPropType } from './util/ApiHelpers.jsx';
 import withREST from './util/withREST.jsx';
 
@@ -33,14 +34,14 @@ export class ResourceListBase extends React.Component {
         <MetricsTable
           resource={resource}
           metrics={processedMetrics}
-          title="HTTP metrics" />
+          title={<Trans>tableTitleHTTPMetrics</Trans>} />
 
         {resource !== 'trafficsplit' &&
         <MetricsTable
           resource={resource}
           isTcpTable
           metrics={processedMetrics}
-          title="TCP metrics" />
+          title={<Trans>tableTitleTCPMetrics</Trans>} />
         }
       </React.Fragment>
     );
diff --git a/web/app/js/components/ServiceMesh.jsx b/web/app/js/components/ServiceMesh.jsx
index e3d445f44..92a28d1eb 100644
--- a/web/app/js/components/ServiceMesh.jsx
+++ b/web/app/js/components/ServiceMesh.jsx
@@ -13,6 +13,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import Spinner from './util/Spinner.jsx';
 import StatusTable from './StatusTable.jsx';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _compact from 'lodash/compact';
 import _countBy from 'lodash/countBy';
@@ -35,11 +36,11 @@ const styles = {
 
 const serviceMeshDetailsColumns = [
   {
-    title: 'Name',
+    title: <Trans>columnTitleName</Trans>,
     dataIndex: 'name',
   },
   {
-    title: 'Value',
+    title: <Trans>columnTitleValue</Trans>,
     dataIndex: 'value',
     isNumeric: true,
   },
@@ -107,9 +108,9 @@ class ServiceMesh extends React.Component {
 
     return [
       { key: 1, name: `${productName} version`, value: releaseVersion },
-      { key: 2, name: `${productName} namespace`, value: controllerNamespace },
-      { key: 3, name: 'Control plane components', value: components.length },
-      { key: 4, name: 'Data plane proxies', value: this.proxyCount() },
+      { key: 2, name: <Trans>{productName} namespace</Trans>, value: controllerNamespace },
+      { key: 3, name: <Trans>Control plane components</Trans>, value: components.length },
+      { key: 4, name: <Trans>Data plane proxies</Trans>, value: this.proxyCount() },
     ];
   }
 
@@ -225,7 +226,7 @@ class ServiceMesh extends React.Component {
             <Typography variant="h6">Control plane</Typography>
           </Grid>
           <Grid item xs={3}>
-            <Typography align="right">Components</Typography>
+            <Typography align="right"><Trans>componentsMsg</Trans></Typography>
             <Typography align="right">{components.length}</Typography>
           </Grid>
         </Grid>
diff --git a/web/app/js/components/StatusTable.jsx b/web/app/js/components/StatusTable.jsx
index 32ed39744..6ba5089db 100644
--- a/web/app/js/components/StatusTable.jsx
+++ b/web/app/js/components/StatusTable.jsx
@@ -2,6 +2,7 @@ import BaseTable from './BaseTable.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import Tooltip from '@material-ui/core/Tooltip';
+import { Trans } from '@lingui/macro';
 import _get from 'lodash/get';
 import _merge from 'lodash/merge';
 import classNames from 'classnames';
@@ -75,18 +76,18 @@ StatusDot.propTypes = {
 
 const columns = {
   resourceName: {
-    title: 'Deployment',
+    title: <Trans>columnTitleDeployment</Trans>,
     dataIndex: 'name',
   },
   pods: {
-    title: 'Pods',
+    title: <Trans>columnTitlePods</Trans>,
     key: 'numEntities',
     isNumeric: true,
     render: d => d.pods.length,
   },
   status: (name, classes) => {
     return {
-      title: name,
+      title: <Trans>columnTitlePodStatus</Trans>,
       key: 'status',
       render: d => {
         return d.pods.map(status => (
diff --git a/web/app/js/components/Tap.jsx b/web/app/js/components/Tap.jsx
index 15ccee807..b9cbbc30b 100644
--- a/web/app/js/components/Tap.jsx
+++ b/web/app/js/components/Tap.jsx
@@ -262,11 +262,12 @@ class Tap extends React.Component {
     });
 
     const url = this.api.urlsForResourceNoStats('all');
-    this.api.setCurrentRequests([this.api.fetchMetrics(url)]);
+    const authorityUrl = this.api.urlsForResource('authority');
+    this.api.setCurrentRequests([this.api.fetchMetrics(url), this.api.fetchMetrics(authorityUrl)]);
     this.serverPromise = Promise.all(this.api.getCurrentPromises())
-      .then(([rsp]) => {
-        const { resourcesByNs, authoritiesByNs } = groupResourcesByNs(rsp);
-
+      .then(rsp => {
+        const { resourcesByNs } = groupResourcesByNs(rsp[0]);
+        const { authoritiesByNs } = groupResourcesByNs(rsp[1]);
         this.setState({
           resourcesByNs,
           authoritiesByNs,
diff --git a/web/app/js/components/TapEventTable.jsx b/web/app/js/components/TapEventTable.jsx
index 3972fa598..e07c1b852 100644
--- a/web/app/js/components/TapEventTable.jsx
+++ b/web/app/js/components/TapEventTable.jsx
@@ -11,6 +11,7 @@ import ListItem from '@material-ui/core/ListItem';
 import ListItemText from '@material-ui/core/ListItemText';
 import PropTypes from 'prop-types';
 import React from 'react';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _get from 'lodash/get';
 import _isEmpty from 'lodash/isEmpty';
@@ -53,7 +54,7 @@ const formatTapLatency = str => {
 };
 
 const httpStatusCol = {
-  title: 'HTTP status',
+  title: <Trans>columnTitleHTTPStatus</Trans>,
   key: 'http-status',
   render: datum => {
     const d = _get(datum, 'responseInit.http.responseInit');
@@ -62,7 +63,7 @@ const httpStatusCol = {
 };
 
 const responseInitLatencyCol = {
-  title: 'Latency',
+  title: <Trans>columnTitleLatency</Trans>,
   key: 'rsp-latency',
   isNumeric: true,
   render: datum => {
@@ -72,7 +73,7 @@ const responseInitLatencyCol = {
 };
 
 const grpcStatusCol = {
-  title: 'GRPC status',
+  title: <Trans>columnTitleGRPCStatus</Trans>,
   key: 'grpc-status',
   render: datum => {
     const d = _get(datum, 'responseEnd.http.responseEnd');
@@ -82,7 +83,7 @@ const grpcStatusCol = {
 };
 
 const pathCol = {
-  title: 'Path',
+  title: <Trans>columnTitlePath</Trans>,
   key: 'path',
   render: datum => {
     const d = _get(datum, 'requestInit.http.requestInit');
@@ -91,7 +92,7 @@ const pathCol = {
 };
 
 const methodCol = {
-  title: 'Method',
+  title: <Trans>columnTitleMethod</Trans>,
   key: 'method',
   render: datum => {
     const d = _get(datum, 'requestInit.http.requestInit');
@@ -101,12 +102,12 @@ const methodCol = {
 
 const topLevelColumns = (resourceType, ResourceLink) => [
   {
-    title: 'Direction',
+    title: <Trans>columnTitleDirection</Trans>,
     key: 'direction',
     render: d => directionColumn(d.base.proxyDirection),
   },
   {
-    title: 'Name',
+    title: <Trans>columnTitleName</Trans>,
     key: 'src-dst',
     render: d => {
       const datum = {
diff --git a/web/app/js/components/TapQueryForm.jsx b/web/app/js/components/TapQueryForm.jsx
index 55173e646..e4d5e97dd 100644
--- a/web/app/js/components/TapQueryForm.jsx
+++ b/web/app/js/components/TapQueryForm.jsx
@@ -24,6 +24,7 @@ import QueryToCliCmd from './QueryToCliCmd.jsx';
 import React from 'react';
 import Select from '@material-ui/core/Select';
 import TextField from '@material-ui/core/TextField';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _flatten from 'lodash/flatten';
 import _isEmpty from 'lodash/isEmpty';
@@ -287,9 +288,17 @@ class TapQueryForm extends React.Component {
     const { handleTapStart, handleTapStop } = this.props;
 
     if (tapIsClosing) {
-      return (<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" disabled>Stop</Button>);
+      return (
+        <Button variant="outlined" color="primary" className="tap-ctrl tap-stop" disabled>
+          <Trans>buttonStop</Trans>
+        </Button>
+      );
     } else if (tapInProgress) {
-      return (<Button variant="outlined" color="primary" className="tap-ctrl tap-stop" onClick={handleTapStop}>Stop</Button>);
+      return (
+        <Button variant="outlined" color="primary" className="tap-ctrl tap-stop" onClick={handleTapStop}>
+          <Trans>buttonStop</Trans>
+        </Button>
+      );
     } else {
       return (
         <Button
@@ -298,7 +307,7 @@ class TapQueryForm extends React.Component {
           className="tap-ctrl tap-start"
           disabled={!query.namespace || !query.resource}
           onClick={handleTapStart}>
-          Start
+          <Trans>buttonStart</Trans>
         </Button>
       );
     }
@@ -330,7 +339,7 @@ class TapQueryForm extends React.Component {
         <Grid container spacing={3}>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
             <FormControl className={classes.formControl}>
-              {this.renderNamespaceSelect('To Namespace', 'toNamespace', 'toResource')}
+              {this.renderNamespaceSelect(<Trans>formToNamespace</Trans>, 'toNamespace', 'toResource')}
             </FormControl>
           </Grid>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
@@ -343,7 +352,7 @@ class TapQueryForm extends React.Component {
         <Grid container spacing={3}>
           <Grid item xs={6} md={3} classes={{ item: classes.formControlWrapper }}>
             <FormControl className={classes.formControl}>
-              <InputLabel htmlFor="authority">Authority</InputLabel>
+              <InputLabel htmlFor="authority"><Trans>formAuthority</Trans></InputLabel>
               <Select
                 value={query.authority}
                 onChange={this.handleFormChange('authority')}
@@ -359,20 +368,20 @@ class TapQueryForm extends React.Component {
             </FormControl>
           </Grid>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
-            { this.renderTextInput('Path', 'path', 'Display requests with paths that start with this prefix') }
+            { this.renderTextInput(<Trans>formPath</Trans>, 'path', 'Display requests with paths that start with this prefix') }
           </Grid>
         </Grid>
 
         <Grid container spacing={3}>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
-            { this.renderTextInput('Scheme', 'scheme', 'Display requests with this scheme') }
+            { this.renderTextInput(<Trans>formScheme</Trans>, 'scheme', 'Display requests with this scheme') }
           </Grid>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
-            { this.renderTextInput('Max RPS', 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
+            { this.renderTextInput(<Trans>formMaxRPS</Trans>, 'maxRps', `Maximum requests per second to tap. Default ${defaultMaxRps}`) }
           </Grid>
           <Grid item xs={6} md={3} className={classes.formControlWrapper}>
             <FormControl className={classes.formControl}>
-              <InputLabel htmlFor="method">HTTP method</InputLabel>
+              <InputLabel htmlFor="method"><Trans>formHTTPMethod</Trans></InputLabel>
               <Select
                 value={query.method}
                 onChange={this.handleFormChange('method')}
@@ -400,7 +409,7 @@ class TapQueryForm extends React.Component {
       <ExpansionPanel expanded={advancedFormExpanded} onChange={this.handleAdvancedFormExpandClick} elevation={3}>
         <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
           <Typography variant="caption" gutterBottom>
-            {advancedFormExpanded ? 'Hide filters' : 'Show more filters'}
+            {advancedFormExpanded ? <Trans>formHideFilters</Trans> : <Trans>formShowFilters</Trans>}
           </Typography>
         </ExpansionPanelSummary>
 
@@ -421,7 +430,7 @@ class TapQueryForm extends React.Component {
           <Grid container spacing={3}>
             <Grid item xs={6} md="auto" className={classes.formControlWrapper}>
               <FormControl className={classes.formControl} fullWidth>
-                {this.renderNamespaceSelect('Namespace', 'namespace', 'resource')}
+                {this.renderNamespaceSelect(<Trans>formNamespace</Trans>, 'namespace', 'resource')}
               </FormControl>
             </Grid>
 
@@ -433,7 +442,9 @@ class TapQueryForm extends React.Component {
 
             <Grid item>
               { this.renderTapButton(tapRequestInProgress, tapIsClosing) }
-              <Button onClick={this.resetTapForm} disabled={tapRequestInProgress} className={classes.resetButton}>Reset</Button>
+              <Button onClick={this.resetTapForm} disabled={tapRequestInProgress} className={classes.resetButton}>
+                <Trans>buttonReset</Trans>
+              </Button>
             </Grid>
           </Grid>
         </CardContent>
diff --git a/web/app/js/components/TopEventTable.jsx b/web/app/js/components/TopEventTable.jsx
index af262e71b..8638f2677 100644
--- a/web/app/js/components/TopEventTable.jsx
+++ b/web/app/js/components/TopEventTable.jsx
@@ -5,6 +5,7 @@ import BaseTable from './BaseTable.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
+import { Trans } from '@lingui/macro';
 import _isEmpty from 'lodash/isEmpty';
 import _isNil from 'lodash/isNil';
 import { withContext } from './util/AppContext.jsx';
@@ -16,7 +17,7 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [
     render: d => directionColumn(d.direction),
   },
   {
-    title: 'Name',
+    title: <Trans>columnTitleName</Trans>,
     filter: d => {
       const [labels, display] = extractDisplayName(d);
       return _isEmpty(labels[resourceType]) ?
@@ -27,33 +28,33 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [
     render: d => srcDstColumn(d, resourceType, ResourceLink),
   },
   {
-    title: 'Method',
+    title: <Trans>columnTitleMethod</Trans>,
     dataIndex: 'httpMethod',
     filter: d => d.httpMethod,
     sorter: d => d.httpMethod,
   },
   {
-    title: 'Path',
+    title: <Trans>columnTitlePath</Trans>,
     dataIndex: 'path',
     filter: d => d.path,
     sorter: d => d.path,
   },
   {
-    title: 'Count',
+    title: <Trans>columnTitleCount</Trans>,
     dataIndex: 'count',
     isNumeric: true,
     defaultSortOrder: 'desc',
     sorter: d => d.count,
   },
   {
-    title: 'Best',
+    title: <Trans>columnTitleBest</Trans>,
     dataIndex: 'best',
     isNumeric: true,
     render: d => formatLatencySec(d.best),
     sorter: d => d.best,
   },
   {
-    title: 'Worst',
+    title: <Trans>columnTitleWorst</Trans>,
     dataIndex: 'worst',
     isNumeric: true,
     defaultSortOrder: 'desc',
@@ -61,14 +62,14 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [
     sorter: d => d.worst,
   },
   {
-    title: 'Last',
+    title: <Trans>columnTitleLast</Trans>,
     dataIndex: 'last',
     isNumeric: true,
     render: d => formatLatencySec(d.last),
     sorter: d => d.last,
   },
   {
-    title: 'Success Rate',
+    title: <Trans>columnTitleSuccessRate</Trans>,
     dataIndex: 'successRate',
     isNumeric: true,
     render: d => _isNil(d) || _isNil(d.successRate) ? '---' :
@@ -76,7 +77,7 @@ const topColumns = (resourceType, ResourceLink, PrefixedLink) => [
     sorter: d => d.successRate.get(),
   },
   {
-    title: 'Tap',
+    title: <Trans>columnTitleTap</Trans>,
     key: 'tap',
     isNumeric: true,
     render: d => tapLink(d, resourceType, PrefixedLink),
diff --git a/web/app/js/components/TopRoutes.jsx b/web/app/js/components/TopRoutes.jsx
index 82705058b..f3fd5f6ce 100644
--- a/web/app/js/components/TopRoutes.jsx
+++ b/web/app/js/components/TopRoutes.jsx
@@ -17,6 +17,7 @@ import QueryToCliCmd from './QueryToCliCmd.jsx';
 import React from 'react';
 import Select from '@material-ui/core/Select';
 import TopRoutesModule from './TopRoutesModule.jsx';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _get from 'lodash/get';
 import _isEmpty from 'lodash/isEmpty';
@@ -232,11 +233,11 @@ class TopRoutes extends React.Component {
 
           <Grid item container spacing={4} alignItems="center" justify="flex-start">
             <Grid item>
-              { this.renderNamespaceDropdown('To Namespace', 'to_namespace', 'Namespace of target resource') }
+              { this.renderNamespaceDropdown(<Trans>formToNamespace</Trans>, 'to_namespace', 'Namespace of target resource') }
             </Grid>
 
             <Grid item>
-              { this.renderResourceDropdown('To Resource', 'to_name', 'to_type', 'Target resource') }
+              { this.renderResourceDropdown(<Trans>formToResource</Trans>, 'to_name', 'to_type', 'Target resource') }
             </Grid>
           </Grid>
         </Grid>
diff --git a/web/app/js/components/TrafficSplitDetail.jsx b/web/app/js/components/TrafficSplitDetail.jsx
index a24bd83c7..da9521769 100644
--- a/web/app/js/components/TrafficSplitDetail.jsx
+++ b/web/app/js/components/TrafficSplitDetail.jsx
@@ -3,6 +3,7 @@ import MetricsTable from './MetricsTable.jsx';
 import Octopus from './Octopus.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
+import { Trans } from '@lingui/macro';
 import Typography from '@material-ui/core/Typography';
 import _each from 'lodash/each';
 import _isNil from 'lodash/isNil';
@@ -73,7 +74,7 @@ const TrafficSplitDetail = ({ resourceMetrics, resourceName, resourceRsp, resour
         metrics={resourceMetrics}
         showName={false}
         showNamespaceColumn={false}
-        title="Leaf Services" />
+        title={<Trans>tableTitleLeafServices</Trans>} />
     </div>
   );
 };
diff --git a/web/app/js/components/util/CopyUtils.jsx b/web/app/js/components/util/CopyUtils.jsx
index b62d45e1b..dbb769ff1 100644
--- a/web/app/js/components/util/CopyUtils.jsx
+++ b/web/app/js/components/util/CopyUtils.jsx
@@ -6,7 +6,7 @@ import Typography from '@material-ui/core/Typography';
 * Instructions for adding resources to service mesh
 */
 export const incompleteMeshMessage = name => {
-  const unspecifiedResources = <Trans>one or more resources</Trans>;
+  const unspecifiedResources = <Trans>unspecifiedResourcesMsg</Trans>;
   const inject = <code>linkerd inject k8s.yml | kubectl apply -f -</code>;
 
   return (
diff --git a/web/app/js/locales/en/messages.json b/web/app/js/locales/en/messages.json
index 59ed9efd0..fac755632 100644
--- a/web/app/js/locales/en/messages.json
+++ b/web/app/js/locales/en/messages.json
@@ -1,5 +1,104 @@
 {
+  "404Msg": "Page not found.",
   "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh",
-  "No data to display": "No data to display",
-  "one or more resources": "one or more resources"
+  "All namespaces have a {productName} install.": "All namespaces have a {productName} install.",
+  "Control plane components": "Control plane components",
+  "Data plane proxies": "Data plane proxies",
+  "NoDataToDisplayMsg": "No data to display",
+  "buttonReset": "Reset",
+  "buttonRunLinkerdCheck": "Run Linkerd Check",
+  "buttonStart": "Start",
+  "buttonStop": "Stop",
+  "columnTitleAlive": "Alive",
+  "columnTitleApexService": "Apex Service",
+  "columnTitleBest": "Best",
+  "columnTitleClusterName": "Cluster Name",
+  "columnTitleCount": "Count",
+  "columnTitleDeployment": "Deployment",
+  "columnTitleDirection": "Direction",
+  "columnTitleGRPCStatus": "GRPC Status",
+  "columnTitleGrafana": "Grafana",
+  "columnTitleHTTPStatus": "HTTP Status",
+  "columnTitleIdentity": "Identity",
+  "columnTitleJaeger": "Jaeger",
+  "columnTitleLast": "Last",
+  "columnTitleLatency": "Latency",
+  "columnTitleLeafService": "Leaf Service",
+  "columnTitleMeshed": "Meshed",
+  "columnTitleMeshedPods": "Meshed Pods",
+  "columnTitleMeshedStatus": "Meshed Status",
+  "columnTitleMethod": "Method",
+  "columnTitleName": "Name",
+  "columnTitleNamespace": "Namespace",
+  "columnTitleOpenConnections": "Connections",
+  "columnTitleP50Latency": "P50 Latency",
+  "columnTitleP95Latency": "P95 Latency",
+  "columnTitleP99Latency": "P99 Latency",
+  "columnTitlePairedServices": "Paired Services",
+  "columnTitlePath": "Path",
+  "columnTitlePodStatus": "Pod Status",
+  "columnTitlePods": "Pods",
+  "columnTitleRPS": "RPS",
+  "columnTitleReadRate": "Read Bytes / sec",
+  "columnTitleRoute": "Route",
+  "columnTitleSecured": "Secured",
+  "columnTitleService": "Service",
+  "columnTitleSuccessRate": "Success Rate",
+  "columnTitleTap": "Tap",
+  "columnTitleValue": "Value",
+  "columnTitleWeight": "Weight",
+  "columnTitleWorst": "Worst",
+  "columnTitleWriteRate": "Write Bytes / sec",
+  "componentsMsg": "Components",
+  "formAuthority": "Authority",
+  "formHTTPMethod": "HTTP Method",
+  "formHideFilters": "Hide filters",
+  "formMaxRPS": "Max RPS",
+  "formNamespace": "Namespace",
+  "formPath": "Path",
+  "formScheme": "Scheme",
+  "formShowFilters": "Show more filters",
+  "formToNamespace": "To Namespace",
+  "formToResource": "To Resource",
+  "menuItemCommunity": "Community",
+  "menuItemControlPlane": "Control Plane",
+  "menuItemCronJobs": "Cron Jobs",
+  "menuItemDaemonSets": "Daemon Sets",
+  "menuItemDeployments": "Deployments",
+  "menuItemDocumentation": "Documentation",
+  "menuItemGateway": "Gateway",
+  "menuItemGitHub": "GitHub",
+  "menuItemJobs": "Jobs",
+  "menuItemMailingList": "Mailing List",
+  "menuItemNamespaces": "Namespaces",
+  "menuItemPods": "Pods",
+  "menuItemReplicaSets": "Replica Sets",
+  "menuItemReplicationControllers": "Replication Controllers",
+  "menuItemRoutes": "Routes",
+  "menuItemSlack": "Slack",
+  "menuItemStatefulSets": "Stateful Sets",
+  "menuItemTap": "Tap",
+  "menuItemTop": "Top",
+  "menuItemTrafficSplits": "Traffic Splits",
+  "noResourcesDetectedMsg": "No resources detected.",
+  "podsAreInitializingMsg": "Pods are initializing",
+  "sidebarHeadingCluster": "Cluster",
+  "sidebarHeadingConfiguration": "Configuration",
+  "sidebarHeadingTools": "Tools",
+  "sidebarHeadingWorkloads": "Workloads",
+  "tableTitleEdgesEmpty": "Edges",
+  "tableTitleEdgesWithIdentity {identity}": "Edges (Identity: {identity})",
+  "tableTitleGateways": "Gateways",
+  "tableTitleHTTPMetrics": "HTTP Metrics",
+  "tableTitleInbound": "Inbound",
+  "tableTitleLeafServices": "Leaf Services",
+  "tableTitleOutbound": "Outbound",
+  "tableTitlePods": "Pods",
+  "tableTitleTCP": "TCP",
+  "tableTitleTCPMetrics": "TCP Metrics",
+  "unspecifiedResourcesMsg": "one or more resources",
+  "{numUnadded} namespace has no meshed resources.": "{numUnadded} namespace has no meshed resources.",
+  "{numUnadded} namespaces have no meshed resources.": "{numUnadded} namespaces have no meshed resourcesß",
+  "{productName} namespace": "{productName} namespace",
+  "{productName} version": "{productName} version"
 }
diff --git a/web/app/js/locales/es/messages.json b/web/app/js/locales/es/messages.json
index a390f26d7..698825593 100644
--- a/web/app/js/locales/es/messages.json
+++ b/web/app/js/locales/es/messages.json
@@ -1,5 +1,104 @@
 {
-  "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agregue {0} a la k8s.yml file<0/><1/>Luego ejecuta {inject} para inyectarla en la malla de servicios",
-  "No data to display": "No hay datos que mostrar",
-  "one or more resources": "uno más recursos más"
+  "404Msg": "Página no encontrada.",
+  "Add {0} to the k8s.yml file<0/><1/>Then run {inject} to add it to the service mesh": "Agrega {0} al archivo k8s.yml<0/><1/>Luego ejecuta {inject} para inyectarlo en la malla de servicios",
+  "All namespaces have a {productName} install.": "Todos los namespaces tienen una instalación de {productName}.",
+  "Control plane components": "Componentes del plano de control",
+  "Data plane proxies": "Proxies del plano de datos",
+  "NoDataToDisplayMsg": "No hay datos que mostrar",
+  "buttonReset": "Restablecer",
+  "buttonRunLinkerdCheck": "Ejecutar Linkerd Check",
+  "buttonStart": "Empezar",
+  "buttonStop": "Terminar",
+  "columnTitleAlive": "Vivo",
+  "columnTitleApexService": "Servicio Apice",
+  "columnTitleBest": "Mejor",
+  "columnTitleClusterName": "Nombre del Clúster",
+  "columnTitleCount": "Total",
+  "columnTitleDeployment": "Deployment",
+  "columnTitleDirection": "Dirección",
+  "columnTitleGRPCStatus": "Estado GRPC",
+  "columnTitleGrafana": "Grafana",
+  "columnTitleHTTPStatus": "Estado HTTP",
+  "columnTitleIdentity": "Identidad",
+  "columnTitleJaeger": "Jaeger",
+  "columnTitleLast": "Último",
+  "columnTitleLatency": "Latencia",
+  "columnTitleLeafService": "Servicio Hoja",
+  "columnTitleMeshed": "En la malla de servicios",
+  "columnTitleMeshedPods": "Pods en la malla de servicios",
+  "columnTitleMeshedStatus": "Estado de la malla de servicios",
+  "columnTitleMethod": "Método",
+  "columnTitleName": "Nombre",
+  "columnTitleNamespace": "Namespace",
+  "columnTitleOpenConnections": "Conexiones",
+  "columnTitleP50Latency": "Latencia P50",
+  "columnTitleP95Latency": "Latencia P95",
+  "columnTitleP99Latency": "Latencia P99",
+  "columnTitlePairedServices": "Servicios Emparejados",
+  "columnTitlePath": "Ruta",
+  "columnTitlePodStatus": "Estado del Pod",
+  "columnTitlePods": "Pods",
+  "columnTitleRPS": "PPS",
+  "columnTitleReadRate": "Lectura Bytes / seg",
+  "columnTitleRoute": "Ruta",
+  "columnTitleSecured": "Asegurado",
+  "columnTitleService": "Servicio",
+  "columnTitleSuccessRate": "Tasa de éxito",
+  "columnTitleTap": "Tap",
+  "columnTitleValue": "Valor",
+  "columnTitleWeight": "Peso",
+  "columnTitleWorst": "Peor",
+  "columnTitleWriteRate": "Escritura Bytes / seg",
+  "componentsMsg": "Componentes",
+  "formAuthority": "Authority",
+  "formHTTPMethod": "Método HTTP",
+  "formHideFilters": "Ocultar filtros",
+  "formMaxRPS": "Max PPS",
+  "formNamespace": "Namespace",
+  "formPath": "Ruta",
+  "formScheme": "Esquema",
+  "formShowFilters": "Mostrar más filtros",
+  "formToNamespace": "Al Namespace",
+  "formToResource": "Al Recurso",
+  "menuItemCommunity": "Comunidad",
+  "menuItemControlPlane": "Plano de Control",
+  "menuItemCronJobs": "Cron Jobs",
+  "menuItemDaemonSets": "Daemon Sets",
+  "menuItemDeployments": "Deployments",
+  "menuItemDocumentation": "Documentación",
+  "menuItemGateway": "Gateway",
+  "menuItemGitHub": "GitHub",
+  "menuItemJobs": "Jobs",
+  "menuItemMailingList": "Lista de Correo",
+  "menuItemNamespaces": "Namespaces",
+  "menuItemPods": "Pods",
+  "menuItemReplicaSets": "Replica Sets",
+  "menuItemReplicationControllers": "Replication Controllers",
+  "menuItemRoutes": "Rutas",
+  "menuItemSlack": "Slack",
+  "menuItemStatefulSets": "Stateful Sets",
+  "menuItemTap": "Tap",
+  "menuItemTop": "Top",
+  "menuItemTrafficSplits": "Traffic Splits",
+  "noResourcesDetectedMsg": "No se han encontrado recursos.",
+  "podsAreInitializingMsg": "Los pods se están inicializando",
+  "sidebarHeadingCluster": "Clúster",
+  "sidebarHeadingConfiguration": "Configuración",
+  "sidebarHeadingTools": "Herramientas",
+  "sidebarHeadingWorkloads": "Cargas de trabajo",
+  "tableTitleEdgesEmpty": "Bordes",
+  "tableTitleEdgesWithIdentity {identity}": "Bordes (Identidad: {identity})",
+  "tableTitleGateways": "Gateways",
+  "tableTitleHTTPMetrics": "Métricas HTTP",
+  "tableTitleInbound": "Entrada",
+  "tableTitleLeafServices": "Servicios Hoja",
+  "tableTitleOutbound": "Salida",
+  "tableTitlePods": "Pods",
+  "tableTitleTCP": "TCP",
+  "tableTitleTCPMetrics": "Métricas TCP",
+  "unspecifiedResourcesMsg": "uno o más recursos",
+  "{numUnadded} namespace has no meshed resources.": "El namespace {numUnadded} no tiene recursos en la malla de servicios.",
+  "{numUnadded} namespaces have no meshed resources.": "Los namespaces {numUnadded} no tienen recursos en la malla de servicios.",
+  "{productName} namespace": "Namespace de {productName}",
+  "{productName} version": "Versión de {productName}"
 }
diff --git a/web/app/package.json b/web/app/package.json
index 07d91ced8..7f85fc6fa 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -82,7 +82,7 @@
     "url-loader": "4.1.0",
     "wdio-chromedriver-service": "6.0.2",
     "webdriverio": "6.1.2",
-    "webpack": "4.43.0",
+    "webpack": "4.44.1",
     "webpack-bundle-analyzer": "3.7.0",
     "webpack-cli": "3.3.11",
     "webpack-dev-server": "3.11.0"
diff --git a/web/app/yarn.lock b/web/app/yarn.lock
index 7eb07a8ae..65f867e46 100644
--- a/web/app/yarn.lock
+++ b/web/app/yarn.lock
@@ -2411,14 +2411,15 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3:
     define-properties "^1.1.3"
     es-abstract "^1.17.0-next.1"
 
-asn1.js@^4.0.0:
-  version "4.10.1"
-  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
-  integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
+asn1.js@^5.2.0:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
+  integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
   dependencies:
     bn.js "^4.0.0"
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
+    safer-buffer "^2.1.0"
 
 asn1@~0.2.3:
   version "0.2.4"
@@ -2794,11 +2795,16 @@ bluebird@^3.5.5:
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
   version "4.11.9"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
   integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
 
+bn.js@^5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
+  integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
+
 body-parser@1.19.0:
   version "1.19.0"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@@ -2916,7 +2922,7 @@ browserify-des@^1.0.0:
     inherits "^2.0.1"
     safe-buffer "^5.1.2"
 
-browserify-rsa@^4.0.0:
+browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
   integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
@@ -2925,17 +2931,19 @@ browserify-rsa@^4.0.0:
     randombytes "^2.0.1"
 
 browserify-sign@^4.0.0:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
-  integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=
-  dependencies:
-    bn.js "^4.1.1"
-    browserify-rsa "^4.0.0"
-    create-hash "^1.1.0"
-    create-hmac "^1.1.2"
-    elliptic "^6.0.0"
-    inherits "^2.0.1"
-    parse-asn1 "^5.0.0"
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
+  integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
+  dependencies:
+    bn.js "^5.1.1"
+    browserify-rsa "^4.0.1"
+    create-hash "^1.2.0"
+    create-hmac "^1.1.7"
+    elliptic "^6.5.3"
+    inherits "^2.0.4"
+    parse-asn1 "^5.1.5"
+    readable-stream "^3.6.0"
+    safe-buffer "^5.2.0"
 
 browserify-zlib@^0.2.0:
   version "0.2.0"
@@ -3284,6 +3292,21 @@ chokidar@^3.0.0:
   optionalDependencies:
     fsevents "~2.1.2"
 
+chokidar@^3.4.1:
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
+  integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.4.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 chownr@^1.1.1:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -3698,14 +3721,14 @@ crc@^3.4.4:
     buffer "^5.1.0"
 
 create-ecdh@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
-  integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
+  integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
   dependencies:
     bn.js "^4.1.0"
-    elliptic "^6.0.0"
+    elliptic "^6.5.3"
 
-create-hash@^1.1.0, create-hash@^1.1.2:
+create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
   integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
@@ -3716,7 +3739,7 @@ create-hash@^1.1.0, create-hash@^1.1.2:
     ripemd160 "^2.0.1"
     sha.js "^2.4.0"
 
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
+create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
   integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
@@ -4375,7 +4398,7 @@ electron-to-chromium@^1.3.413:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.414.tgz#9d0a92defefda7cc1cf8895058b892795ddd6b41"
   integrity sha512-UfxhIvED++qLwWrAq9uYVcqF8FdeV9sU2S7qhiHYFODxzXRrd1GZRl/PjITHsTEejgibcWDraD8TQqoHb1aCBQ==
 
-elliptic@^6.0.0:
+elliptic@^6.5.3:
   version "6.5.3"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
   integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
@@ -4436,10 +4459,10 @@ enhanced-resolve@4.1.0:
     memory-fs "^0.4.0"
     tapable "^1.0.0"
 
-enhanced-resolve@^4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
-  integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
+enhanced-resolve@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126"
+  integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==
   dependencies:
     graceful-fs "^4.1.2"
     memory-fs "^0.5.0"
@@ -5699,12 +5722,13 @@ has@^1.0.3:
     function-bind "^1.1.1"
 
 hash-base@^3.0.0:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
-  integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
+  integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
   dependencies:
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
+    inherits "^2.0.4"
+    readable-stream "^3.6.0"
+    safe-buffer "^5.2.0"
 
 hash-sum@^1.0.2:
   version "1.0.2"
@@ -8171,7 +8195,12 @@ negotiator@0.6.2:
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
   integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
-neo-async@^2.5.0, neo-async@^2.6.1:
+neo-async@^2.5.0:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
+neo-async@^2.6.1:
   version "2.6.1"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
   integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
@@ -8690,14 +8719,13 @@ parent-module@^1.0.0:
   dependencies:
     callsites "^3.0.0"
 
-parse-asn1@^5.0.0:
-  version "5.1.5"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
-  integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
+parse-asn1@^5.0.0, parse-asn1@^5.1.5:
+  version "5.1.6"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
+  integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
   dependencies:
-    asn1.js "^4.0.0"
+    asn1.js "^5.2.0"
     browserify-aes "^1.0.0"
-    create-hash "^1.1.0"
     evp_bytestokey "^1.0.0"
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
@@ -8873,9 +8901,9 @@ pathval@^1.1.0:
   integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
 
 pbkdf2@^3.0.3:
-  version "3.0.17"
-  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"
-  integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
+  integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==
   dependencies:
     create-hash "^1.1.2"
     create-hmac "^1.1.4"
@@ -9317,7 +9345,7 @@ randexp@0.4.6:
     discontinuous-range "1.0.0"
     ret "~0.1.10"
 
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
+randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
   integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
@@ -9526,7 +9554,7 @@ read-pkg@^5.2.0:
     string_decoder "~1.1.1"
     util-deprecate "~1.0.1"
 
-readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0:
+readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
   integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
@@ -9558,6 +9586,13 @@ readdirp@~3.3.0:
   dependencies:
     picomatch "^2.0.7"
 
+readdirp@~3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
+  integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==
+  dependencies:
+    picomatch "^2.2.1"
+
 realpath-native@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
@@ -9949,12 +9984,12 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@>=5.1.0:
+safe-buffer@>=5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
   integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
@@ -10076,10 +10111,12 @@ serialize-error@^6.0.0:
   dependencies:
     type-fest "^0.12.0"
 
-serialize-javascript@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
-  integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
+serialize-javascript@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
+  integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
+  dependencies:
+    randombytes "^2.1.0"
 
 serialize-query-params@^1.1.0:
   version "1.1.0"
@@ -10768,21 +10805,30 @@ terminal-link@^2.0.0:
     supports-hyperlinks "^2.0.0"
 
 terser-webpack-plugin@^1.4.3:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c"
-  integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
+  version "1.4.5"
+  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b"
+  integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==
   dependencies:
     cacache "^12.0.2"
     find-cache-dir "^2.1.0"
     is-wsl "^1.1.0"
     schema-utils "^1.0.0"
-    serialize-javascript "^2.1.2"
+    serialize-javascript "^4.0.0"
     source-map "^0.6.1"
     terser "^4.1.2"
     webpack-sources "^1.4.0"
     worker-farm "^1.7.0"
 
-terser@^4.1.2, terser@^4.6.3:
+terser@^4.1.2:
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
+  integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
+  dependencies:
+    commander "^2.20.0"
+    source-map "~0.6.1"
+    source-map-support "~0.5.12"
+
+terser@^4.6.3:
   version "4.6.11"
   resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
   integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
@@ -11327,14 +11373,23 @@ walker@^1.0.7, walker@~1.0.5:
   dependencies:
     makeerror "1.0.x"
 
-watchpack@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2"
-  integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==
+watchpack-chokidar2@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"
+  integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==
   dependencies:
     chokidar "^2.1.8"
+
+watchpack@^1.7.4:
+  version "1.7.4"
+  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b"
+  integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==
+  dependencies:
     graceful-fs "^4.1.2"
     neo-async "^2.5.0"
+  optionalDependencies:
+    chokidar "^3.4.1"
+    watchpack-chokidar2 "^2.0.0"
 
 wbuf@^1.1.0, wbuf@^1.7.3:
   version "1.7.3"
@@ -11496,10 +11551,10 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
-webpack@4.43.0:
-  version "4.43.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6"
-  integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==
+webpack@4.44.1:
+  version "4.44.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21"
+  integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==
   dependencies:
     "@webassemblyjs/ast" "1.9.0"
     "@webassemblyjs/helper-module-context" "1.9.0"
@@ -11509,7 +11564,7 @@ webpack@4.43.0:
     ajv "^6.10.2"
     ajv-keywords "^3.4.1"
     chrome-trace-event "^1.0.2"
-    enhanced-resolve "^4.1.0"
+    enhanced-resolve "^4.3.0"
     eslint-scope "^4.0.3"
     json-parse-better-errors "^1.0.2"
     loader-runner "^2.4.0"
@@ -11522,7 +11577,7 @@ webpack@4.43.0:
     schema-utils "^1.0.0"
     tapable "^1.1.3"
     terser-webpack-plugin "^1.4.3"
-    watchpack "^1.6.1"
+    watchpack "^1.7.4"
     webpack-sources "^1.4.1"
 
 websocket-driver@0.6.5:


Test patch:
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index f8049d22d..d16de49db 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -18,10 +18,7 @@ jobs:
       # actions/checkout@v2
       uses: actions/checkout@722adc6
     - name: Go unit tests
-      env:
-        GITCOOKIE_SH: ${{ secrets.GITCOOKIE_SH }}
       run: |
-        echo "$GITCOOKIE_SH" | bash
         go get gotest.tools/gotestsum@v0.4.2
         # TODO: validate bin/protoc-go.sh does not dirty the repo
         gotestsum -- -cover -race -v -mod=readonly ./...
@@ -40,5 +37,5 @@ jobs:
       run: |
         export PATH="$HOME/.yarn/bin:$PATH"
         export NODE_ENV=test
-        bin/web
+        bin/web --frozen-lockfile
         bin/web test --reporters="jest-dot-reporter" --reporters="./gh_ann_reporter.js"
diff --git a/cli/cmd/testdata/install_addon_config.golden b/cli/cmd/testdata/install_addon_config.golden
index b3ab9c306..0441aaf3a 100644
--- a/cli/cmd/testdata/install_addon_config.golden
+++ b/cli/cmd/testdata/install_addon_config.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
diff --git a/cli/cmd/testdata/install_addon_control-plane.golden b/cli/cmd/testdata/install_addon_control-plane.golden
index a0361be1f..71f21df2d 100644
--- a/cli/cmd/testdata/install_addon_control-plane.golden
+++ b/cli/cmd/testdata/install_addon_control-plane.golden
@@ -11,7 +11,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -568,6 +568,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_config.golden b/cli/cmd/testdata/install_config.golden
index 47661e50d..1a2ebadc0 100644
--- a/cli/cmd/testdata/install_config.golden
+++ b/cli/cmd/testdata/install_config.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
diff --git a/cli/cmd/testdata/install_control-plane.golden b/cli/cmd/testdata/install_control-plane.golden
index 594bf1dcb..83bf9cd13 100644
--- a/cli/cmd/testdata/install_control-plane.golden
+++ b/cli/cmd/testdata/install_control-plane.golden
@@ -11,7 +11,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -568,6 +568,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_controlplane_tracing_output.golden b/cli/cmd/testdata/install_controlplane_tracing_output.golden
index ac510bd24..06f82c5ef 100644
--- a/cli/cmd/testdata/install_controlplane_tracing_output.golden
+++ b/cli/cmd/testdata/install_controlplane_tracing_output.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1387,6 +1387,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         - -trace-collector=linkerd-collector.linkerd.svc.cluster.local:55678
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
diff --git a/cli/cmd/testdata/install_custom_registry.golden b/cli/cmd/testdata/install_custom_registry.golden
index c6f9d9d21..4a62ead21 100644
--- a/cli/cmd/testdata/install_custom_registry.golden
+++ b/cli/cmd/testdata/install_custom_registry.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"my.custom.registry/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"my.custom.registry/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"my.custom.registry/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         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 707a4327c..cdc7f1fa3 100644
--- a/cli/cmd/testdata/install_default.golden
+++ b/cli/cmd/testdata/install_default.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 a9d300087..89377424c 100644
--- a/cli/cmd/testdata/install_default_override_dst_get_nets.golden
+++ b/cli/cmd/testdata/install_default_override_dst_get_nets.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.0.0.0/8","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_grafana_existing.golden b/cli/cmd/testdata/install_grafana_existing.golden
index 2e1b49a88..36c2fb3f2 100644
--- a/cli/cmd/testdata/install_grafana_existing.golden
+++ b/cli/cmd/testdata/install_grafana_existing.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -797,7 +797,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1354,6 +1354,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 7bb9e5583..18bfff61c 100644
--- a/cli/cmd/testdata/install_ha_output.golden
+++ b/cli/cmd/testdata/install_ha_output.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"100m","requestMemory":"20Mi","limitCpu":"1","limitMemory":"250Mi"},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1452,6 +1452,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 4c2346194..a18a6bf93 100644
--- a/cli/cmd/testdata/install_ha_with_overrides_output.golden
+++ b/cli/cmd/testdata/install_ha_with_overrides_output.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"400m","requestMemory":"300Mi","limitCpu":"1","limitMemory":"250Mi"},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1452,6 +1452,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 8bd72dd20..b8b57d7af 100644
--- a/cli/cmd/testdata/install_heartbeat_disabled_output.golden
+++ b/cli/cmd/testdata/install_heartbeat_disabled_output.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -756,7 +756,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1313,6 +1313,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 eab5f7c95..7f7651bdf 100644
--- a/cli/cmd/testdata/install_helm_output.golden
+++ b/cli/cmd/testdata/install_helm_output.golden
@@ -141,7 +141,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -841,8 +841,7 @@ data:
       },
       "autoInjectContext": null,
       "omitWebhookSideEffects": false,
-      "clusterDomain": "cluster.local",
-      "enableEndpointSlices": "false"
+      "clusterDomain": "cluster.local"
     }
   proxy: |
     {
@@ -1436,6 +1435,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:linkerd-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_helm_output_addons.golden b/cli/cmd/testdata/install_helm_output_addons.golden
index 1e1e67f58..e82d66bbe 100644
--- a/cli/cmd/testdata/install_helm_output_addons.golden
+++ b/cli/cmd/testdata/install_helm_output_addons.golden
@@ -141,7 +141,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -841,8 +841,7 @@ data:
       },
       "autoInjectContext": null,
       "omitWebhookSideEffects": false,
-      "clusterDomain": "cluster.local",
-      "enableEndpointSlices": "false"
+      "clusterDomain": "cluster.local"
     }
   proxy: |
     {
@@ -1436,6 +1435,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 d3dadd5a3..6baa46fe4 100644
--- a/cli/cmd/testdata/install_helm_output_ha.golden
+++ b/cli/cmd/testdata/install_helm_output_ha.golden
@@ -141,7 +141,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -841,8 +841,7 @@ data:
       },
       "autoInjectContext": null,
       "omitWebhookSideEffects": false,
-      "clusterDomain": "cluster.local",
-      "enableEndpointSlices": "false"
+      "clusterDomain": "cluster.local"
     }
   proxy: |
     {
@@ -1531,6 +1530,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 8ed1fc04e..da937975d 100644
--- a/cli/cmd/testdata/install_no_init_container.golden
+++ b/cli/cmd/testdata/install_no_init_container.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -797,7 +797,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":true,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":true,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1278,6 +1278,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 970839f68..db9dc57b1 100644
--- a/cli/cmd/testdata/install_output.golden
+++ b/cli/cmd/testdata/install_output.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -1347,6 +1347,7 @@ spec:
         - -controller-namespace=Namespace
         - -enable-h2-upgrade=true
         - -log-level=ControllerLogLevel
+        - -enable-endpoint-slices=false
         image: ControllerImage:ControllerImageVersion
         imagePullPolicy: ImagePullPolicy
         livenessProbe:
diff --git a/cli/cmd/testdata/install_prometheus_overwrite.golden b/cli/cmd/testdata/install_prometheus_overwrite.golden
index 169e0cea4..5af65770c 100644
--- a/cli/cmd/testdata/install_prometheus_overwrite.golden
+++ b/cli/cmd/testdata/install_prometheus_overwrite.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_proxy_ignores.golden b/cli/cmd/testdata/install_proxy_ignores.golden
index 0be71ad9b..e5c8057a0 100644
--- a/cli/cmd/testdata/install_proxy_ignores.golden
+++ b/cli/cmd/testdata/install_proxy_ignores.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[{"portRange":"22"},{"portRange":"8100-8102"}],"ignoreOutboundPorts":[{"portRange":"5432"}],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_restricted_dashboard.golden b/cli/cmd/testdata/install_restricted_dashboard.golden
index 91c82964d..eeaa7d75a 100644
--- a/cli/cmd/testdata/install_restricted_dashboard.golden
+++ b/cli/cmd/testdata/install_restricted_dashboard.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -732,7 +732,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1289,6 +1289,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_tracing.golden b/cli/cmd/testdata/install_tracing.golden
index f4bf34a9f..7427d420b 100644
--- a/cli/cmd/testdata/install_tracing.golden
+++ b/cli/cmd/testdata/install_tracing.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/controller:install-control-plane-version
         imagePullPolicy: IfNotPresent
         livenessProbe:
diff --git a/cli/cmd/testdata/install_tracing_overwrite.golden b/cli/cmd/testdata/install_tracing_overwrite.golden
index af33fc7cf..8f6ce5f7a 100644
--- a/cli/cmd/testdata/install_tracing_overwrite.golden
+++ b/cli/cmd/testdata/install_tracing_overwrite.golden
@@ -133,7 +133,7 @@ rules:
   resources: ["jobs"]
   verbs: ["list", "get", "watch"]
 - apiGroups: [""]
-  resources: ["pods", "endpoints", "services"]
+  resources: ["pods", "endpoints", "services", "nodes"]
   verbs: ["list", "get", "watch"]
 - apiGroups: ["linkerd.io"]
   resources: ["serviceprofiles"]
@@ -800,7 +800,7 @@ metadata:
     linkerd.io/created-by: linkerd/cli dev-undefined
 data:
   global: |
-    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local","endpointSliceEnabled":false}
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"-----BEGIN CERTIFICATE-----\nMIIBYDCCAQegAwIBAgIBATAKBggqhkjOPQQDAjAYMRYwFAYDVQQDEw1jbHVzdGVy\nLmxvY2FsMB4XDTE5MDMwMzAxNTk1MloXDTI5MDIyODAyMDM1MlowGDEWMBQGA1UE\nAxMNY2x1c3Rlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAChpAt0\nxtgO9qbVtEtDK80N6iCL2Htyf2kIv2m5QkJ1y0TFQi5hTVe3wtspJ8YpZF0pl364\n6TiYeXB8tOOhIACjQjBAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHSUEFjAUBggrBgEF\nBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBE\nAiBQ/AAwF8kG8VOmRSUTPakSSa/N4mqK2HsZuhQXCmiZHwIgZEzI5DCkpU7w3SIv\nOLO4Zsk1XrGZHGsmyiEyvYF9lpY=\n-----END CERTIFICATE-----\n","issuanceLifetime":"86400s","clockSkewAllowance":"20s","scheme":"linkerd.io/tls"},"autoInjectContext":null,"omitWebhookSideEffects":false,"clusterDomain":"cluster.local"}
   proxy: |
     {"proxyImage":{"imageName":"gcr.io/linkerd-io/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"gcr.io/linkerd-io/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxyInitImageVersion":"v1.3.6","debugImage":{"imageName":"gcr.io/linkerd-io/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version","destinationGetNetworks":"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16","logFormat":"plain","outboundConnectTimeout":"","inboundConnectTimeout":""}
   install: |
@@ -1357,6 +1357,7 @@ spec:
         - -controller-namespace=linkerd
         - -enable-h2-upgrade=true
         - -log-level=info
+        - -enable-endpoint-slices=false
         image: gcr.io/linkerd-io/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 0ec60d527..d032942c2 100644
--- a/controller/api/destination/endpoint_translator_test.go
+++ b/controller/api/destination/endpoint_translator_test.go
@@ -10,6 +10,7 @@ import (
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
 	"github.com/linkerd/linkerd2-proxy-api/go/net"
 	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
+	pkgk8s "github.com/linkerd/linkerd2/controller/k8s"
 	"github.com/linkerd/linkerd2/pkg/addr"
 	"github.com/linkerd/linkerd2/pkg/k8s"
 	logging "github.com/sirupsen/logrus"
@@ -115,12 +116,37 @@ var (
 )
 
 func makeEndpointTranslator(t *testing.T) (*mockDestinationGetServer, *endpointTranslator) {
+	k8sAPI, err := pkgk8s.NewFakeAPI(`
+apiVersion: v1
+kind: Node
+metadata:
+  annotations:
+    kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
+    node.alpha.kubernetes.io/ttl: "0"
+  labels:
+    beta.kubernetes.io/arch: amd64
+    beta.kubernetes.io/os: linux
+    kubernetes.io/arch: amd64
+    kubernetes.io/hostname: kind-worker
+    kubernetes.io/os: linux
+    topology.kubernetes.io/region: west
+    topology.kubernetes.io/zone: west-1a
+  name: test-123
+`,
+	)
+	if err != nil {
+		t.Fatalf("NewFakeAPI returned an error: %s", err)
+	}
+	k8sAPI.Sync(nil)
+
 	mockGetServer := &mockDestinationGetServer{updatesReceived: []*pb.Update{}}
 	translator := newEndpointTranslator(
 		"linkerd",
 		"trust.domain",
 		true,
 		"service-name.service-ns",
+		"test-123",
+		k8sAPI.Client,
 		mockGetServer,
 		logging.WithField("test", t.Name()),
 	)
@@ -131,7 +157,7 @@ func TestEndpointTranslatorForRemoteGateways(t *testing.T) {
 	t.Run("Sends one update for add and another for remove", func(t *testing.T) {
 		mockGetServer, translator := makeEndpointTranslator(t)
 
-		translator.Add(mkAddressSetForServices(remoteGatewayWithNoTLS))
+		translator.Add(mkAddressSetForServices(remoteGatewayWithNoTLS, remoteGatewayWithTLS))
 		translator.Remove(mkAddressSetForServices(remoteGatewayWithTLS))
 
 		expectedNumUpdates := 2
@@ -236,7 +262,7 @@ func TestEndpointTranslatorForPods(t *testing.T) {
 	t.Run("Sends one update for add and another for remove", func(t *testing.T) {
 		mockGetServer, translator := makeEndpointTranslator(t)
 
-		translator.Add(mkAddressSetForPods(normalPod))
+		translator.Add(mkAddressSetForPods(normalPod, tlsOptionalPod))
 		translator.Remove(mkAddressSetForPods(tlsOptionalPod))
 
 		expectedNumUpdates := 2
@@ -249,12 +275,12 @@ func TestEndpointTranslatorForPods(t *testing.T) {
 	t.Run("Sends addresses as removed or added", func(t *testing.T) {
 		mockGetServer, translator := makeEndpointTranslator(t)
 
-		translator.Add(mkAddressSetForPods(normalPod, tlsOptionalPod))
+		translator.Add(mkAddressSetForPods(normalPod, tlsOptionalPod, tlsDisabledPod))
 		translator.Remove(mkAddressSetForPods(tlsDisabledPod))
 
 		addressesAdded := mockGetServer.updatesReceived[0].GetAdd().Addrs
 		actualNumberOfAdded := len(addressesAdded)
-		expectedNumberOfAdded := 2
+		expectedNumberOfAdded := 3
 		if actualNumberOfAdded != expectedNumberOfAdded {
 			t.Fatalf("Expecting [%d] addresses to be added, got [%d]: %v", expectedNumberOfAdded, actualNumberOfAdded, addressesAdded)
 		}
@@ -365,8 +391,9 @@ func TestEndpointTranslatorForPods(t *testing.T) {
 
 func mkAddressSetForServices(gatewayAddresses ...watcher.Address) watcher.AddressSet {
 	set := watcher.AddressSet{
-		Addresses: make(map[watcher.ServiceID]watcher.Address),
-		Labels:    map[string]string{"service": "service-name", "namespace": "service-ns"},
+		Addresses:       make(map[watcher.ServiceID]watcher.Address),
+		Labels:          map[string]string{"service": "service-name", "namespace": "service-ns"},
+		TopologicalPref: []string{},
 	}
 	for _, a := range gatewayAddresses {
 		a := a // pin
@@ -384,8 +411,9 @@ func mkAddressSetForServices(gatewayAddresses ...watcher.Address) watcher.Addres
 
 func mkAddressSetForPods(podAddresses ...watcher.Address) watcher.AddressSet {
 	set := watcher.AddressSet{
-		Addresses: make(map[watcher.PodID]watcher.Address),
-		Labels:    map[string]string{"service": "service-name", "namespace": "service-ns"},
+		Addresses:       make(map[watcher.PodID]watcher.Address),
+		Labels:          map[string]string{"service": "service-name", "namespace": "service-ns"},
+		TopologicalPref: []string{},
 	}
 	for _, p := range podAddresses {
 		id := watcher.PodID{Name: p.Pod.Name, Namespace: p.Pod.Namespace}
diff --git a/controller/api/destination/server_test.go b/controller/api/destination/server_test.go
index 9caffde05..f93e5f767 100644
--- a/controller/api/destination/server_test.go
+++ b/controller/api/destination/server_test.go
@@ -115,6 +115,7 @@ spec:
 		"linkerd",
 		"trust.domain",
 		"mycluster.local",
+		k8sAPI,
 		log,
 		make(<-chan struct{}),
 	}
diff --git a/controller/api/destination/traffic_split_adaptor_test.go b/controller/api/destination/traffic_split_adaptor_test.go
index f1e743767..7e24178bb 100644
--- a/controller/api/destination/traffic_split_adaptor_test.go
+++ b/controller/api/destination/traffic_split_adaptor_test.go
@@ -22,8 +22,8 @@ func TestTrafficSplitAdaptor(t *testing.T) {
 			},
 			DstOverrides: []*sp.WeightedDst{
 				{
-					Authority: "foo",
-					Weight:    resource.MustParse("500m"),
+					Authority: "foo.ns.svc.cluster.local.:80",
+					Weight:    resource.MustParse("1"),
 				},
 			},
 		},
diff --git a/pkg/addr/addr_test.go b/pkg/addr/addr_test.go
index bbe75ed27..771ae44c9 100644
--- a/pkg/addr/addr_test.go
+++ b/pkg/addr/addr_test.go
@@ -198,3 +198,114 @@ func TestNetToPublic(t *testing.T) {
 		})
 	}
 }
+
+func TestParseProxyIPV4(t *testing.T) {
+	var testCases = []struct {
+		ip      string
+		expAddr *pb.IPAddress
+		expErr  bool
+	}{
+		{
+			ip:      "10.0",
+			expAddr: nil,
+			expErr:  true,
+		},
+		{
+			ip:      "x.x.x.x",
+			expAddr: nil,
+			expErr:  true,
+		},
+		{
+			ip: "10.10.10.10",
+			expAddr: &pb.IPAddress{
+				Ip: &pb.IPAddress_Ipv4{Ipv4: 168430090},
+			},
+			expErr: false,
+		},
+	}
+
+	for _, testCase := range testCases {
+		res, err := ParseProxyIPV4(testCase.ip)
+		if testCase.expErr && err == nil {
+			t.Fatalf("expected get err, but get nil")
+		}
+		if !testCase.expErr {
+			if err != nil {
+				t.Fatalf("Unexpected err %v", err)
+			}
+			if !proto.Equal(res, testCase.expAddr) {
+				t.Fatalf("Unexpected TCP Address: [%+v] expected: [%+v]", res, testCase.expAddr)
+			}
+		}
+	}
+}
+
+func TestParsePublicIPV4(t *testing.T) {
+	var testCases = []struct {
+		ip      string
+		expAddr *public.IPAddress
+		expErr  bool
+	}{
+		{
+			ip:      "10.0",
+			expAddr: nil,
+			expErr:  true,
+		},
+		{
+			ip:      "x.x.x.x",
+			expAddr: nil,
+			expErr:  true,
+		},
+		{
+			ip: "10.10.10.11",
+			expAddr: &public.IPAddress{
+				Ip: &public.IPAddress_Ipv4{Ipv4: 168430091},
+			},
+			expErr: false,
+		},
+	}
+
+	for _, testCase := range testCases {
+		res, err := ParsePublicIPV4(testCase.ip)
+		if testCase.expErr && err == nil {
+			t.Fatalf("expected get err, but get nil")
+		}
+		if !testCase.expErr {
+			if err != nil {
+				t.Fatalf("Unexpected err %v", err)
+			}
+			if !proto.Equal(res, testCase.expAddr) {
+				t.Fatalf("Unexpected TCP Address: [%+v] expected: [%+v]", res, testCase.expAddr)
+			}
+		}
+	}
+}
+
+func TestProxyAddressToString(t *testing.T) {
+	var testCases = []struct {
+		addr   *pb.TcpAddress
+		expStr string
+	}{
+		{
+			addr: &pb.TcpAddress{
+				Ip:   &pb.IPAddress{Ip: &pb.IPAddress_Ipv4{Ipv4: 1}},
+				Port: 1234,
+			},
+			expStr: "0.0.0.1:1234",
+		},
+		{
+			addr: &pb.TcpAddress{
+				Ip:   &pb.IPAddress{Ip: &pb.IPAddress_Ipv4{Ipv4: 65535}},
+				Port: 5678,
+			},
+			expStr: "0.0.255.255:5678",
+		},
+	}
+
+	for _, testCase := range testCases {
+		res := ProxyAddressToString(testCase.addr)
+		if !(res == testCase.expStr) {
+			t.Fatalf("Unexpected string: %s expected: %s", res, testCase.expStr)
+		}
+	}
+}
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index a44226cc4..32b0e467b 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -2335,17 +2335,8 @@ metadata:
 	})
 }
 
-func getConfigAndKubeSystemNamespace(ha bool, nsLabel string) []string {
+func getWebhookAndKubeSystemNamespace(nsLabel string, failurePolicy string) []string {
 	return []string{fmt.Sprintf(`
-kind: ConfigMap
-apiVersion: v1
-metadata:
-  name: linkerd-config
-  namespace: linkerd
-data:
-  install: |
-    {"cliVersion":"dev-undefined","flags":[{"name":"ha","value":"%v"}]}`, ha),
-		fmt.Sprintf(`
 apiVersion: v1
 kind: Namespace
 metadata:
@@ -2353,6 +2344,35 @@ metadata:
   labels:
     %s
   name: kube-system`, nsLabel),
+		fmt.Sprintf(`
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: MutatingWebhookConfiguration
+metadata:
+  name: linkerd-proxy-injector-webhook-config
+  labels:
+    linkerd.io/control-plane-component: proxy-injector
+    linkerd.io/control-plane-ns: linkerd
+webhooks:
+- name: linkerd-proxy-injector.linkerd.io
+  namespaceSelector:
+    matchExpressions:
+    - key: config.linkerd.io/admission-webhooks
+      operator: NotIn
+      values:
+      - disabled
+  clientConfig:
+    service:
+      name: linkerd-proxy-injector
+      namespace: linkerd
+      path: "/"
+    caBundle: cHJveHkgaW5qZWN0b3IgQ0EgYnVuZGxl
+  failurePolicy: %s
+  rules:
+  - operations: [ "CREATE" ]
+    apiGroups: [""]
+    apiVersions: ["v1"]
+    resources: ["pods"]
+  sideEffects: None`, failurePolicy),
 	}
 }
 
@@ -2363,24 +2383,24 @@ func TestKubeSystemNamespaceInHA(t *testing.T) {
 		expectedOutput  string
 	}{
 		{
-			"passes when HA is not enabled",
-			getConfigAndKubeSystemNamespace(false, ""),
+			"passes when webhook policy is Ignore is not enabled",
+			getWebhookAndKubeSystemNamespace("", "Ignore"),
 			"",
 		},
 		{
-			"passes when HA is enabled and namespace has required metadata",
-			getConfigAndKubeSystemNamespace(true, "config.linkerd.io/admission-webhooks: disabled"),
+			"passes when webhook policy is Fail and namespace has required metadata",
+			getWebhookAndKubeSystemNamespace("config.linkerd.io/admission-webhooks: disabled", "Fail"),
 			"l5d-injection-disabled pod injection disabled on kube-system",
 		},
 		{
-			"fails when HA and admission hooks are enabled",
-			getConfigAndKubeSystemNamespace(true, "config.linkerd.io/admission-webhooks: enabled"),
-			"l5d-injection-disabled pod injection disabled on kube-system: kube-system namespace needs to have the label config.linkerd.io/admission-webhooks: disabled if HA mode is enabled",
+			"fails when webhook policy is Fail and admission hooks are enabled",
+			getWebhookAndKubeSystemNamespace("config.linkerd.io/admission-webhooks: enabled", "Fail"),
+			"l5d-injection-disabled pod injection disabled on kube-system: kube-system namespace needs to have the label config.linkerd.io/admission-webhooks: disabled if injector webhook failure policy is Fail",
 		},
 		{
-			"fails when HA is enabled and metadata is missing",
-			getConfigAndKubeSystemNamespace(true, ""),
-			"l5d-injection-disabled pod injection disabled on kube-system: kube-system namespace needs to have the label config.linkerd.io/admission-webhooks: disabled if HA mode is enabled",
+			"fails when webhook policy is Fail and metadata is missing",
+			getWebhookAndKubeSystemNamespace("", "Fail"),
+			"l5d-injection-disabled pod injection disabled on kube-system: kube-system namespace needs to have the label config.linkerd.io/admission-webhooks: disabled if injector webhook failure policy is Fail",
 		},
 	}
 
@@ -2391,13 +2411,7 @@ func TestKubeSystemNamespaceInHA(t *testing.T) {
 			hc := NewHealthChecker([]CategoryID{}, &Options{})
 			hc.ControlPlaneNamespace = "linkerd"
 
-			var err error
 			hc.kubeAPI, _ = k8s.NewFakeAPI(tc.k8sConfigs...)
-			_, hc.linkerdConfig, err = hc.checkLinkerdConfigConfigMap()
-			if err != nil {
-				t.Fatalf("Unexpected error: %q", err)
-			}
-
 			hc.addCheckAsCategory("l5d-injection-disabled", LinkerdHAChecks, "pod injection disabled on kube-system")
 
 			obs := newObserver()
diff --git a/web/app/js/components/TopRoutesTable.jsx b/web/app/js/components/TopRoutesTable.jsx
index 266d9afac..d13f929a8 100644
--- a/web/app/js/components/TopRoutesTable.jsx
+++ b/web/app/js/components/TopRoutesTable.jsx
@@ -2,52 +2,53 @@ import BaseTable from './BaseTable.jsx';
 import PropTypes from 'prop-types';
 import React from 'react';
 import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
+import { Trans } from '@lingui/macro';
 import { metricToFormatter } from './util/Utils.js';
 
 const routesColumns = [
   {
-    title: 'Route',
+    title: <Trans>columnTitleRoute</Trans>,
     dataIndex: 'route',
     filter: d => d.route,
     sorter: d => d.route,
   },
   {
-    title: 'Service',
+    title: <Trans>columnTitleService</Trans>,
     tooltip: 'hostname:port used when communicating with this target',
     dataIndex: 'authority',
     filter: d => d.authority,
     sorter: d => d.authority,
   },
   {
-    title: 'Success Rate',
+    title: <Trans>columnTitleSuccessRate</Trans>,
     dataIndex: 'successRate',
     isNumeric: true,
     render: d => <SuccessRateMiniChart sr={d.successRate} />,
     sorter: d => d.successRate,
   },
   {
-    title: 'RPS',
+    title: <Trans>columnTitleRPS</Trans>,
     dataIndex: 'requestRate',
     isNumeric: true,
     render: d => metricToFormatter.NO_UNIT(d.requestRate),
     sorter: d => d.requestRate,
   },
   {
-    title: 'P50 Latency',
+    title: <Trans>columnTitleP50Latency</Trans>,
     dataIndex: 'latency.P50',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.latency.P50),
     sorter: d => d.latency.P50,
   },
   {
-    title: 'P95 Latency',
+    title: <Trans>columnTitleP95Latency</Trans>,
     dataIndex: 'latency.P95',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.latency.P95),
     sorter: d => d.latency.P95,
   },
   {
-    title: 'P99 Latency',
+    title: <Trans>columnTitleP99Latency</Trans>,
     dataIndex: 'latency.P99',
     isNumeric: true,
     render: d => metricToFormatter.LATENCY(d.latency.P99),