Linkerd’s control plane components are designed to be resilient, but true high availability requires more than just individual component redundancy. You need to replicate the entire control plane across multiple Kubernetes nodes.
Here’s how you can achieve this using a multi-instance deployment:
The Problem: Single Point of Failure
If your Linkerd control plane runs on a single Kubernetes node and that node goes down, your entire service mesh becomes unavailable. This means:
- No new pods will be injected with the Linkerd proxy.
- Existing proxies might still function for a while, but they won’t be able to communicate with the control plane for updates, metrics, or policy enforcement.
- The Linkerd dashboard and CLI commands will fail.
The Solution: Multi-Instance Control Plane
Linkerd’s Helm chart supports deploying multiple replicas of its control plane components. This ensures that if one instance fails, others can take over seamlessly.
Step 1: Uninstall Existing Linkerd (if applicable)
If you have an existing single-instance Linkerd installation, you’ll need to uninstall it first to avoid conflicts.
linkerd uninstall --all-namespaces | kubectl delete -f -
This command uninstalls Linkerd and deletes all its associated resources across all namespaces.
Step 2: Install Linkerd with Multiple Replicas
You’ll use the linkerd install command with specific Helm values to configure multiple replicas. The key parameters are controller.replicas and controller.deploymentStrategy.
Using linkerd install directly:
linkerd install \
--set controller.replicas=3 \
--set controller.deploymentStrategy.type=RollingUpdate \
--set controller.deploymentStrategy.rollingUpdate.maxUnavailable=1 \
--set controller.deploymentStrategy.rollingUpdate.maxSurge=1 \
--set identity.replicas=3 \
--set heartbeat.replicas=3 \
--set prometheus.replicas=3 \
--set grafana.replicas=3 \
--set ingressController.replicas=3 \
--set tap.replicas=3 \
--set webhooks.replicas=3 \
--set proxyInjector.replicas=3 \
--set diagnostic.replicas=3 \
--set certManager.enabled=false \
--set clusterIssuer.enabled=false \
--set identityContext.tlsOnly=true \
--set identityContext.issuerUrl="https://kubernetes.default.svc.cluster.local" \
--set identityContext.crtKeyAlgorithm="rsa" \
--set identityContext.crtKeySize=2048 \
--set identityContext.crtKeyValidFor="8760h" \
--set identityContext.defaultSANs="linkerd-gateway.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-proxy-injector.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-identity.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-controller.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-proxy-injector.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-tap.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-webhooks.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-heartbeat.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-prometheus.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-grafana.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-destination.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-sp-validator.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-cni.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-gateway" \
--set identityContext.defaultSANs="linkerd-proxy-injector" \
--set identityContext.defaultSANs="linkerd-identity" \
--set identityContext.defaultSANs="linkerd-controller" \
--set identityContext.defaultSANs="linkerd-tap" \
--set identityContext.defaultSANs="linkerd-webhooks" \
--set identityContext.defaultSANs="linkerd-heartbeat" \
--set identityContext.defaultSANs="linkerd-prometheus" \
--set identityContext.defaultSANs="linkerd-grafana" \
--set identityContext.defaultSANs="linkerd-destination" \
--set identityContext.defaultSANs="linkerd-sp-validator" \
--set identityContext.defaultSANs="linkerd-cni" \
--set identityContext.defaultSANs="localhost" \
--set identityContext.defaultSANs="kubernetes" \
--set identityContext.defaultSANs="kubernetes.default" \
--set identityContext.defaultSANs="kubernetes.default.svc" \
--set identityContext.defaultSANs="kubernetes.default.svc.cluster" \
--set identityContext.defaultSANs="kubernetes.default.svc.cluster.local" \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs="linkerd-gateway.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-proxy-injector.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-identity.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-controller.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-tap.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-webhooks.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-heartbeat.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-prometheus.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-grafana.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-destination.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-sp-validator.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-cni.linkerd.svc.cluster.local" \
--set identityContext.defaultSANs="linkerd-gateway" \
--set identityContext.defaultSANs="linkerd-proxy-injector" \
--set identityContext.defaultSANs="linkerd-identity" \
--set identityContext.defaultSANs="linkerd-controller" \
--set identityContext.defaultSANs="linkerd-tap" \
--set identityContext.defaultSANs="linkerd-webhooks" \
--set identityContext.defaultSANs="linkerd-heartbeat" \
--set identityContext.defaultSANs="linkerd-prometheus" \
--set identityContext.defaultSANs="linkerd-grafana" \
--set identityContext.defaultSANs="linkerd-destination" \
--set identityContext.defaultSANs="linkerd-sp-validator" \
--set identityContext.defaultSANs="linkerd-cni" \
--set identityContext.defaultSANs="localhost" \
--set identityContext.defaultSANs="kubernetes" \
--set identityContext.defaultSANs="kubernetes.default" \
--set identityContext.defaultSANs="kubernetes.default.svc" \
--set identityContext.defaultSANs="kubernetes.default.svc.cluster" \
--set identityContext.defaultSANs="kubernetes.default.svc.cluster.local" \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-identity -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-controller -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-proxy-injector -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-tap -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-webhooks -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-heartbeat -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-prometheus -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-grafana -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-destination -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-sp-validator -n linkerd -o jsonpath='{.metadata.name}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.spec.clusterIP}') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc.cluster.local') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd.svc') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}.linkerd') \
--set identityContext.defaultSANs=$(kubectl get service linkerd-cni -n linkerd -o jsonpath='{.metadata.name}') \
| kubectl apply -f -
Explanation of Key Flags:
--set controller.replicas=3: This is the primary flag. It tells Linkerd to create 3 replicas of the main control plane components (likelinkerd-controller,linkerd-proxy-injector,linkerd-webhooks, etc.). You can adjust this number based on your availability needs.--set controller.deploymentStrategy.type=RollingUpdate: Ensures that updates to the control plane are performed gradually, minimizing downtime.--set controller.deploymentStrategy.rollingUpdate.maxUnavailable=1: During an update, only one replica can be unavailable at a time.--set controller.deploymentStrategy.rollingUpdate.maxSurge=1: During an update, one extra replica can be created beyond the desired count.--set identity.replicas=3,--set heartbeat.replicas=3, etc.: These flags ensure that other critical components like the identity service, heartbeat, Prometheus, Grafana, ingress controller, tap, webhooks, proxy injector, and diagnostic components also run with multiple replicas. This is crucial for overall control plane HA.--set certManager.enabled=false,--set clusterIssuer.enabled=false: If you are managing your certificates with an external CA or have other mechanisms in place, you might disable Linkerd’s built-in cert-manager integration. For HA, ensure your chosen certificate management solution is also highly available.--set identityContext.*: These are important for configuring the TLS certificates used by the control plane. Ensuring all necessary Service DNS names and cluster IPs are included in the Subject Alternative Names (SANs) for the control plane’s identity is vital for inter-component communication. The provided example includes a comprehensive set of SANs derived from common Linkerd service names.
Step 3: Verify the Installation
After applying the configuration, check that all control plane pods are running and have multiple replicas.
kubectl get pods -n linkerd -o wide
You should see multiple pods for components like linkerd-controller, linkerd-proxy-injector, etc., spread across your Kubernetes nodes.
How it Works Mechanically
By running multiple replicas of each control plane component, Kubernetes’s scheduler and controller manager ensure that if one pod or node fails, other replicas can continue to serve traffic. The Service objects for these components will automatically direct traffic to the healthy replicas. For instance, if linkerd-controller has 3 replicas, and one pod dies, the Kubernetes Service linkerd-controller.linkerd.svc.cluster.local will still have two healthy endpoints to route requests to.
The RollingUpdate strategy for deployments ensures that when you upgrade Linkerd, you don’t experience a complete outage. New pods are brought up before old ones are torn down, allowing for a smooth transition.
The Next Hurdle
Once your control plane is highly available, you’ll likely encounter issues related to the data plane. The next challenge is ensuring the Linkerd proxies within your application pods are resilient and can gracefully handle control plane unavailability.