Skip to content

Linkerd

Who this page is for: in plain English, Linkerd is a service mesh - it puts a small proxy next to every pod so that traffic between your services is encrypted, retried, and measured without changing application code. This page is production depth, aimed at readers already comfortable with Pods, Services, and Deployments. New to Kubernetes? Start with the Beginner track.

Linkerd is a CNCF graduated service mesh built around a single idea: a mesh should do a small number of things - mTLS, retries/timeouts, and golden metrics - and do them with as little operational surface area as possible. It was the first project in the "service mesh" category and remains the reference point for how small a mesh's footprint can be.

Where Istio treats the mesh as a platform for arbitrarily rich L7 policy, Linkerd treats the mesh as infrastructure that should be nearly invisible: install it, and every meshed service gets mTLS and metrics with zero configuration. That minimalism is a deliberate trade-off, not a missing feature set, and it's the reason teams choose Linkerd over Istio as often as they do.

A distribution note worth knowing before you install: since early 2024, Buoyant (Linkerd's steward and primary maintainer) no longer ships "stable" release artifacts from the open source project directly. The Linkerd source remains Apache 2.0 and CNCF-governed, and free "edge" releases (cut roughly every 10 days from main) remain available on GitHub, but a production-grade stable build now comes from Buoyant Enterprise for Linkerd (BEL) - free to download, with a paid license required for production use at companies above a certain size after a grace period. This prompted a CNCF TOC governance review; it hasn't changed Linkerd's CNCF graduated status, but it's a real factor in how teams plan an installation.

Architecture

flowchart TD
    subgraph Control Plane
        Destination["destination\n(service discovery, policy)"]
        Identity["identity\n(CA, cert issuance)"]
        Proxy_Injector["proxy-injector\n(admission webhook)"]
    end
    subgraph Pod A
        AppA[App container] <--> ProxyA["linkerd2-proxy\n(Rust)"]
    end
    subgraph Pod B
        AppB[App container] <--> ProxyB["linkerd2-proxy\n(Rust)"]
    end
    Destination -- discovery + policy --> ProxyA
    Destination -- discovery + policy --> ProxyB
    Identity -- short-lived certs --> ProxyA
    Identity -- short-lived certs --> ProxyB
    ProxyA <-- mTLS --> ProxyB

destination serves service discovery and routing/policy information to every proxy over a gRPC API - the closest analog to Istio's Pilot, but with a far smaller configuration surface.

identity is the certificate authority. It issues a short-lived TLS certificate to every proxy at pod startup and handles rotation, so there is no manual certificate management anywhere in the system.

proxy-injector is a mutating admission webhook that adds the linkerd2-proxy sidecar (and an linkerd-init container to set up iptables redirection) to any pod in a meshed namespace.

Sidecar injection

Namespaces get meshed with an annotation, not a label:

kubectl annotate namespace my-app linkerd.io/inject=enabled

Or annotate individual workloads:

metadata:
  annotations:
    linkerd.io/inject: enabled   # or "disabled"

Recent Linkerd releases inject the proxy as a native sidecar container - a Kubernetes init container with restartPolicy: Always - rather than as a regular container, once the cluster is on a Kubernetes version where native sidecars are available. Because a native sidecar is started and made ready in init-container order, init containers declared after the proxy in the init sequence get network access through the mesh, which wasn't reliably true under the older sidecar model. Ordering within the init container list still governs: an init container declared before the proxy still runs without the mesh.

Whether your install defaults to native sidecars depends on the release and channel you're on. Given the distribution split described above, check the release notes for the specific edge, stable, or Buoyant Enterprise build you're installing rather than assuming a version number - and confirm with kubectl get pod -o jsonpath='{.spec.initContainers[*].name}' on a meshed pod.

Why a purpose-built proxy instead of Envoy

Istio, Cilium's service mesh mode, and most other meshes use Envoy, a general-purpose C++ proxy with a huge feature surface (extensions, Lua/WASM filters, dozens of load-balancing algorithms). Linkerd instead ships linkerd2-proxy, a proxy written from scratch in Rust and scoped specifically to what a sidecar needs to do for HTTP/1.1, HTTP/2, gRPC, and TCP traffic in a mesh.

The reasoning:

  • Memory safety without a GC - Rust eliminates the class of memory-corruption bugs that come with C/C++ proxies, without the latency variance a garbage-collected language would introduce.
  • Smaller attack and config surface - linkerd2-proxy doesn't expose a general-purpose extension mechanism, so there's less to misconfigure and less to audit.
  • Lower resource footprint - fewer features to load and no general-purpose config-processing machinery means a lighter runtime.

This is a deliberate trade-off: linkerd2-proxy can't do arbitrary WASM filters or the long tail of Envoy's protocol support. In exchange, Linkerd's data plane is smaller, easier to reason about, and cheaper to run per-pod.

Automatic mutual TLS

This is Linkerd's signature feature. mTLS is on by default for all meshed HTTP, HTTP/2, and gRPC traffic - there is no PeerAuthentication resource to write, no STRICT/PERMISSIVE mode to choose.

Every pod gets a workload identity, structured like a SPIFFE identity, derived from its Kubernetes ServiceAccount:

<service-account>.<namespace>.serviceaccount.identity.linkerd.cluster.local

At pod startup, linkerd2-proxy generates a key pair and requests a certificate from the identity controller, which acts as the mesh's CA. Certificates are short-lived (24-hour default) and rotated automatically in the background - there is no cert-manager integration required for this inner loop, though you can bring your own trust anchor for the CA itself.

# Confirm mTLS is active between two meshed pods
linkerd viz edges deployment -n my-app

# Inspect a proxy's identity
linkerd identity -n my-app deploy/checkout

Because identity is tied to the ServiceAccount rather than IP, policy and observability stay correct across pod restarts and rescheduling.

The linkerd CLI and linkerd check

The linkerd CLI is widely regarded as one of the best pieces of mesh UX in the ecosystem, centered on linkerd check, a preflight and postflight validator that runs a battery of cluster and mesh health checks and reports exactly what's wrong and how to fix it.

# Before installing: verify cluster prerequisites
linkerd check --pre

# Install the control plane (CRDs first, then control plane)
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -

# After installing: verify control plane health
linkerd check

# After meshing workloads: verify data plane health
linkerd check --proxy

linkerd check catches problems that would otherwise surface as confusing runtime failures: missing RBAC, clock skew that breaks cert validation, conflicting CNI plugins, expired trust anchors, proxies running a version that's skewed too far from the control plane. Every failure links to a docs page explaining the specific check and remediation. This preflight discipline is baked into upgrades too - always run linkerd check before and after any linkerd upgrade.

For production installs, Helm (the linkerd-crds chart followed by linkerd-control-plane) is the recommended path over the raw linkerd install CLI shown above, since it gives you repeatable, version-controlled installs; the CLI remains the quickest way to get started and to run linkerd check against whatever you've installed.

Golden metrics without instrumentation

Every meshed service automatically gets the three "golden metrics" - success rate, request volume, and latency percentiles - with zero application code changes, because linkerd2-proxy records them for every request it proxies.

# Live, top-style view of golden metrics per pod
linkerd viz stat deploy -n my-app

# Per-route metrics (requires a ServiceProfile for route grouping)
linkerd viz routes deploy/checkout -n my-app

# Real-time tap of live requests
linkerd viz tap deploy/checkout -n my-app

Under the hood, linkerd-viz (an optional add-on) scrapes Prometheus-formatted metrics that every proxy exposes - request_total, response_latency_ms_bucket, and similar series labeled by direction, deployment, and route. These feed the CLI, the Linkerd dashboard, and can be scraped by your own Prometheus instead of the bundled one.

Traffic policy: Gateway API and the legacy ServiceProfile

Linkerd's routing model is intentionally narrower than Istio's VirtualService/DestinationRule pair. As of Linkerd 2.16, Gateway API's HTTPRoute/GRPCRoute reached feature parity with ServiceProfile for retries, timeouts, and per-route metrics, and that's now where active development happens - ServiceProfile is retained only for backward compatibility and receives no new features. New deployments should configure retries, timeouts, and route metrics on HTTPRoute/GRPCRoute directly rather than writing new ServiceProfile resources.

ServiceProfile (legacy) - retries, timeouts, per-route metrics

A ServiceProfile declares the routes a service exposes and per-route behavior. It doesn't do traffic splitting - its job is retries, timeouts, and giving linkerd viz routes something to group metrics by. Note that for backward compatibility, if a ServiceProfile exists for a Service, proxies use it in preference to any HTTPRoute configuring the same Service - so a stray legacy ServiceProfile can silently shadow newer Gateway API configuration.

apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
  name: checkout.my-app.svc.cluster.local
  namespace: my-app
spec:
  routes:
    - name: POST /orders
      condition:
        method: POST
        pathRegex: /orders
      timeout: 3s
      isRetryable: true
    - name: GET /orders/{id}
      condition:
        method: GET
        pathRegex: /orders/[^/]+
      timeout: 1s
      isRetryable: true
  retryBudget:
    retryRatio: 0.2
    minRetriesPerSecond: 10
    ttl: 10s

isRetryable only takes effect for idempotent-safe routes you explicitly mark - Linkerd will not guess. The retryBudget caps how much retry traffic the mesh will generate as a ratio of original request volume, preventing retry storms from amplifying an outage.

Traffic splitting for canaries

Weighted traffic splitting is done with Gateway API HTTPRoute (the current, recommended path). The older SMI TrafficSplit CRD and the linkerd-smi extension that provided it are now deprecated and slated for removal in a future release - new canary/weighting setups should use HTTPRoute weighted backendRefs rather than TrafficSplit:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: checkout
  namespace: my-app
spec:
  parentRefs:
    - name: checkout-svc
      kind: Service
      group: core
  rules:
    - backendRefs:
        - name: checkout-stable
          port: 80
          weight: 90
        - name: checkout-canary
          port: 80
          weight: 10

Combine this with linkerd viz stat on the canary backend to watch success rate and latency before shifting more weight - this is the basis for automated canary tools like Flagger, which drive the weight adjustments off exactly these golden metrics.

Multi-cluster

Linkerd supports multi-cluster communication over mTLS-encrypted gateways, without requiring a flat shared network between clusters:

linkerd multicluster install | kubectl apply -f -
linkerd multicluster link --cluster-name cluster-west | kubectl apply --context=cluster-east -f -
linkerd multicluster check

Linked services appear locally suffixed with the remote cluster name (checkout-cluster-west) rather than transparently mirroring the local name - an explicit, discoverable naming choice rather than magic DNS rewriting.

Beyond gateway-based mirroring, Linkerd also supports pod-to-pod multi-cluster communication on flat networks (where cross-cluster traffic goes directly pod-to-pod instead of through a gateway) and federated services, which merge endpoints from multiple clusters behind one virtual service for active-active failover without the gateway hop. A single Link resource is either gateway-mode or flat-mode; teams needing both against the same pair of clusters create two separate links.

Linkerd vs. Istio

Dimension Linkerd Istio
Control plane Single small binary set (destination, identity, proxy-injector) istiod consolidates Pilot/Citadel/Galley; ambient mode adds ztunnel/waypoints
Data plane proxy linkerd2-proxy (Rust, purpose-built) Envoy (C++, general-purpose)
Resource footprint Low - proxy is minimal by design Higher, though ambient mode narrows the gap
mTLS On by default, zero YAML to enable Opt-in enforcement via PeerAuthentication, more knobs
Traffic policy model Gateway API HTTPRoute/GRPCRoute (retries/timeouts/weighting; current) + legacy ServiceProfile (retries/timeouts, backward-compat only) VirtualService + DestinationRule (routing, subsets, circuit breaking, outlier detection)
L7 feature depth Deliberately narrow Broad (fault injection, header manipulation, complex routing, WASM filters)
Multi-cluster Gateway-based, explicit naming Multiple topologies (multi-primary, primary-remote)
Operational UX linkerd check preflight/postflight validation istioctl analyze, istioctl proxy-config
CNCF status Graduated Graduated

Decision framework

Choose Linkerd when the primary goal is mTLS everywhere, low operational overhead, and clear golden metrics, and you don't need deep L7 traffic-shaping features. Teams that want "flip it on and forget it" tend to land here.

Choose Istio when you need rich L7 policy - fault injection for chaos testing, complex header-based routing, WASM extensibility, fine-grained circuit breaking - or you're already invested in the broader Istio/Envoy ecosystem (Envoy Gateway, ambient mesh, etc.) and want one proxy technology across ingress and mesh.

Both are graduated CNCF projects and both are production-proven at large scale - this is a genuine trade-off between simplicity and feature breadth, not a maturity gap.

Common mistakes

  • Meshing the control plane's own namespace. Injecting linkerd2-proxy into linkerd-namespace workloads (or Prometheus/Grafana add-ons) can create bootstrapping and scraping issues. Leave infrastructure namespaces unmeshed unless you have a specific reason.
  • Skipping linkerd check before upgrades. Most "the mesh broke after upgrade" incidents are caught in advance by linkerd check --pre - expired trust anchors and version skew are the most common culprits.
  • Writing new ServiceProfile resources instead of HTTPRoute. ServiceProfile is retained for backward compatibility only and gets no new features; it also only handles retries/timeouts/route metrics, never weighting. New configuration should target HTTPRoute/GRPCRoute, and any legacy ServiceProfile left in place for a Service will silently take precedence over an HTTPRoute configuring the same Service.
  • Marking non-idempotent routes as retryable. isRetryable: true on a POST /orders without idempotency keys can double-submit orders under retry. Only mark routes retryable when the backend safely tolerates duplicate execution.
  • Forgetting the trust anchor rotation window. The default Linkerd CA trust anchor has a long but finite lifetime; if it expires without rotation, every proxy in the mesh loses the ability to establish mTLS simultaneously. Track and rotate it well before expiry.
  • Assuming linkerd2-proxy supports arbitrary Envoy-style extensions. If a design depends on WASM filters or exotic protocol support, that's a signal to reach for Istio instead of trying to bolt equivalent behavior onto Linkerd.
  • Istio - the other graduated mesh, and the main alternative you'll be choosing between
  • Cilium - eBPF-based mesh mode, a third option with no per-pod proxy
  • Prometheus - where Linkerd's golden metrics land
  • Networking Overview - how mesh traffic relates to the underlying CNI
  • Services and Networking - the Service abstraction Linkerd routes on top of
  • Troubleshooting - general debugging when linkerd check comes back clean