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:
Or annotate individual workloads:
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:
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-proxyintolinkerd-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 checkbefore upgrades. Most "the mesh broke after upgrade" incidents are caught in advance bylinkerd check --pre- expired trust anchors and version skew are the most common culprits. - Writing new
ServiceProfileresources instead ofHTTPRoute.ServiceProfileis retained for backward compatibility only and gets no new features; it also only handles retries/timeouts/route metrics, never weighting. New configuration should targetHTTPRoute/GRPCRoute, and any legacyServiceProfileleft in place for a Service will silently take precedence over anHTTPRouteconfiguring the same Service. - Marking non-idempotent routes as retryable.
isRetryable: trueon aPOST /orderswithout 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.
Source Links¶
- Linkerd documentation
- linkerd2 on GitHub and its releases
- linkerd2-proxy (the Rust data plane)
- Linkerd automatic mTLS
- Linkerd HTTPRoute and retries/timeouts
- Linkerd multi-cluster communication
- Buoyant blog (Linkerd stewardship and stable-release policy)
- CNCF project page: Linkerd
- Kubernetes: sidecar containers (native sidecars)
Related Concepts¶
- 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 checkcomes back clean