← Back to DevBytes

Troubleshooting Linkerd Service Mesh: Common Issues and Fixes

Introduction to Troubleshooting Linkerd Service Mesh

Linkerd is a lightweight, CNCF-graduated service mesh designed to add reliability, security, and observability to Kubernetes workloads without requiring application code changes. Built on the Rust-based linkerd2-proxy, it operates as a transparent sidecar that intercepts traffic between services. While Linkerd is celebrated for its simplicity compared to alternatives like Istio, production deployments still encounter issues that require methodical troubleshooting.

This tutorial walks through the most common Linkerd problems — from installation failures to mTLS misconfigurations, traffic routing anomalies, and performance bottlenecks — and provides actionable fixes for each. Whether you are running Linkerd in staging or managing a multi-cluster production mesh, these techniques will help you diagnose and resolve issues quickly.

Why Troubleshooting Linkerd Matters

A service mesh sits on the critical path of every request in your cluster. When something breaks, the symptoms can be subtle: increased latency, intermittent 503 errors, failed mTLS handshakes, or missing telemetry. Because Linkerd injects sidecars transparently, developers often do not realize the mesh is involved until an outage occurs.

Effective troubleshooting matters because:

Essential Diagnostic Tooling

Before diving into specific issues, ensure you have the right tools available. The linkerd CLI is your primary interface, supplemented by kubectl and standard Linux utilities.

Verifying CLI and Cluster Connectivity

Start every troubleshooting session by confirming that your CLI version matches the control plane version. Version mismatches are a frequent source of confusing errors.

# Check CLI version
linkerd version

# Check control plane version
linkerd check

# Run a comprehensive health check
linkerd check --output wide

The linkerd check command is the single most valuable diagnostic tool. It validates the control plane, data plane, and configuration in one pass. Pay close attention to warnings, as they often precede failures.

Inspecting the Control Plane

# List control plane components
kubectl get pods -n linkerd

# Check control plane logs
kubectl logs -n linkerd deploy/linkerd-destination -f
kubectl logs -n linkerd deploy/linkerd-identity -f
kubectl logs -n linkerd deploy/linkerd-proxy-injector -f

# View control plane resources
kubectl get all -n linkerd

Common Issue 1: Sidecar Injection Failures

The most frequent Linkerd issue is pods running without the linkerd-proxy sidecar. Without injection, the pod cannot participate in mTLS, retries, or telemetry collection.

Symptoms

Diagnosis

# Check if namespace has injection enabled
kubectl get namespace -o jsonpath='{.metadata.annotations.linkerd\.io/inject}'

# Verify the pod has two containers
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'

# Check the injector logs for errors
kubectl logs -n linkerd deploy/linkerd-proxy-injector | grep -i error

Fixes

If injection is not enabled on the namespace, annotate it and restart the pods:

# Enable injection on a namespace
kubectl annotate namespace <namespace> linkerd.io/inject=enabled

# Restart deployments to trigger injection
kubectl rollout restart deployment -n <namespace>

# Alternatively, inject manually for testing
kubectl get deployment <name> -n <namespace> -o yaml | linkerd inject - | kubectl apply -f -

If injection is enabled but pods still lack the sidecar, check for webhook failures. The mutating webhook requires TLS and proper certificate rotation. Verify the injector certificate:

# Check webhook configuration
kubectl get mutatingwebhookconfiguration linkerd-proxy-injector-webhook-config -o yaml

# Verify the injector certificate validity
kubectl get secret linkerd-proxy-injector-k8s-tls -n linkerd -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates

Common Issue 2: mTLS and Identity Problems

Linkerd uses automatic mTLS between meshed workloads. The linkerd-identity component issues short-lived certificates. When identity fails, you will see connection errors and TLS handshake failures.

Symptoms

Diagnosis

# Run the identity check
linkerd check --proxy

# Inspect a specific proxy's identity
linkerd diagnostics proxy <pod-name> -n <namespace>

# Check identity controller logs
kubectl logs -n linkerd deploy/linkerd-identity -f | grep -i error

# Verify the trust anchor
kubectl get secret linkerd-identity-issuer -n linkerd -o jsonpath='{.data.crt\.pem}' | base64 -d | openssl x509 -noout -text

Fixes

If the trust anchor has expired, you must rotate credentials. Linkerd supports external issuers via cert-manager or a built-in self-signed issuer:

# Check current issuer configuration
kubectl get configmap linkerd-config -n linkerd -o yaml | grep -A 10 identity

# Rotate the identity issuer with cert-manager (if installed)
linkerd upgrade --identity-external-issuer | kubectl apply -f -

# If using the built-in issuer, regenerate credentials
step certificate create root.linkerd.cluster.local ca.crt ca.key \
  --profile root-ca --no-password --insecure

step certificate create identity.linkerd.cluster.local issuer.crt issuer.key \
  --profile intermediate-ca --not-after 8760h --no-password --insecure \
  --ca ca.crt --ca-key ca.key

# Apply the new trust anchor and issuer
kubectl create secret generic linkerd-identity-issuer \
  --from-file=ca.crt=ca.crt \
  --from-file=issuer.crt=issuer.crt \
  --from-file=issuer.key=issuer.key \
  -n linkerd --dry-run=client -o yaml | kubectl apply -f -

# Restart identity and proxies
kubectl rollout restart deploy/linkerd-identity -n linkerd
kubectl rollout restart daemonset/linkerd-cni -n linkerd-cni

Common Issue 3: Connectivity and Routing Errors

When meshed services cannot communicate, the problem often lies in the destination service, DNS resolution, or service profile configuration.

Symptoms

Diagnosis

# Tap into live traffic to see what is happening
linkerd viz tap deploy/<deployment> -n <namespace>

# View top routes and their error rates
linkerd viz routes deploy/<deployment> -n <namespace>

# Check the destination controller
kubectl logs -n linkerd deploy/linkerd-destination -f

# Inspect the service profile
kubectl get serviceprofile -n <namespace>
kubectl describe serviceprofile <name> -n <namespace>

Fixes

If the destination controller is overloaded, scale it up:

# Scale the destination controller
kubectl scale deploy/linkerd-destination -n linkerd --replicas=3

# Verify it is healthy
kubectl get pods -n linkerd -l linkerd.io/control-plane-component=destination

If service profiles are misconfigured, retries and timeouts will not work. Generate a profile from OpenAPI or create one manually:

# Generate a service profile from an OpenAPI spec
linkerd profile --openapi swagger.json <service-name> -n <namespace> | kubectl apply -f -

# Create a manual service profile with retry policy
cat <<'EOF' | kubectl apply -f -
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: orders-service.default.svc.cluster.local
  namespace: default
spec:
  routes:
  - name: GET /orders
    condition:
      method: GET
      pathRegex: /orders
    retryBudget:
      retryRatio: 0.2
      minRetriesPerSecond: 10
      ttl: 10s
  - name: POST /orders
    condition:
      method: POST
      pathRegex: /orders
    timeout: 5s
EOF

Common Issue 4: Missing or Incomplete Metrics

Linkerd's Prometheus instance scrapes proxy metrics. If the dashboard shows no data, the issue is usually with scraping, the metrics API, or the viz extension.

Diagnosis

# Check the viz extension
linkerd viz check

# Verify Prometheus is running and scraping
kubectl get pods -n linkerd-viz
kubectl logs -n linkerd-viz deploy/prometheus -f | grep -i error

# Query Prometheus directly
kubectl -n linkerd-viz port-forward deploy/prometheus 9090:9090
# Then open http://localhost:9090 and query: up{job="linkerd-proxy"}

# Check the metrics API
kubectl get apiservice | grep linkerd

Fixes

If Prometheus is not scraping proxies, verify the scrape configuration and pod annotations:

# Check if pods have the required annotations
kubectl get pod <pod-name> -o jsonpath='{.metadata.annotations}'

# Restart the viz extension
linkerd viz install | kubectl apply -f -

# If using an external Prometheus, ensure it scrapes the proxy admin port (4191)
# Add this to your Prometheus scrape config:
# - job_name: 'linkerd-proxy'
#   kubernetes_sd_configs:
#   - role: pod
#   relabel_configs:
#   - source_labels: [__meta_kubernetes_pod_container_name]
#     action: keep
#     regex: ^linkerd-proxy$
#   - source_labels: [__meta_kubernetes_pod_annotation_linkerd_io_admin_port]
#     action: keep
#     regex: .+
#   - source_labels: [__address__, __meta_kubernetes_pod_annotation_linkerd_io_admin_port]
#     action: replace
#     regex: ([^:]+)(?::\d+)?;(\d+)
#     replacement: $1:$2
#     target_label: __address__

Common Issue 5: High Resource Consumption

Under high load, the Linkerd proxy and control plane can consume significant CPU and memory. Tuning resource limits is essential for production stability.

Diagnosis

# Check proxy resource usage
kubectl top pod <pod-name> -n <namespace> --containers

# Check control plane resource usage
kubectl top pods -n linkerd

# Inspect proxy memory and connection stats
linkerd diagnostics proxy-metrics <pod-name> -n <namespace> | grep -E "process_|inbound|outbound"

Fixes

Adjust proxy resource requests and limits via the proxy config annotation:

# Patch a deployment with custom proxy resources
kubectl patch deployment <name> -n <namespace> --type=json -p='[
  {
    "op": "add",
    "path": "/spec/template/metadata/annotations/config.linkerd.io~1proxy-cpu-request",
    "value": "100m"
  },
  {
    "op": "add",
    "path": "/spec/template/metadata/annotations/config.linkerd.io~1proxy-cpu-limit",
    "value": "500m"
  },
  {
    "op": "add",
    "path": "/spec/template/metadata/annotations/config.linkerd.io~1proxy-memory-request",
    "value": "64Mi"
  },
  {
    "op": "add",
    "path": "/spec/template/metadata/annotations/config.linkerd.io~1proxy-memory-limit",
    "value": "256Mi"
  }
]'

