Repository: linkerd/linkerd2
Pull request #11949 (2024-01-18 20:38:02)

stable-2.14.9
This stable release adds a cni-repair-controller which fixes the issue of
injected pods that cannot acquire proper network config because linkerd-cni
and/or the cluster's network CNI haven't fully started ([#11699]). It also
fixes a bug in the destination controller where having a large number of
Server resources could cause the destination controller to use an excessive
amount of CPU ([#11907]). Finally, it fixes a conflict with tap resource
shortnames which was causing warnings from kubectl v1.29.0+ ([#11816]).

[#11699]: https://github.com/linkerd/linkerd2/pull/11699
[#11907]: https://github.com/linkerd/linkerd2/pull/11907
[#11816]: https://github.com/linkerd/linkerd2/pull/11816

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->



Patch:
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 175e7bd14..5e2b42b24 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -257,7 +257,7 @@ jobs:
           - rsa-ca
           - helm-upgrade
           - uninstall
-          - upgrade-edge
+          # - upgrade-edge
           - upgrade-stable
     continue-on-error: true
     runs-on: ubuntu-20.04
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4bb6829a1..f7fc5d1b9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -122,7 +122,7 @@ jobs:
         - rsa-ca
         - helm-upgrade
         - uninstall
-        - upgrade-edge
+        #- upgrade-edge
         - upgrade-stable
     timeout-minutes: 60
     runs-on: ubuntu-20.04
diff --git a/CHANGES.md b/CHANGES.md
index 17ba7b17e..f95583ff9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,19 @@
 # Changes
 
+## stable-2.14.9
+
+This stable release adds a cni-repair-controller which fixes the issue of
+injected pods that cannot acquire proper network config because linkerd-cni
+and/or the cluster's network CNI haven't fully started ([#11699]). It also
+fixes a bug in the destination controller where having a large number of
+Server resources could cause the destination controller to use an excessive
+amount of CPU ([#11907]). Finally, it fixes a conflict with tap resource
+shortnames which was causing warnings from kubectl v1.29.0+ ([#11816]).
+
+[#11699]: https://github.com/linkerd/linkerd2/pull/11699
+[#11907]: https://github.com/linkerd/linkerd2/pull/11907
+[#11816]: https://github.com/linkerd/linkerd2/pull/11816
+
 ## stable-2.14.8
 
 This stable release fixes an issue in the control plane where discovery for pod
diff --git a/charts/linkerd-control-plane/Chart.yaml b/charts/linkerd-control-plane/Chart.yaml
index f7fd02fe3..f376562bb 100644
--- a/charts/linkerd-control-plane/Chart.yaml
+++ b/charts/linkerd-control-plane/Chart.yaml
@@ -16,7 +16,7 @@ dependencies:
 - name: partials
   version: 0.1.0
   repository: file://../partials
-version: 1.16.9
+version: 1.16.10
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/charts/linkerd-control-plane/README.md b/charts/linkerd-control-plane/README.md
index 8658a5417..d46fc2aef 100644
--- a/charts/linkerd-control-plane/README.md
+++ b/charts/linkerd-control-plane/README.md
@@ -3,7 +3,7 @@
 Linkerd gives you observability, reliability, and security
 for your microservices — with no code change required.
 
-![Version: 1.16.9](https://img.shields.io/badge/Version-1.16.9-informational?style=flat-square)
+![Version: 1.16.10](https://img.shields.io/badge/Version-1.16.10-informational?style=flat-square)
 ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/charts/linkerd2-cni/Chart.yaml b/charts/linkerd2-cni/Chart.yaml
index eb754ab4e..8b43328e1 100644
--- a/charts/linkerd2-cni/Chart.yaml
+++ b/charts/linkerd2-cni/Chart.yaml
@@ -9,4 +9,4 @@ description: |
 kubeVersion: ">=1.21.0-0"
 icon: https://linkerd.io/images/logo-only-200h.png
 name: "linkerd2-cni"
-version: 30.12.1
+version: 30.12.2
diff --git a/charts/linkerd2-cni/README.md b/charts/linkerd2-cni/README.md
index bbfb5254b..53e4e2e6b 100644
--- a/charts/linkerd2-cni/README.md
+++ b/charts/linkerd2-cni/README.md
@@ -6,7 +6,7 @@ Linkerd [CNI plugin](https://linkerd.io/2/features/cni/) takes care of setting
 up your pod's network so  incoming and outgoing traffic is proxied through the
 data plane.
 
-![Version: 30.12.1](https://img.shields.io/badge/Version-30.12.1-informational?style=flat-square)
+![Version: 30.12.2](https://img.shields.io/badge/Version-30.12.2-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
@@ -31,7 +31,7 @@ Kubernetes: `>=1.21.0-0`
 | ignoreOutboundPorts | string | `""` | Default set of outbound ports to skip via iptables |
 | image.name | string | `"cr.l5d.io/linkerd/cni-plugin"` | Docker image for the CNI plugin |
 | image.pullPolicy | string | `"IfNotPresent"` | Pull policy for the linkerd-cni container |
-| image.version | string | `"v1.2.2"` | Tag for the CNI container Docker image |
+| image.version | string | `"v1.3.0"` | Tag for the CNI container Docker image |
 | imagePullSecrets | list | `[]` |  |
 | inboundProxyPort | int | `4143` | Inbound port for the proxy container |
 | logLevel | string | `"info"` | Log level for the CNI plugin |
@@ -43,7 +43,17 @@ Kubernetes: `>=1.21.0-0`
 | proxyAdminPort | int | `4191` | Admin port for the proxy container |
 | proxyControlPort | int | `4190` | Control port for the proxy container |
 | proxyUID | int | `2102` | User id under which the proxy shall be ran |
-| resources | object | `{"cpu":{"limit":"","request":""},"ephemeral-storage":{"limit":"","request":""},"memory":{"limit":"","request":""}}` | Resource requests and limits for linkerd-cni daemonset containers |
+| repairController.enableSecurityContext | bool | `true` | Include a securityContext in the repair-controller container |
+| repairController.enabled | bool | `false` | Enables the repair-controller container |
+| repairController.logFormat | string | plain | Log format (`plain` or `json`) for the repair-controller container |
+| repairController.logLevel | string | info | Log level for the repair-controller container |
+| repairController.resources.cpu.limit | string | `""` | Maximum amount of CPU units that the repair-controller container can use |
+| repairController.resources.cpu.request | string | `""` | Amount of CPU units that the repair-controller container requests |
+| repairController.resources.ephemeral-storage.limit | string | `""` | Maximum amount of ephemeral storage that the repair-controller container can use |
+| repairController.resources.ephemeral-storage.request | string | `""` | Amount of ephemeral storage that the repair-controller container requests |
+| repairController.resources.memory.limit | string | `""` | Maximum amount of memory that the repair-controller container can use |
+| repairController.resources.memory.request | string | `""` | Amount of memory that the repair-controller container requests |
+| resources | object | `{"cpu":{"limit":"","request":""},"ephemeral-storage":{"limit":"","request":""},"memory":{"limit":"","request":""}}` | Resource requests and limits for linkerd-cni daemonset container |
 | resources.cpu.limit | string | `""` | Maximum amount of CPU units that the cni container can use |
 | resources.cpu.request | string | `""` | Amount of CPU units that the cni container requests |
 | resources.ephemeral-storage.limit | string | `""` | Maximum amount of ephemeral storage that the cni container can use |
diff --git a/charts/linkerd2-cni/templates/cni-plugin.yaml b/charts/linkerd2-cni/templates/cni-plugin.yaml
index 160449ee3..69d3e0e64 100644
--- a/charts/linkerd2-cni/templates/cni-plugin.yaml
+++ b/charts/linkerd2-cni/templates/cni-plugin.yaml
@@ -112,6 +112,14 @@ rules:
 - apiGroups: [""]
   resources: ["pods", "nodes", "namespaces", "services"]
   verbs: ["list", "get", "watch"]
+{{- if .Values.repairController.enabled }}
+- apiGroups: [""]
+  resources: ["pods"]
+  verbs: ["delete"]
+- apiGroups: ["events.k8s.io"]
+  resources: ["events"]
+  verbs: ["create"]
+{{- end }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
@@ -274,6 +282,59 @@ spec:
         {{- if .Values.resources }}
         {{- include "partials.resources" .Values.resources | nindent 8 }}
         {{- end }}
+      {{- if .Values.repairController.enabled }}
+      # This container watches over pods whose linkerd-network-validator
+      # container failed, probably because of a race condition while setting up
+      # the CNI plugin chain, and deletes those pods so they can try acquiring a
+      # proper network config again
+      - name: repair-controller
+        image: {{ .Values.image.name -}}:{{- .Values.image.version }}
+        imagePullPolicy: {{ .Values.image.pullPolicy }}
+        {{- if .Values.repairController.enableSecurityContext }}
+        env:
+        - name: LINKERD_CNI_REPAIR_CONTROLLER_NODE_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: spec.nodeName
+        - name: LINKERD_CNI_REPAIR_CONTROLLER_POD_NAME
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        command:
+          - /usr/lib/linkerd/linkerd-cni-repair-controller
+        args:
+          - --admin-addr=0.0.0.0:9990
+          - --log-format
+          - {{ .Values.repairController.logFormat }}
+          - --log-level
+          - {{ .Values.repairController.logLevel }}
+        livenessProbe:
+          httpGet:
+            path: /live
+            port: admin-http
+        readinessProbe:
+          failureThreshold: 7
+          httpGet:
+            path: /ready
+            port: admin-http
+          initialDelaySeconds: 10
+        ports:
+        - containerPort: 9990
+          name: admin-http
+        securityContext:
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - ALL
+          privileged: false
+          readOnlyRootFilesystem: true
+          seccompProfile:
+            type: RuntimeDefault
+        {{- end }}
+        {{- if .Values.resources }}
+        {{- include "partials.resources" .Values.resources | nindent 8 }}
+        {{- end }}
+      {{- end }}
       volumes:
       {{- if ne .Values.destCNIBinDir .Values.destCNINetDir }}
       - name: cni-bin-dir
diff --git a/charts/linkerd2-cni/values.yaml b/charts/linkerd2-cni/values.yaml
index 977763edb..a9f9e8fd4 100644
--- a/charts/linkerd2-cni/values.yaml
+++ b/charts/linkerd2-cni/values.yaml
@@ -53,7 +53,7 @@ image:
   # -- Docker image for the CNI plugin
   name: "cr.l5d.io/linkerd/cni-plugin"
   # -- Tag for the CNI container Docker image
-  version: "v1.2.2"
+  version: "v1.3.0"
   # -- Pull policy for the linkerd-cni container
   pullPolicy: IfNotPresent
 
@@ -71,22 +71,44 @@ imagePullSecrets: []
 
 # -- Add additional initContainers to the daemonset
 extraInitContainers: []
-# - name: wait-for-other-cni
-#   image: busybox:1.33
-#   command:
-#     - /bin/sh
-#     - -xc
-#     - |
-#       for i in $(seq 1 180); do
-#         test -f /host/etc/cni/net.d/10-aws.conflist && exit 0
-#         sleep 1
-#       done
-#       exit 1
-#   volumeMounts:
-#     - mountPath: /host/etc/cni/net.d
-#       name: cni-net-dir
 
-# -- Resource requests and limits for linkerd-cni daemonset containers
+# The cni-repair-controller scans pods in each node to find those that have
+# been injected by linkerd, and whose linkerd-network-validator container has
+# failed. This is usually caused by a race between linkerd-cni and the CNI
+# plugin used in the cluster. This controller deletes those failed pods so they
+# can restart and rety re-acquiring a proper network config.
+repairController:
+  # -- Enables the repair-controller container
+  enabled: false
+
+  # -- Log level for the repair-controller container
+  # @default -- info
+  logLevel: info
+  # -- Log format (`plain` or `json`) for the repair-controller container
+  # @default -- plain
+  logFormat: plain
+
+  # -- Include a securityContext in the repair-controller container
+  enableSecurityContext: true
+
+  resources:
+    cpu:
+      # -- Maximum amount of CPU units that the repair-controller container can use
+      limit: ""
+      # -- Amount of CPU units that the repair-controller container requests
+      request: ""
+    memory:
+      # -- Maximum amount of memory that the repair-controller container can use
+      limit: ""
+      # -- Amount of memory that the repair-controller container requests
+      request: ""
+    ephemeral-storage:
+      # -- Maximum amount of ephemeral storage that the repair-controller container can use
+      limit: ""
+      # -- Amount of ephemeral storage that the repair-controller container requests
+      request: ""
+
+# -- Resource requests and limits for linkerd-cni daemonset container
 resources:
   cpu:
     # -- Maximum amount of CPU units that the cni container can use
diff --git a/controller/api/destination/watcher/endpoints_watcher.go b/controller/api/destination/watcher/endpoints_watcher.go
index 2fffb46a9..9cb2dcbbd 100644
--- a/controller/api/destination/watcher/endpoints_watcher.go
+++ b/controller/api/destination/watcher/endpoints_watcher.go
@@ -1180,6 +1180,7 @@ func (pp *portPublisher) unsubscribe(listener EndpointUpdateListener) {
 }
 
 func (pp *portPublisher) updateServer(server *v1beta1.Server, selector labels.Selector, isAdd bool) {
+	updated := false
 	for id, address := range pp.addresses.Addresses {
 		if address.Pod != nil && selector.Matches(labels.Set(address.Pod.Labels)) {
 			var portMatch bool
@@ -1205,12 +1206,18 @@ func (pp *portPublisher) updateServer(server *v1beta1.Server, selector labels.Se
 				} else {
 					address.OpaqueProtocol = false
 				}
-				pp.addresses.Addresses[id] = address
+				if pp.addresses.Addresses[id].OpaqueProtocol != address.OpaqueProtocol {
+					pp.addresses.Addresses[id] = address
+					updated = true
+				}
 			}
 		}
 	}
-	for _, listener := range pp.listeners {
-		listener.Add(pp.addresses)
+	if updated {
+		for _, listener := range pp.listeners {
+			listener.Add(pp.addresses)
+		}
+		pp.metrics.incUpdates()
 	}
 }
 
diff --git a/jaeger/charts/linkerd-jaeger/Chart.yaml b/jaeger/charts/linkerd-jaeger/Chart.yaml
index f78aa90b2..ff15cc2e3 100644
--- a/jaeger/charts/linkerd-jaeger/Chart.yaml
+++ b/jaeger/charts/linkerd-jaeger/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: linkerd-jaeger
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.12.9
+version: 30.12.10
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index af67bcee1..7bd158036 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Jaeger extension adds distributed tracing to Linkerd using
 OpenCensus and Jaeger.
 
-![Version: 30.12.9](https://img.shields.io/badge/Version-30.12.9-informational?style=flat-square)
+![Version: 30.12.10](https://img.shields.io/badge/Version-30.12.10-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/multicluster/charts/linkerd-multicluster/Chart.yaml b/multicluster/charts/linkerd-multicluster/Chart.yaml
index c8bad2f85..e2d0ac12e 100644
--- a/multicluster/charts/linkerd-multicluster/Chart.yaml
+++ b/multicluster/charts/linkerd-multicluster/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: "linkerd-multicluster"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.11.9
+version: 30.11.10
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/multicluster/charts/linkerd-multicluster/README.md b/multicluster/charts/linkerd-multicluster/README.md
index 2a4343bc6..d9f94d94a 100644
--- a/multicluster/charts/linkerd-multicluster/README.md
+++ b/multicluster/charts/linkerd-multicluster/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Multicluster extension contains resources to support multicluster
 linking to remote clusters
 
-![Version: 30.11.9](https://img.shields.io/badge/Version-30.11.9-informational?style=flat-square)
+![Version: 30.11.10](https://img.shields.io/badge/Version-30.11.10-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/pkg/charts/cni/values.go b/pkg/charts/cni/values.go
index 6c04d7764..46e29d23d 100644
--- a/pkg/charts/cni/values.go
+++ b/pkg/charts/cni/values.go
@@ -35,6 +35,15 @@ type Resources struct {
 	EphemeralStorage Constraints `json:"ephemeral-storage"`
 }
 
+// RepairController contains the config for the repair-controller container
+type RepairController struct {
+	Image                 Image     `json:"image"`
+	LogLevel              string    `json:"logLevel"`
+	LogFormat             string    `json:"logFormat"`
+	EnableSecurityContext bool      `json:"enableSecurityContext"`
+	Resources             Resources `json:"resources"`
+}
+
 // Values contains the top-level elements in the cni Helm chart
 type Values struct {
 	InboundProxyPort    uint                `json:"inboundProxyPort"`
@@ -60,6 +69,7 @@ type Values struct {
 	EnablePSP           bool                `json:"enablePSP"`
 	Privileged          bool                `json:"privileged"`
 	Resources           Resources           `json:"resources"`
+	RepairController    RepairController    `json:"repairController"`
 }
 
 // NewValues returns a new instance of the Values type.
diff --git a/pkg/version/version.go b/pkg/version/version.go
index 9428558de..96e15e21e 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -16,7 +16,7 @@ var Version = undefinedVersion
 // https://github.com/linkerd/linkerd2-proxy-init This has to be kept in sync
 // with the default version in the control plane's values.yaml.
 var ProxyInitVersion = "v2.2.3"
-var LinkerdCNIVersion = "v1.2.2"
+var LinkerdCNIVersion = "v1.3.0"
 
 const (
 	// undefinedVersion should take the form `channel-version` to conform to
diff --git a/viz/charts/linkerd-viz/Chart.yaml b/viz/charts/linkerd-viz/Chart.yaml
index f86dacd10..cd8b71bd6 100644
--- a/viz/charts/linkerd-viz/Chart.yaml
+++ b/viz/charts/linkerd-viz/Chart.yaml
@@ -11,7 +11,7 @@ kubeVersion: ">=1.21.0-0"
 name: "linkerd-viz"
 sources:
 - https://github.com/linkerd/linkerd2/
-version: 30.12.9
+version: 30.12.10
 icon: https://linkerd.io/images/logo-only-200h.png
 maintainers:
   - name: Linkerd authors
diff --git a/viz/charts/linkerd-viz/README.md b/viz/charts/linkerd-viz/README.md
index c0648a1b7..2efd57c6b 100644
--- a/viz/charts/linkerd-viz/README.md
+++ b/viz/charts/linkerd-viz/README.md
@@ -3,7 +3,7 @@
 The Linkerd-Viz extension contains observability and visualization
 components for Linkerd.
 
-![Version: 30.12.9](https://img.shields.io/badge/Version-30.12.9-informational?style=flat-square)
+![Version: 30.12.10](https://img.shields.io/badge/Version-30.12.10-informational?style=flat-square)
 
 ![AppVersion: edge-XX.X.X](https://img.shields.io/badge/AppVersion-edge--XX.X.X-informational?style=flat-square)
 
diff --git a/viz/tap/api/handlers.go b/viz/tap/api/handlers.go
index d740dca27..5869eb7bb 100644
--- a/viz/tap/api/handlers.go
+++ b/viz/tap/api/handlers.go
@@ -55,19 +55,18 @@ var (
 
 	resources = []struct {
 		name       string
-		shortname  string
 		namespaced bool
 	}{
-		{"namespaces", "ns", false},
-		{"pods", "po", true},
-		{"replicationcontrollers", "rc", true},
-		{"services", "svc", true},
-		{"daemonsets", "ds", true},
-		{"deployments", "deploy", true},
-		{"replicasets", "rs", true},
-		{"statefulsets", "sts", true},
-		{"jobs", "", true},
-		{"cronjobs", "cj", true},
+		{"namespaces", false},
+		{"pods", true},
+		{"replicationcontrollers", true},
+		{"services", true},
+		{"daemonsets", true},
+		{"deployments", true},
+		{"replicasets", true},
+		{"statefulsets", true},
+		{"jobs", true},
+		{"cronjobs", true},
 	}
 )
 
@@ -257,7 +256,6 @@ func handleAPIResourceList(w http.ResponseWriter, _ *http.Request, _ httprouter.
 		resList.APIResources = append(resList.APIResources,
 			metav1.APIResource{
 				Name:       res.name,
-				ShortNames: []string{res.shortname},
 				Namespaced: res.namespaced,
 				Kind:       gvk.Kind,
 				Verbs:      metav1.Verbs{"watch"},


Test patch:
diff --git a/cli/cmd/install-cni-plugin_test.go b/cli/cmd/install-cni-plugin_test.go
index 762dfbbac..d286dc629 100644
--- a/cli/cmd/install-cni-plugin_test.go
+++ b/cli/cmd/install-cni-plugin_test.go
@@ -16,7 +16,7 @@ func TestRenderCNIPlugin(t *testing.T) {
 
 	image := cniPluginImage{
 		name:       "my-docker-registry.io/awesome/cni-plugin-test-image",
-		version:    "v1.2.2",
+		version:    "v1.3.0",
 		pullPolicy: nil,
 	}
 	fullyConfiguredOptions := &cniPluginOptions{
diff --git a/cli/cmd/install_cni_helm_test.go b/cli/cmd/install_cni_helm_test.go
index 201a2ee24..3223b9cfc 100644
--- a/cli/cmd/install_cni_helm_test.go
+++ b/cli/cmd/install_cni_helm_test.go
@@ -35,7 +35,7 @@ func TestRenderCniHelm(t *testing.T) {
   			"logLevel": "debug",
 			"image": {
 				"name": "cr.l5d.io/linkerd/cni-plugin",
-				"version": "v1.2.2"
+				"version": "v1.3.0"
 			},
   			"proxyUID": 1111,
   			"destCNINetDir": "/etc/cni/net.d-test",
diff --git a/cli/cmd/testdata/install-cni-plugin_default.golden b/cli/cmd/testdata/install-cni-plugin_default.golden
index 1fb551be4..b53078e53 100644
--- a/cli/cmd/testdata/install-cni-plugin_default.golden
+++ b/cli/cmd/testdata/install-cni-plugin_default.golden
@@ -118,7 +118,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:v1.2.2
+        image: cr.l5d.io/linkerd/cni-plugin:v1.3.0
         imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
index b31c00286..b64981de5 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured.golden
@@ -119,7 +119,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.2.2
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.3.0
         imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
index 2b547098b..0bea8074d 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_equal_dsts.golden
@@ -119,7 +119,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.2.2
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.3.0
         imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
index b31c00286..b64981de5 100644
--- a/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
+++ b/cli/cmd/testdata/install-cni-plugin_fully_configured_no_namespace.golden
@@ -119,7 +119,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.2.2
+        image: my-docker-registry.io/awesome/cni-plugin-test-image:v1.3.0
         imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
index 196296afc..d1b14c150 100644
--- a/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
+++ b/cli/cmd/testdata/install-cni-plugin_skip_ports.golden
@@ -119,7 +119,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:v1.2.2
+        image: cr.l5d.io/linkerd/cni-plugin:v1.3.0
         imagePullPolicy: 
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install_cni_helm_default_output.golden b/cli/cmd/testdata/install_cni_helm_default_output.golden
index 6bd305f06..566534f1e 100644
--- a/cli/cmd/testdata/install_cni_helm_default_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_default_output.golden
@@ -111,7 +111,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:v1.2.2
+        image: cr.l5d.io/linkerd/cni-plugin:v1.3.0
         imagePullPolicy: IfNotPresent
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/cli/cmd/testdata/install_cni_helm_override_output.golden b/cli/cmd/testdata/install_cni_helm_override_output.golden
index 7d99a13d1..2fd31eac2 100644
--- a/cli/cmd/testdata/install_cni_helm_override_output.golden
+++ b/cli/cmd/testdata/install_cni_helm_override_output.golden
@@ -112,7 +112,7 @@ spec:
       # script copies the files into place and then sleeps so
       # that Kubernetes doesn't keep trying to restart it.
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:v1.2.2
+        image: cr.l5d.io/linkerd/cni-plugin:v1.3.0
         imagePullPolicy: IfNotPresent
         env:
         - name: DEST_CNI_NET_DIR
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 5f748a2ee..7180bb793 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -2421,7 +2421,7 @@ spec:
       serviceAccountName: linkerd-cni
       containers:
       - name: install-cni
-        image: cr.l5d.io/linkerd/cni-plugin:v1.2.2
+        image: cr.l5d.io/linkerd/cni-plugin:v1.3.0
         env:
         - name: DEST_CNI_NET_DIR
           valueFrom: