Repository: linkerd/linkerd2
Pull request #6257 (2021-06-11 22:04:38)

artifacthub.io Verified Publisher
 Followup to #6181
    
After having uploaded the `artifacthub-repo.yml` file, we were able to
claim ownership of the linkerd2-edge repo. This generated a
`repositoryID` in artifacthub.io that we're adding into this same file,
so that we can be marked as "Verified Publisher".
    
Also we moved that file one level up into `charts/` so it doesn't get
included in the built chart. And created two versions of that file, one
for edge and another for stable (whose ID will be received when the next
stable helm release is pushed), that will be copied into the helm repo
by the `release.yml` workflow.


alpeb: Please hold on review, I've just realized I have to cater for different IDs depending on the repo (edge vs stable).
alpeb: Ok ready for review, initial comment updated.

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a6cd468f4..1a216fa05 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -329,6 +329,7 @@ jobs:
         mkdir -p target/helm
         gsutil cp gs://helm.linkerd.io/edge/index.yaml target/helm/index-pre.yaml
         bin/helm-build package
+        cp charts/artifacthub-repo-edge.yml target/helm/artifacthub-repo.yml
         gsutil rsync target/helm gs://helm.linkerd.io/edge
     - name: Stable Helm chart creation and upload
       if: startsWith(github.ref, 'refs/tags/stable')
@@ -336,4 +337,5 @@ jobs:
         mkdir -p target/helm
         gsutil cp gs://helm.linkerd.io/stable/index.yaml target/helm/index-pre.yaml
         bin/helm-build package