# Or set defaults globally via the linkerd-config ConfigMap
kubectl edit configmap linkerd-config -n linkerd

You can also tune the proxy's connection pool and worker thread count:

# Set proxy worker threads (defaults to number of CPUs)
kubectl annotate deployment <name> -n <namespace> config.linkerd.io/proxy-cpu-limit=1

# Disable admin port metrics if not needed (reduces overhead)
kubectl annotate deployment <name> -n <namespace> config.linkerd.io/disable-identity=false

Common Issue 6: CNI Plugin Conflicts

Linkerd offers a CNI plugin mode that handles traffic redirection at the CNI layer instead of using iptables in an init container. This can conflict with other CNI plugins like Calico or Cilium.

Symptoms

Diagnosis and Fixes

# Check CNI plugin status
kubectl get pods -n linkerd-cni

# Verify the CNI plugin is installed on nodes
kubectl debug node/<node-name> -- ls /opt/cni/bin/

# Check CNI configuration order
kubectl debug node/<node-name> -- cat /etc/cni/net.d/10-linkerd-cni.conf

# If conflicts exist, ensure linkerd-cni runs last in the plugin chain
# Reinstall with proper chaining
linkerd install-cni --dest-cni-bin-dir=/opt/cni/bin \
  --dest-cni-net-dir=/etc/cni/net.d | kubectl apply -f -

Best Practices for Linkerd Operations

1. Always Run linkerd check After Changes

Make linkerd check part of your CI/CD pipeline and post-deployment verification. It catches configuration drift before it causes outages.

# Integrate into CI
linkerd check --expected-version <your-version> --wait=60s

2. Use Resource Profiles Proactively

Do not wait for performance issues. Set conservative resource requests on the proxy from day one and adjust based on observed usage.

3. Monitor the Control Plane

The control plane components (identity, destination, proxy injector) are single points of failure. Monitor their health and scale them for HA:

# Enable HA mode during installation
linkerd install --ha | kubectl apply -f -

# Or upgrade an existing installation
linkerd upgrade --ha | kubectl apply -f -

4. Rotate Certificates Before Expiry

Set up alerts on certificate expiry. The default trust anchor lifetime is one year, and issuer certificates rotate automatically, but the trust anchor does not.

# Check certificate expiry
kubectl get secret linkerd-identity-issuer -n linkerd -o jsonpath='{.data.crt\.pem}' \
  | base64 -d | openssl x509 -noout -enddate

5. Use Tap Sparingly in Production

The linkerd viz tap command is powerful for debugging but adds overhead and exposes request data. Restrict its use to break-glass scenarios and use RBAC to limit access.

6. Keep CLI and Control Plane Versions Aligned

Always upgrade the CLI and control plane together. Running mismatched versions can produce misleading check results and unexpected behavior.

# Upgrade both together
linkerd upgrade | kubectl apply -f -
linkerd viz upgrade | kubectl apply -f -

Advanced Debugging Techniques

Proxy Admin Endpoints

Each Linkerd proxy exposes an admin server on port 4191 with detailed diagnostics:

# Port-forward to a proxy's admin port
kubectl port-forward pod/<pod-name> 4191:4191 -n <namespace>

# Useful admin endpoints:
# /metrics       - Prometheus metrics
# /ready         - Readiness probe
# /config        - Current proxy configuration
# /stats         - Live connection stats
# /servers       - Inbound/outbound server info
# /certs         - TLS certificate details

curl http://localhost:4191/config
curl http://localhost:4191/stats

Debug Container

For network-level debugging, attach a debug container to a pod:

# Add an ephemeral debug container
kubectl debug -it pod/<pod-name> -n <namespace> --image=nicolaka/netshoot --target=linkerd-proxy

# Inside the debug container, test connectivity
curl -v http://<service-name>.<namespace>.svc.cluster.local:8080
nslookup <service-name>.<namespace>.svc.cluster.local
tcpdump -i eth0 -n port 4191

Collecting Diagnostics for Support

# Generate a full diagnostic bundle
linkerd diagnostics --output bundle.tar.gz

# This includes:
# - Control plane logs
# - Proxy configs
# - Cluster state
# - Linkerd configuration
# Share this with the Linkerd community or Buoyant support

Conclusion

Troubleshooting Linkerd effectively requires understanding its architecture: the control plane components (identity, destination, proxy injector), the data plane sidecars, and the viz extension for observability. By mastering linkerd check, proxy admin endpoints, and the linkerd viz tap and routes commands, you can diagnose the vast majority of issues without deep kernel-level debugging. The most common problems — sidecar injection failures, mTLS certificate expiry, destination controller overload, and missing metrics — all have well-established diagnostic paths and fixes. Adopting the best practices of running HA mode, proactively managing resources, rotating certificates before expiry, and keeping CLI and control plane versions aligned will prevent most issues before they impact production. Remember that Linkerd's design philosophy is simplicity, and when troubleshooting feels overly complex, the root cause is often a configuration mismatch rather than a deep system bug — start with linkerd check and work outward from there.

— Ad —

Google AdSense will appear here after approval

← Back to all articles