+        cp charts/artifacthub-repo-stable.yml target/helm/artifacthub-repo.yml
         gsutil rsync target/helm gs://helm.linkerd.io/stable
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 9d244c1bc..5f4d64408 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -8,6 +8,7 @@
 - [AppddictionStudio](https://appddicitonstudio.com)
 - [Attest](https://www.askattest.com)
 - [Bink](https://bink.com)
+- [Boomin](https://www.boomin.com)
 - [Buoyant](https://buoyant.io)
 - [Candide](https://candidegardening.com)
 - [Celonis](https://celonis.com)
diff --git a/bin/helm-build b/bin/helm-build
index a8f2fce8e..bfeb90dc8 100755
--- a/bin/helm-build
+++ b/bin/helm-build
@@ -71,8 +71,6 @@ if [ "$1" = package ]; then
     mv "$rootdir"/target/helm/index-pre.yaml "$rootdir"/target/helm/index-pre-"$version".yaml
     "$bindir"/helm repo index --url "https://helm.linkerd.io/$repo/" --merge "$rootdir"/target/helm/index-pre-"$version".yaml "$rootdir"/target/helm
 
-    cp "$rootdir"/charts/linkerd2/artifacthub-repo.yml "$rootdir"/target/helm/
-
     # restore version in Values files
     setValues "$fullVersion" "linkerdVersionValue"
 fi
diff --git a/charts/linkerd2/artifacthub-repo.yml b/charts/artifacthub-repo-edge.yml
similarity index 67%
rename from charts/linkerd2/artifacthub-repo.yml
rename to charts/artifacthub-repo-edge.yml
index 9bcacc411..0d99fe10c 100644
--- a/charts/linkerd2/artifacthub-repo.yml
+++ b/charts/artifacthub-repo-edge.yml
@@ -1,4 +1,4 @@
-#repositoryID: The ID of the Artifact Hub repository where the packages will be published to (optional, but it enables verified publisher)
+repositoryID: 3f724e34-9d3c-459e-99ba-aa8f09620ff4
 owners:
   - name: olix0r
     email: ver@buoyant.io
diff --git a/charts/artifacthub-repo-stable.yml b/charts/artifacthub-repo-stable.yml
new file mode 100644
index 000000000..04147eb76
--- /dev/null
+++ b/charts/artifacthub-repo-stable.yml
@@ -0,0 +1,14 @@
+#repositoryID: To be determined after the stable repo gets claimed
+owners:
+  - name: olix0r
+    email: ver@buoyant.io
+  - name: alpeb
+    email: alejandro@buoyant.io
+  - name: adleong
+    email: alex@buoyant.io
+  - name: hawkw
+    email: eliza@buoyant.io
+  - name: kleimkuhler
+    email: kevinl@buoyant.io
+  - name: pothulapati
+    email: tarun@buoyant.io
diff --git a/jaeger/charts/linkerd-jaeger/README.md b/jaeger/charts/linkerd-jaeger/README.md
index 9b834b50a..229f02f46 100644
--- a/jaeger/charts/linkerd-jaeger/README.md
+++ b/jaeger/charts/linkerd-jaeger/README.md
@@ -71,11 +71,11 @@ Kubernetes: `>=1.16.0-0`
 
 | Key | Type | Default | Description |
 |-----|------|---------|-------------|
+| collector.config | string | see `value.yaml` for actual configuration | OpenTelemetry Collector config, See the [Configuration docs](https://opentelemetry.io/docs/collector/configuration/) for more information |
 | collector.enabled | bool | `true` | Set to false to exclude collector installation |
-| collector.image.name | string | `"omnition/opencensus-collector"` |  |
+| collector.image.name | string | `"otel/opentelemetry-collector"` |  |
 | collector.image.pullPolicy | string | `"Always"` |  |
-| collector.image.version | string | `"0.1.11"` |  |
-| collector.jaegerAddr | string | `nil` | address of the jaeger backend to send traces to |
+| collector.image.version | string | `"0.27.0"` |  |
 | collector.nodeSelector | object | `{"beta.kubernetes.io/os":"linux"}` | NodeSelector section, See the [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information |
 | collector.tolerations | string | `nil` | Tolerations section, See the [K8S documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information |
 | installNamespace | bool | `true` | Set to false when installing in a custom namespace. |
diff --git a/jaeger/charts/linkerd-jaeger/templates/tracing.yaml b/jaeger/charts/linkerd-jaeger/templates/tracing.yaml
index 4278928e5..5a5a32226 100644
--- a/jaeger/charts/linkerd-jaeger/templates/tracing.yaml
+++ b/jaeger/charts/linkerd-jaeger/templates/tracing.yaml
@@ -12,22 +12,7 @@ metadata:
     component: collector
 data:
   collector-config: |
-    receivers:
-      opencensus:
-        port: 55678
-      zipkin:
-        port: 9411
-      jaeger:
-        jaeger-thrift-http-port: 14268
-    queued-exporters:
-      jaeger-all-in-one:
-        num-workers: 4
-        queue-size: 100
-        retry-on-failure: true
-        sender-type: jaeger-thrift-http
-        jaeger-thrift-http:
-          collector-endpoint: {{.Values.collector.jaegerAddr | default (printf "http://%s.%s:14268/api/traces" "jaeger" .Values.namespace) }}
-          timeout: 5s
+    {{- .Values.collector.config | trim | nindent 4 }}
 ---
 apiVersion: v1
 kind: Service
@@ -39,6 +24,10 @@ metadata:
 spec:
   type: ClusterIP
   ports:
+  - name: otlp
+    port: 4317
+    protocol: TCP
+    targetPort: 4317
   - name: opencensus
     port: 55678
     protocol: TCP
@@ -47,10 +36,14 @@ spec:
     port: 9411
     protocol: TCP
     targetPort: 9411
-  - name: jaeger
+  - name: jaeger-thrift
     port: 14268
     protocol: TCP
     targetPort: 14268
+  - name: jaeger-grpc
+    port: 14250
+    protocol: TCP
+    targetPort: 14250
   selector:
     component: collector
 ---
@@ -85,22 +78,29 @@ spec:
       {{- include "linkerd.node-selector" (dict "Values" .Values.collector) | nindent 6 }}
       containers:
       - command:
-        - /occollector_linux
+        - /otelcol
         - --config=/conf/collector-config.yaml
         env:
         - name: GOGC
           value: "80"
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
         image: {{.Values.collector.image.name}}:{{.Values.collector.image.version}}
         imagePullPolicy: {{.Values.collector.image.pullPolicy}}
         livenessProbe:
           httpGet:
             path: /
             port: 13133
-        name: oc-collector
+        name: ot-collector
         ports:
+        - containerPort: 4317
         - containerPort: 55678
         - containerPort: 9411
         - containerPort: 14268
+        - containerPort: 14250
         - containerPort: 8888
         readinessProbe:
           httpGet:
@@ -140,6 +140,8 @@ spec:
   ports:
     - name: collection
       port: 14268
+    - name: grpc
+      port: 14250
     - name: ui
       port: 16686
 ---
@@ -182,6 +184,8 @@ spec:
           name: admin
         - containerPort: 14268
           name: collection
+        - containerPort: 14250
+          name: grpc
         - containerPort: 16686
           name: ui
         {{- if .Values.jaeger.resources -}}
diff --git a/jaeger/charts/linkerd-jaeger/values.yaml b/jaeger/charts/linkerd-jaeger/values.yaml
index 957246a63..1375a43ff 100644
--- a/jaeger/charts/linkerd-jaeger/values.yaml
+++ b/jaeger/charts/linkerd-jaeger/values.yaml
@@ -21,8 +21,8 @@ collector:
   # -- Set to false to exclude collector installation
   enabled: true
   image:
-    name: omnition/opencensus-collector
-    version: 0.1.11
+    name: otel/opentelemetry-collector
+    version: 0.27.0
     pullPolicy: Always
 
   # -- NodeSelector section, See the
@@ -33,8 +33,39 @@ collector:
   # for more information
   tolerations: *default_tolerations
 
-  # -- address of the jaeger backend to send traces to
-  jaegerAddr:
+  # -- OpenTelemetry Collector config, See the
+  # [Configuration docs](https://opentelemetry.io/docs/collector/configuration/)
+  # for more information
+  # @default -- see `value.yaml` for actual configuration
+  config: |
+    receivers:
+      otlp:
+        protocols:
+          grpc:
+          http:
+      opencensus:
+      zipkin:
+      jaeger:
+        protocols:
+          grpc:
+          thrift_http:
+          thrift_compact:
+          thrift_binary:
+    processors:
+      batch:
+    extensions:
+      health_check:
+    exporters:
+      jaeger:
+        endpoint: jaeger.${POD_NAMESPACE}:14250
+        insecure: true
+    service:
+      extensions: [health_check]
+      pipelines:
+        traces:
+          receivers: [otlp,opencensus,zipkin,jaeger]
+          processors: [batch]
+          exporters: [jaeger]
 
 jaeger:
   # -- Set to false to exclude all-in-one Jaeger installation
@@ -43,6 +74,7 @@ jaeger:
     name: jaegertracing/all-in-one
     version: 1.19.2
     pullPolicy: Always
+
   # -- NodeSelector section, See the
   # [K8S documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) for more information
   nodeSelector: *default_node_selector
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index e008cad7e..ee9854f9e 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"sort"
 	"strings"
 	"time"
@@ -763,6 +764,19 @@ func (hc *HealthChecker) allCategories() []*Category {
 						return validateControlPlanePods(hc.controlPlanePods)
 					},
 				},
+				{
+					description: "cluster networks contains all node podCIDRs",
+					hintAnchor:  "l5d-cluster-networks-cidr",
+					check: func(ctx context.Context) error {
+						// We explicitly initialize the config here so that we dont rely on the "l5d-existence-linkerd-config"
+						// check to set the clusterNetworks value, since `linkerd check config` will skip that check.
+						err := hc.InitializeLinkerdGlobalConfig(ctx)
+						if err != nil {
+							return err
+						}
+						return hc.checkClusterNetworks(ctx)
+					},
+				},
 			},
 			false,
 		),
@@ -1867,6 +1881,48 @@ func (hc *HealthChecker) CheckNamespace(ctx context.Context, namespace string, s
 	return nil
 }
 
+func (hc *HealthChecker) checkClusterNetworks(ctx context.Context) error {
+	nodes, err := hc.kubeAPI.GetNodes(ctx)
+	if err != nil {
+		return err
+	}
+	clusterNetworks := strings.Split(hc.linkerdConfig.ClusterNetworks, ",")
+	clusterIPNets := make([]*net.IPNet, len(clusterNetworks))
+	for i, clusterNetwork := range clusterNetworks {
+		_, clusterIPNets[i], err = net.ParseCIDR(clusterNetwork)
+		if err != nil {
+			return err
+		}
+	}
+	var badPodCIDRS []string
+	for _, node := range nodes {
+		podCIDR := node.Spec.PodCIDR
+		podIP, podIPNet, err := net.ParseCIDR(podCIDR)
+		if err != nil {
+			return err
+		}
+		exists := cluterNetworksContainCIDR(clusterIPNets, podIPNet, podIP)
+		if !exists {
+			badPodCIDRS = append(badPodCIDRS, podCIDR)
+		}
+	}
+	if len(badPodCIDRS) > 0 {
+		return fmt.Errorf("node has podCIDR(s) %v which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=%s", badPodCIDRS, strings.Join(badPodCIDRS, ","))
+	}
+	return nil
+}
+
+func cluterNetworksContainCIDR(clusterIPNets []*net.IPNet, podIPNet *net.IPNet, podIP net.IP) bool {
+	for _, clusterIPNet := range clusterIPNets {
+		clusterIPMaskOnes, _ := clusterIPNet.Mask.Size()
+		podCIDRMaskOnes, _ := podIPNet.Mask.Size()
+		if clusterIPNet.Contains(podIP) && podCIDRMaskOnes >= clusterIPMaskOnes {
+			return true
+		}
+	}
+	return false
+}
+
 func (hc *HealthChecker) expectedRBACNames() []string {
 	return []string{
 		fmt.Sprintf("linkerd-%s-identity", hc.ControlPlaneNamespace),
diff --git a/pkg/k8s/api.go b/pkg/k8s/api.go
index 386db9110..55e1014c2 100644
--- a/pkg/k8s/api.go
+++ b/pkg/k8s/api.go
@@ -147,6 +147,15 @@ func (kubeAPI *KubernetesAPI) NamespaceExists(ctx context.Context, namespace str
 	return ns != nil, nil
 }
 
+// GetNodes returs all the nodes in a cluster.
+func (kubeAPI *KubernetesAPI) GetNodes(ctx context.Context) ([]corev1.Node, error) {
+	nodes, err := kubeAPI.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
+	if err != nil {
+		return nil, err
+	}
+	return nodes.Items, nil
+}
+
 // GetPodsByNamespace returns all pods in a given namespace
 func (kubeAPI *KubernetesAPI) GetPodsByNamespace(ctx context.Context, namespace string) ([]corev1.Pod, error) {
 	podList, err := kubeAPI.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})


Test patch:
diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index dc2db8654..76533f028 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -1,10 +1,6 @@
 name: Integration tests
 on:
-  pull_request:
-    paths-ignore:
-    - '*.md'
-    - '**/*.md'
-    - 'web/app/package.json'
+  pull_request: {}
   push:
     paths-ignore:
     - '*.md'
@@ -12,7 +8,7 @@ on:
     - 'web/app/package.json'
     branches:
     - main
-permissions: 
+permissions:
   contents: read
 env:
   GH_ANNOTATION: true
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index 0389253ec..c01c59061 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -1,9 +1,6 @@
 name: Unit tests
 on:
-  pull_request:
-    paths-ignore:
-    - '*.md'
-    - '**/*.md'
+  pull_request: {}
   push:
     paths-ignore:
     - '*.md'
diff --git a/jaeger/cmd/testdata/install_collector_disabled.golden b/jaeger/cmd/testdata/install_collector_disabled.golden
index bd55dbf30..6b1e8033f 100644
--- a/jaeger/cmd/testdata/install_collector_disabled.golden
+++ b/jaeger/cmd/testdata/install_collector_disabled.golden
@@ -219,6 +219,8 @@ spec:
   ports:
     - name: collection
       port: 14268
+    - name: grpc
+      port: 14250
     - name: ui
       port: 16686
 ---
@@ -259,6 +261,8 @@ spec:
           name: admin
         - containerPort: 14268
           name: collection
+        - containerPort: 14250
+          name: grpc
         - containerPort: 16686
           name: ui
       dnsPolicy: ClusterFirst
diff --git a/jaeger/cmd/testdata/install_default.golden b/jaeger/cmd/testdata/install_default.golden
index 0fc6f4436..0f79ed75f 100644
--- a/jaeger/cmd/testdata/install_default.golden
+++ b/jaeger/cmd/testdata/install_default.golden
@@ -227,21 +227,33 @@ metadata:
 data:
   collector-config: |
     receivers:
+      otlp:
+        protocols:
+          grpc:
+          http:
       opencensus:
-        port: 55678
       zipkin:
-        port: 9411
       jaeger:
-        jaeger-thrift-http-port: 14268
-    queued-exporters:
-      jaeger-all-in-one:
-        num-workers: 4
-        queue-size: 100
-        retry-on-failure: true
-        sender-type: jaeger-thrift-http
-        jaeger-thrift-http:
-          collector-endpoint: http://jaeger.linkerd-jaeger:14268/api/traces
-          timeout: 5s
+        protocols:
+          grpc:
+          thrift_http:
+          thrift_compact:
+          thrift_binary:
+    processors:
+      batch:
+    extensions:
+      health_check:
+    exporters:
+      jaeger:
+        endpoint: jaeger.${POD_NAMESPACE}:14250
+        insecure: true
+    service:
+      extensions: [health_check]
+      pipelines:
+        traces:
+          receivers: [otlp,opencensus,zipkin,jaeger]
+          processors: [batch]
+          exporters: [jaeger]
 ---
 apiVersion: v1
 kind: Service
@@ -253,6 +265,10 @@ metadata:
 spec:
   type: ClusterIP
   ports:
+  - name: otlp
+    port: 4317
+    protocol: TCP
+    targetPort: 4317
   - name: opencensus
     port: 55678
     protocol: TCP
@@ -261,10 +277,14 @@ spec:
     port: 9411
     protocol: TCP
     targetPort: 9411
-  - name: jaeger
+  - name: jaeger-thrift
     port: 14268
     protocol: TCP
     targetPort: 14268
+  - name: jaeger-grpc
+    port: 14250
+    protocol: TCP
+    targetPort: 14250
   selector:
     component: collector
 ---
@@ -297,22 +317,29 @@ spec:
         beta.kubernetes.io/os: linux
       containers:
       - command:
-        - /occollector_linux
+        - /otelcol
         - --config=/conf/collector-config.yaml
         env:
         - name: GOGC
           value: "80"
-        image: omnition/opencensus-collector:0.1.11
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        image: otel/opentelemetry-collector:0.27.0
         imagePullPolicy: Always
         livenessProbe:
           httpGet:
             path: /
             port: 13133
-        name: oc-collector
+        name: ot-collector
         ports:
+        - containerPort: 4317
         - containerPort: 55678
         - containerPort: 9411
         - containerPort: 14268
+        - containerPort: 14250
         - containerPort: 8888
         readinessProbe:
           httpGet:
@@ -347,6 +374,8 @@ spec:
   ports:
     - name: collection
       port: 14268
+    - name: grpc
+      port: 14250
     - name: ui
       port: 16686
 ---
@@ -387,6 +416,8 @@ spec:
           name: admin
         - containerPort: 14268
           name: collection
+        - containerPort: 14250
+          name: grpc
         - containerPort: 16686
           name: ui
       dnsPolicy: ClusterFirst
diff --git a/jaeger/cmd/testdata/install_jaeger_disabled.golden b/jaeger/cmd/testdata/install_jaeger_disabled.golden
index 1f8864e44..137d840a7 100644
--- a/jaeger/cmd/testdata/install_jaeger_disabled.golden
+++ b/jaeger/cmd/testdata/install_jaeger_disabled.golden
@@ -215,21 +215,33 @@ metadata:
 data:
   collector-config: |
     receivers:
+      otlp:
+        protocols:
+          grpc:
+          http:
       opencensus:
-        port: 55678
       zipkin:
-        port: 9411
       jaeger:
-        jaeger-thrift-http-port: 14268
-    queued-exporters:
-      jaeger-all-in-one:
-        num-workers: 4
-        queue-size: 100
-        retry-on-failure: true
-        sender-type: jaeger-thrift-http
-        jaeger-thrift-http:
-          collector-endpoint: http://jaeger.linkerd-jaeger:14268/api/traces
-          timeout: 5s
+        protocols:
+          grpc:
+          thrift_http:
+          thrift_compact:
+          thrift_binary:
+    processors:
+      batch:
+    extensions:
+      health_check:
+    exporters:
+      jaeger:
+        endpoint: jaeger.${POD_NAMESPACE}:14250
+        insecure: true
+    service:
+      extensions: [health_check]
+      pipelines:
+        traces:
+          receivers: [otlp,opencensus,zipkin,jaeger]
+          processors: [batch]
+          exporters: [jaeger]
 ---
 apiVersion: v1
 kind: Service
@@ -241,6 +253,10 @@ metadata:
 spec:
   type: ClusterIP
   ports:
+  - name: otlp
+    port: 4317
+    protocol: TCP
+    targetPort: 4317
   - name: opencensus
     port: 55678
     protocol: TCP
@@ -249,10 +265,14 @@ spec:
     port: 9411
     protocol: TCP
     targetPort: 9411
-  - name: jaeger
+  - name: jaeger-thrift
     port: 14268
     protocol: TCP
     targetPort: 14268
+  - name: jaeger-grpc
+    port: 14250
+    protocol: TCP
+    targetPort: 14250
   selector:
     component: collector
 ---
@@ -285,22 +305,29 @@ spec:
         beta.kubernetes.io/os: linux
       containers:
       - command:
-        - /occollector_linux
+        - /otelcol
         - --config=/conf/collector-config.yaml
         env:
         - name: GOGC
           value: "80"
-        image: omnition/opencensus-collector:0.1.11
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.namespace
+        image: otel/opentelemetry-collector:0.27.0
         imagePullPolicy: Always
         livenessProbe:
           httpGet:
             path: /
             port: 13133
-        name: oc-collector
+        name: ot-collector
         ports:
+        - containerPort: 4317
         - containerPort: 55678
         - containerPort: 9411
         - containerPort: 14268
+        - containerPort: 14250
         - containerPort: 8888
         readinessProbe:
           httpGet:
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index 44933da10..772133e18 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -1204,6 +1204,107 @@ data:
 	}
 }
 
+func TestCheckClusterNetworks(t *testing.T) {
+	var testCases = []struct {
+		checkDescription string
+		k8sConfigs       []string
+		expected         []string
+	}{
+		{
+			checkDescription: "cluster networks contains all node podCIDRs",
+			k8sConfigs: []string{`
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-ns
+`,
+				`
+apiVersion: v1
+kind: Node
+metadata:
+  name: linkerd-test-ns-identity
+spec:
+  podCIDR: 90.10.90.24/24
+`,
+				`
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: linkerd-config
+  namespace: test-ns
+  labels:
+    linkerd.io/control-plane-ns: test-ns
+data:
+  values: |
+    clusterNetworks: "10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16"
+`,
+			},
+			expected: []string{
+				"linkerd-existence cluster networks contains all node podCIDRs: node has podCIDR(s) [90.10.90.24/24] which are not contained in the Linkerd clusterNetworks.\n\tTry installing linkerd via --set clusterNetworks=90.10.90.24/24",
+			},
+		},
+		{
+			checkDescription: "cluster networks contains all node podCIDRs",
+			k8sConfigs: []string{`
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: test-ns
+`,
+				`
+apiVersion: v1
+kind: Node
+metadata:
+  name: linkerd-test-ns-identity
+spec:
+  podCIDR: 10.0.0.24/24
+`,
+				`
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: linkerd-config
+  namespace: test-ns
+  labels:
+    linkerd.io/control-plane-ns: test-ns
+data:
+  values: |
+    clusterNetworks: "10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16"
+`,
+			},
+			expected: []string{
+				"linkerd-existence cluster networks contains all node podCIDRs",
+			},
+		},
+	}
+
+	for i, tc := range testCases {
+		tc := tc // pin
+		t.Run(fmt.Sprintf("%d: returns expected config result", i), func(t *testing.T) {
+			hc := NewHealthChecker(
+				[]CategoryID{},
+				&Options{
+					ControlPlaneNamespace: "test-ns",
+				},
+			)
+
+			var err error
+			hc.kubeAPI, err = k8s.NewFakeAPI(tc.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("Unexpected error: %s", err)
+			}
+
+			obs := newObserver()
+			hc.addCheckAsCategory("linkerd-existence", LinkerdControlPlaneExistenceChecks,
+				tc.checkDescription)
+			hc.RunChecks(obs.resultFn)
+			if !reflect.DeepEqual(obs.results, tc.expected) {
+				t.Fatalf("Expected results\n%s,\nbut got:\n%s", strings.Join(tc.expected, "\n"), strings.Join(obs.results, "\n"))
+			}
+		})
+	}
+}
+
 func proxiesWithCertificates(certificates ...string) []string {
 	result := []string{}
 	for i, certificate := range certificates {
diff --git a/test/integration/testdata/check.cni.golden b/test/integration/testdata/check.cni.golden
index c7c95e380..bcd102a9a 100644
--- a/test/integration/testdata/check.cni.golden
+++ b/test/integration/testdata/check.cni.golden
@@ -18,6 +18,7 @@ linkerd-existence
 √ control plane replica sets are ready
 √ no unschedulable pods
 √ control plane pods are ready
+√ cluster networks contains all node podCIDRs
 
 linkerd-config
 --------------
diff --git a/test/integration/testdata/check.cni.proxy.golden b/test/integration/testdata/check.cni.proxy.golden
index 36d63ea87..185b2c44a 100644
--- a/test/integration/testdata/check.cni.proxy.golden
+++ b/test/integration/testdata/check.cni.proxy.golden
@@ -18,6 +18,7 @@ linkerd-existence
 √ control plane replica sets are ready
 √ no unschedulable pods
 √ control plane pods are ready
+√ cluster networks contains all node podCIDRs
 
 linkerd-config
 --------------
diff --git a/test/integration/testdata/check.golden b/test/integration/testdata/check.golden
index ba54a3a85..44dd0e3f9 100644
--- a/test/integration/testdata/check.golden
+++ b/test/integration/testdata/check.golden
@@ -18,6 +18,7 @@ linkerd-existence
 √ control plane replica sets are ready
 √ no unschedulable pods
 √ control plane pods are ready
+√ cluster networks contains all node podCIDRs
 
 linkerd-config
 --------------
diff --git a/test/integration/testdata/check.multicluster.proxy.golden b/test/integration/testdata/check.multicluster.proxy.golden
index 9fa3a576e..9e512eba2 100644
--- a/test/integration/testdata/check.multicluster.proxy.golden
+++ b/test/integration/testdata/check.multicluster.proxy.golden
@@ -18,6 +18,7 @@ linkerd-existence
 √ control plane replica sets are ready
 √ no unschedulable pods
 √ control plane pods are ready
+√ cluster networks contains all node podCIDRs
 
 linkerd-config
 --------------
diff --git a/test/integration/testdata/check.proxy.golden b/test/integration/testdata/check.proxy.golden
index fccf16c39..58ab20b93 100644
--- a/test/integration/testdata/check.proxy.golden
+++ b/test/integration/testdata/check.proxy.golden
@@ -18,6 +18,7 @@ linkerd-existence
 √ control plane replica sets are ready
 √ no unschedulable pods
 √ control plane pods are ready
+√ cluster networks contains all node podCIDRs
 
 linkerd-config
 --------------