Linkerd
Linkerd is the deliberately small service mesh: you add one annotation to a namespace, and from that moment every request between your services is mutually authenticated, encrypted, latency-aware load balanced, and reported as golden metrics — without a single line of application code, a sidecar you have to configure, or a YAML dialect you have to learn. It solves the platform problem that security, reliability and observability of service-to-service traffic should be a property of the substrate rather than a thing each of forty teams reimplements badly in their own language.
Imagine every kid in a big school gets a tiny, super-fast helper who walks beside them everywhere. When you pass a note to a friend, your helper seals it in an envelope only your friend’s helper can open, checks that the person taking it really is your friend (not someone in a costume), picks the shortest line to hand it over, and writes down “note delivered, took 4 milliseconds, no problems.” You never learn how envelopes or ID checks work — you just write notes. Linkerd is those helpers. And the reason people like it is that the helper is tiny: it fits in a pocket, it never asks you questions, and it starts sealing envelopes the second it shows up.
What Linkerd is and the problem it solves
☺ Like you’re 10: A helper that lives next to every app and quietly does the hard, boring, dangerous parts of talking over a network.
Linkerd is a CNCF-graduated service mesh — the first service mesh to graduate, and the original project that gave the category its name. Like every mesh it works by putting a proxy next to each application container, transparently intercepting all inbound and outbound TCP traffic, and moving a family of cross-cutting network concerns out of your app and into that proxy. What distinguishes Linkerd from its neighbours is not what it does but how much: it is unapologetically opinionated, and it treats every configuration knob it does not ship as a feature.
The problem: the same five hard things, in every service
As the Networking & Service Mesh lesson sets out, once you have a few dozen services calling each other, an identical list of concerns appears in every one: encrypt this connection and verify who is on the other end; retry idempotent failures; give up after a sensible timeout; balance across replicas by something smarter than round-robin; emit consistent success-rate and latency metrics. Solving that in application code means solving it in Go and Java and Python and Node, then keeping four libraries in sync forever. It is the archetypal platform problem — universal, undifferentiated, and easy to get subtly wrong.
The Linkerd bargain: fewer knobs, on by default
Linkerd’s answer is a small number of behaviours that are already correct before you configure anything. Install it, mesh a namespace, and you immediately have mutual TLS on all meshed TCP traffic, latency-aware load balancing, per-route golden metrics and transparent proxying — with no policy files written. Everything beyond that (retries, timeouts, authorization, traffic splitting) is opt-in and deliberately narrow. The trade is real: you cannot express arbitrary L7 routing gymnastics the way you can in Istio. In exchange, the thing you install on Monday is still comprehensible on Friday.
The one-line contrast the exam wants: Istio is maximal configurability on Envoy; Linkerd is minimal configurability on a purpose-built Rust micro-proxy, with mTLS on by default. If you remember one sentence about Linkerd, remember that its data plane is not Envoy — linkerd2-proxy is written in Rust specifically to be small and fast, typically tens of megabytes of memory and sub-millisecond added latency per hop, rather than the hundred-plus megabytes a general-purpose Envoy sidecar tends to want.
What Linkerd is not
Linkerd is not an ingress controller — it meshes traffic inside the cluster and expects NGINX, Envoy Gateway or a Gateway API implementation at the edge. It is not a CNI plugin: it rides on whatever pod network Cilium, Calico or your cloud provider gives you, and it does not replace NetworkPolicy (L3/L4 and IP-based, where Linkerd policy is L7 and identity-based). And it is not a progressive-delivery controller: it provides the traffic-splitting mechanism that Flagger or Argo Rollouts drive, but never decides on its own to promote or roll back.
Where Linkerd fits in a platform
☺ Like you’re 10: It sits underneath everything your apps say to each other — below your apps, above the plain network.
Which plane it serves
Linkerd is genuinely both planes at once. Its control plane is a handful of Deployments in the linkerd namespace that issue certificates, answer service-discovery questions and mutate pods. Its data plane is the fleet of linkerd-proxy sidecars, where every actual byte flows. In the platform architecture layering it belongs to the networking and runtime-security domain: a substrate capability the platform team owns and application teams consume without knowing it is there.
Its neighbours
- Underneath: the CNI and the flat pod network — see Networking. Linkerd needs working pod-to-pod IP connectivity before it can secure it.
- Alongside: Prometheus, which scrapes the proxies. The
linkerd vizextension bundles its own short-retention Prometheus; in production you point it at yours — see Observability. - Above: Flagger and Argo Rollouts, which write traffic weights into Linkerd’s routing resources to run canaries.
- Adjacent: cert-manager, the standard way to rotate Linkerd’s issuer certificate automatically — the single most valuable integration you can add.
- Governed by: Security & Policy. Mesh identity is how you get from “default-allow inside the cluster” to real zero-trust authorization.
CNPE domain relevance
Linkerd is on the official CNPE tool list. It lands squarely in the infrastructure and networking competencies — service mesh, service-to-service security, and observability of the platform’s own traffic — and it touches the security domain through workload identity and authorization policy, plus the delivery domain as the traffic layer beneath progressive delivery. The exam is far more likely to test what a mesh gives you and what it costs than to ask you to recall an obscure field name.
How Linkerd works
☺ Like you’re 10: A few helpers-in-chief hand out ID cards and directions; a tiny helper next to each app does the actual talking.
The control plane: three components, that’s it
A default install puts a small, memorable set of Deployments in the linkerd namespace:
| Component | What it does | What breaks if it is down |
|---|---|---|
linkerd-identity | A certificate authority. Validates each proxy’s Kubernetes ServiceAccount token and issues it a short-lived TLS leaf certificate (24h by default) whose identity encodes the service account — e.g. checkout.prod.serviceaccount.identity.linkerd.cluster.local. | Existing proxies keep running until their leaf expires; new pods cannot get an identity and fail to become ready. |
linkerd-destination | Service discovery and policy. Tells each proxy the endpoints behind a Service, which routes/retries/timeouts apply (from ServiceProfile and HTTPRoute), and what authorization policy governs each inbound port. | Proxies use cached state; new endpoints and policy changes stop propagating. |
linkerd-proxy-injector | A mutating admission webhook. Sees the linkerd.io/inject: enabled annotation and rewrites the pod spec to add the proxy sidecar and the init container that sets up iptables. | New pods are created unmeshed — silently, which is the nasty part. |
Extensions add more: linkerd viz (metrics, tap, dashboard), linkerd multicluster (gateway and service mirroring), linkerd jaeger (trace collection), and historically linkerd smi (the SMI TrafficSplit CRD, which moved out of core and has since been superseded by Gateway API HTTPRoute). Each installs separately into its own namespace — a good example of keeping the mandatory core small and the optional parts optional.
The data plane: a micro-proxy, not Envoy
linkerd2-proxy is a purpose-built L7 proxy written in Rust. Because it exists only to serve Linkerd, it carries none of Envoy’s general-purpose configuration surface — there is no equivalent of writing raw Envoy filters, and that is the point. An init container (or the optional linkerd-cni plugin, if you would rather not grant NET_ADMIN to init containers) programs iptables so inbound traffic lands on port 4143 and outbound leaves via 4140, with an admin/metrics endpoint on 4191. The application container is untouched and unaware.
Load balancing is a real differentiator. Linkerd balances using EWMA — an exponentially weighted moving average of observed latency per endpoint — so it continuously prefers the replicas answering fastest, rather than spraying requests round-robin at a pod that happens to be garbage-collecting. For HTTP/2 and gRPC it balances per request rather than per connection, which is exactly where naive kube-proxy load balancing falls down.
The certificate chain — the thing you must understand
Linkerd’s mTLS rests on a three-level chain, and knowing it is the difference between operating Linkerd calmly and being paged by it. At the top sits the trust anchor: a self-signed root whose public half is distributed to every proxy so they can validate each other. Below it sits the issuer certificate and key, held by linkerd-identity, which signs on the root’s behalf. At the bottom are the leaf certificates minted for each proxy, valid 24 hours and rotated automatically.
Leaves take care of themselves. The issuer and the anchor do not — they have real expiry dates, and if the issuer expires, linkerd-identity stops minting leaves, every proxy’s leaf ages out within a day, and all meshed traffic stops. That is Linkerd’s signature outage; it gets its own warning below.
The CRDs Linkerd introduces
| Resource | API group | What it is for |
|---|---|---|
ServiceProfile | linkerd.io | Per-service route definitions, giving you per-route metrics plus retries and timeouts. The older mechanism, still widely used. |
HTTPRoute | gateway.networking.k8s.io (and Linkerd’s own policy.linkerd.io flavour) | The modern way to express routing, weighted backends, retries and timeouts — and the object authorization policy is attached to. |
Server | policy.linkerd.io | Names a specific port on a set of pods as a policy target. Creating one flips that port to default-deny. |
AuthorizationPolicy | policy.linkerd.io | Binds a target (Server, HTTPRoute, or a whole namespace) to a list of allowed authentications. |
MeshTLSAuthentication | policy.linkerd.io | “These mesh identities are allowed” — expressed as service accounts or identity strings. |
NetworkAuthentication | policy.linkerd.io | “These CIDRs are allowed” — for unmeshed callers such as an ingress controller, health checkers or Prometheus. |
TrafficSplit | split.smi-spec.io | The SMI weighted-split resource, moved out of core into the separate linkerd-smi extension and now retired. Legacy only; use HTTPRoute backend weights. |
“I found out we ran a service mesh about four months after we started running one. Someone added linkerd.io/inject: enabled to our namespace, our pods grew a second container, and… nothing else changed. Then during an incident I ran linkerd viz stat deploy and got success rate and p99 for every service we own, per route, without having instrumented a thing. That was the day I understood what ‘the platform does it for you’ actually means.”
The resources you will actually write
☺ Like you’re 10: Three small files: one to switch the helpers on, one to say who may talk to whom, one to say “try again if it fails.”
Meshing a namespace, and tuning one workload
Ninety per cent of adopting Linkerd is one annotation on a namespace. The injector then adds the sidecar to every pod created in it — note that annotating a namespace does not retrofit existing pods; they need a rollout restart.
apiVersion: v1
kind: Namespace
metadata:
name: prod
annotations:
linkerd.io/inject: enabled # every new pod here gets a proxy
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
namespace: prod
spec:
replicas: 3
selector:
matchLabels: { app: checkout }
template:
metadata:
labels: { app: checkout }
annotations:
# Pod-level annotations WIN over the namespace-level one.
linkerd.io/inject: enabled
config.linkerd.io/proxy-cpu-request: "100m"
config.linkerd.io/proxy-memory-request: "20Mi"
config.linkerd.io/skip-outbound-ports: "3306" # let raw MySQL bypass the proxy
config.linkerd.io/default-inbound-policy: all-authenticated # require mTLS inbound
spec:
serviceAccountName: checkout # THIS is the workload's mesh identity
containers:
- name: app
image: ghcr.io/acme/checkout:1.9.0
ports:
- containerPort: 8080Two details earn their keep. linkerd.io/inject: disabled on a pod opts one workload out of a meshed namespace, and linkerd.io/inject: ingress is the special mode for ingress controllers that need the proxy to route by the original Host header rather than destination IP. And note serviceAccountName: in Linkerd the ServiceAccount is the identity, so giving every workload its own is a prerequisite for meaningful policy, not a nicety.
Zero-trust authorization: Server plus AuthorizationPolicy
By default a meshed port accepts traffic from anyone who can reach it — encrypted, but not restricted. Creating a Server for a port switches it to default-deny, and you then explicitly grant access. This is the mesh-layer complement to the IP-based rules in Security & Policy: it authorizes on cryptographic identity, which survives pod rescheduling and IP reuse in a way NetworkPolicy cannot.
# The Server CRD is multi-version; confirm what your cluster serves with
# kubectl api-resources --api-group=policy.linkerd.io
apiVersion: policy.linkerd.io/v1beta1
kind: Server # naming a port makes it DEFAULT-DENY
metadata:
name: payments-http
namespace: prod
spec:
podSelector:
matchLabels: { app: payments }
port: http # container port NAME or number
proxyProtocol: HTTP/2 # helps the proxy pick the right protocol logic
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication # WHO: a set of mesh identities
metadata:
name: checkout-identity
namespace: prod
spec:
identities:
- "checkout.prod.serviceaccount.identity.linkerd.cluster.local"
---
apiVersion: policy.linkerd.io/v1alpha1
kind: NetworkAuthentication # WHO: unmeshed callers, by CIDR
metadata:
name: cluster-probes
namespace: prod
spec:
networks:
# Use YOUR cluster's real ranges: node network for kubelet probes,
# pod network for an unmeshed scraper. 10.42.0.0/16 is only an example.
- cidr: 10.42.0.0/16
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy # BIND the who to the what
metadata:
name: payments-allow-checkout
namespace: prod
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: payments-http
requiredAuthenticationRefs: # refs in ONE policy are combined restrictively:
- group: policy.linkerd.io # a caller must satisfy every ref listed here
kind: MeshTLSAuthentication
name: checkout-identity
---
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy # a SECOND policy = an ADDITIONAL way in
metadata: # (any matching policy authorizes the request)
name: payments-allow-probes
namespace: prod
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: payments-http
requiredAuthenticationRefs:
- group: policy.linkerd.io
kind: NetworkAuthentication
name: cluster-probesNote the shape of that second policy: give each class of caller its own AuthorizationPolicy targeting the same Server. Stacking a MeshTLSAuthentication and a NetworkAuthentication inside one policy’s requiredAuthenticationRefs narrows rather than widens — it describes a caller that must be both — which is almost never what you meant when you were trying to let in a meshed client and an unmeshed probe.
The moment a Server exists for a port, everything that is not explicitly authorized starts getting connection refusals — including your ingress controller, your liveness probes if they arrive from an unauthorized source, and Prometheus scraping 4191. Roll this out in the safe order: create the Server with a permissive AuthorizationPolicy first, watch linkerd viz stat for the affected workload, then tighten. Never introduce default-deny on a Friday, and cross-check symptoms against Triage: Networking.
Retries, timeouts and a weighted split
Reliability behaviour comes from either a ServiceProfile (long-standing, and still the one to know for per-route metrics) or an HTTPRoute (the modern, Gateway-API-aligned path). Both appear below because you will meet both in the wild.
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
# The name MUST be the fully-qualified service name.
name: payments.prod.svc.cluster.local
namespace: prod
spec:
routes:
- name: GET /v1/balance # gives you PER-ROUTE golden metrics
condition:
method: GET
pathRegex: /v1/balance
isRetryable: true # opt this route in to retries (idempotent only!)
timeout: 300ms
- name: POST /v1/charge
condition:
method: POST
pathRegex: /v1/charge
isRetryable: false # NOT idempotent — never retry money
timeout: 3s
retryBudget: # a BUDGET, not a fixed retry count
retryRatio: 0.2 # at most 20% extra requests from retries
minRetriesPerSecond: 10
ttl: 10s
---
# The modern equivalent: Gateway API HTTPRoute with a weighted split —
# the shape a progressive-delivery controller manipulates during a canary.
# Older clusters may still serve this type as v1beta1; Linkerd also ships
# the same shape under its own policy.linkerd.io group.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: payments-canary
namespace: prod
annotations:
retry.linkerd.io/http: 5xx # retry 5xx responses on this route
retry.linkerd.io/limit: "2"
timeout.linkerd.io/request: "5s"
spec:
parentRefs:
- name: payments # the Service this route governs
kind: Service
group: "" # core API group MUST be the empty string
port: 8080
rules:
- backendRefs:
- name: payments-primary
port: 8080
weight: 90 # 90% stable
- name: payments-canary
port: 8080
weight: 10 # 10% canaryLinkerd’s retry budget is a better default than a retry count, and it is worth being able to explain. A fixed “retry 3 times” multiplies load exactly when a dependency is already struggling — the classic retry storm. A budget caps retries as a percentage of normal traffic, so a failing service sees at most 20% extra load no matter how many callers are unhappy. Same reliability benefit, no amplification.
Day-to-day commands
☺ Like you’re 10: Mostly you type one word — check — and Linkerd tells you exactly what is wrong.
Install, and the one command that matters
Note the two-phase install: CRDs first, then the control plane. And note linkerd check appearing three times — before, after, and again. That is not padding; it is the recommended workflow.
# 1. Will this cluster even work? Run BEFORE installing anything. linkerd check --pre # 2. CRDs first, then the control plane. GitOps-friendly: pipe to a file, commit it. linkerd install --crds | kubectl apply -f - linkerd install | kubectl apply -f - # 3. Verify. If this is all green, the mesh is healthy — including cert expiry dates. linkerd check # 4. Metrics, dashboard and tap live in an extension. linkerd viz install | kubectl apply -f - linkerd check # re-run: it now checks the extension too # Mesh an existing workload (inject rewrites the manifest, it does not apply it) kubectl -n prod get deploy checkout -o yaml | linkerd inject - | kubectl apply -f - # ...or annotate the namespace and restart: kubectl annotate ns prod linkerd.io/inject=enabled kubectl -n prod rollout restart deploy # Upgrades follow the same two-phase shape linkerd upgrade --crds | kubectl apply -f - linkerd upgrade | kubectl apply -f - kubectl -n prod rollout restart deploy # data plane only picks up the new proxy on restart
Observing and debugging live traffic
# Golden metrics per workload: success rate, RPS, p50/p95/p99, and how much is meshed
linkerd viz stat deploy -n prod
# NAME MESHED SUCCESS RPS LATENCY_P50 LATENCY_P95 LATENCY_P99 TCP_CONN
# checkout 3/3 99.82% 24.3 3ms 12ms 31ms 18
# Per-route metrics — this is what a ServiceProfile buys you
linkerd viz routes deploy/payments -n prod
# WHO is calling this service, and who is it calling? ("to" and "from" edges)
linkerd viz stat deploy/payments -n prod --from deploy/checkout
linkerd viz edges deploy -n prod # shows the mTLS identity on each edge
# Live top-N view of routes hitting one workload
linkerd viz top deploy/checkout -n prod
# The debugging superpower: a live sample of real requests, with headers and latency
linkerd viz tap deploy/checkout -n prod --path /v1/balance
# Open the dashboard
linkerd viz dashboard &
# Certificates: linkerd check covers anchor + issuer expiry; --proxy adds the data plane
linkerd check --proxy
# linkerd identity dumps a leaf cert. It works on PODS, not deploy/... :
linkerd identity -n prod -l app=checkout
# Is a specific pod's traffic actually authorized / actually mTLS?
linkerd viz authz -n prod deploy/payments
linkerd diagnostics proxy-metrics -n prod deploy/checkout | grep tlslinkerd viz tap deserves respect: it streams real requests off live proxies with method, path, response class and latency, turning “the frontend says payments is slow” into an answer in twenty seconds. Add these to the muscle memory you are building on Command Reference.
Gotchas and failure modes
☺ Like you’re 10: Four things bite: an expired ID card, a helper who never showed up, apps that start talking too early, and locking a door on yourself.
Certificate expiry — the outage that takes the whole mesh
This is Linkerd’s defining operational risk. If you accepted the auto-generated certificates from a plain linkerd install, your trust anchor and issuer are valid for one year and nothing will remind you. When the issuer expires, linkerd-identity can no longer sign leaves; within 24 hours every proxy’s leaf expires; every meshed connection fails its handshake. The fix is prevention, in three parts: generate long-lived certificates yourself at install time rather than accepting the defaults; wire the issuer to cert-manager so it rotates automatically; and alert on the expiry metric so linkerd check’s warning reaches a human months early. Rotating the trust anchor itself is a careful bundle-both-then-remove-the-old-one dance — plan it, do not improvise it.
linkerd check before and after every changeThis is the single most repeated piece of advice in Linkerd operations and it is repeated because it works. linkerd check verifies API access, control-plane health, proxy versions, extension health, and — critically — certificate validity and expiry dates. Run it before an upgrade to confirm you are starting from green, and after to confirm you still are. A mesh outage that linkerd check would have warned about weeks earlier is an entirely self-inflicted incident.
The pod that quietly isn’t meshed
Injection is a mutating webhook, and webhooks fail silently. If linkerd-proxy-injector was unhealthy when a pod was created, that pod simply has no sidecar — no mTLS, no metrics. Same story if someone annotated the namespace but never restarted existing workloads, or if a pod-level linkerd.io/inject: disabled overrides the namespace. Detection is easy once you know to look: linkerd viz stat’s MESHED column shows 2/3 instead of 3/3, and linkerd check --proxy flags version skew and missing proxies.
Startup ordering, jobs, and skipped ports
- Race at startup. An app that opens outbound connections in its first milliseconds can beat the proxy to readiness. Kubernetes native sidecars largely solve this; where unavailable,
config.linkerd.io/proxy-awaitmakes the app container wait for the proxy. - Jobs that never finish. A
Jobcompletes its work but the proxy keeps running, so the pod never reachesCompleted. Linkerd exposes a proxy shutdown endpoint for exactly this — or simply skip meshing short-lived jobs. - Protocols the proxy shouldn’t touch. Some server-speaks-first protocols behave badly through an L7-aware proxy. Use
config.linkerd.io/skip-outbound-portsorskip-inbound-ports— but remember skipped traffic is unencrypted by Linkerd, which is a security decision, not just a compatibility one. - Resource maths. The proxy is tiny, but tiny multiplied by four thousand pods is still real memory. Set proxy requests deliberately and account for them in capacity planning.
Two controllers fighting over your routes
If Flagger is writing weights into an HTTPRoute or TrafficSplit while an Argo CD or Flux reconciler with self-heal enabled insists on the version in Git, the two will fight forever and your canary will never progress. Exclude the generated routing objects from GitOps ownership. It is the same one-field-two-controllers anti-pattern that shows up everywhere in platform engineering.
On a throwaway kind cluster: run linkerd check --pre, then install the CRDs, control plane and viz extension, running linkerd check after each step. Deploy the emojivoto demo unmeshed first and confirm linkerd viz stat deploy -n emojivoto shows 0/4 meshed. Now annotate the namespace, kubectl rollout restart deploy -n emojivoto, and watch the MESHED column fill in and success rates appear — with zero application changes. Then: (1) linkerd viz edges deploy -n emojivoto and read the mTLS identity on each edge; (2) linkerd viz tap deploy/web -n emojivoto and watch live requests scroll past; (3) create a Server for the voting service with no AuthorizationPolicy, watch success rate collapse, then add a MeshTLSAuthentication for web’s service account and watch it recover. Injection, identity, observability and default-deny — all felt, in twenty minutes.
Alternatives and when to choose it
☺ Like you’re 10: There are a few helper-teams to hire. They mostly differ in how many buttons they have and how heavy they are.
The field
| Option | Data plane | mTLS | Configurability | Choose it when… |
|---|---|---|---|---|
| Linkerd | linkerd2-proxy — purpose-built Rust micro-proxy | Automatic, on by default for all meshed traffic | Deliberately narrow; strong defaults | You want mTLS, golden metrics and reliable retries across many services with the smallest possible operational surface |
| Istio | Envoy, as sidecar or ambient (ztunnel + waypoint) | Yes, SPIFFE identities; PeerAuthentication to enforce STRICT | Very high — VirtualService, DestinationRule, EnvoyFilter, WASM | You need advanced L7 routing, egress control, multi-mesh federation, or your vendor/cloud ships it |
| Cilium Service Mesh | eBPF in-kernel plus a shared per-node Envoy for L7 | Yes, identity-based mutual auth | Moderate; converges with your CNI | Cilium is already your CNI and you would rather not run a second distributed system |
| No mesh (Services + NetworkPolicy + libraries) | kube-proxy / eBPF only | Only what your apps implement | n/a | A handful of services, one language, and no regulatory pressure for encryption in transit |
The honest decision rule
Choose Linkerd when the requirement is “encrypt and observe everything, reliably, without a platform team of six.” Its simplicity is not a limitation you tolerate — it is the product. Choose Istio when you have a concrete requirement Linkerd cannot express: complex header-based routing trees, egress gateways with strict external policy, or federation across many meshes. Choose Cilium’s mesh when it is already your data path. And genuinely consider choosing nothing yet: as Networking puts it, the best mesh is often the least mesh that solves your actual problem, and a mesh adopted without a driving requirement is a distributed system you now have to keep alive at 3am. Both Linkerd and Istio are CNCF-graduated, so neither is a maturity risk — the difference is entirely one of fit.
Foxy: All our meshed services went down at once. Every single one. Nothing was deployed. This mesh thing is a liability.
Pip: Run linkerd check. I’ll wait. …Right there: “certificate will expire on 2026-07-19.” That was yesterday. The issuer expired, so I can’t sign new leaf certs, and every proxy’s 24-hour leaf has now aged out.
Benny: And linkerd check has been printing that warning for sixty days. Nobody was running it, and nobody alerted on it. This is a monitoring gap wearing a certificate costume.
Gizmo: Easy fix — just add skip-inbound-ports for everything. No proxy, no certs, no problem! Ships in one commit! 🤑
Timmy: Gizmo, that is “fix the smoke alarm by removing the battery.” The fix is cert-manager rotating the issuer automatically, and an alert on the expiry metric. Then this never happens again.
Pip: Before and after every change. Say it with me.
Dot: Meanwhile I have never opened Linkerd once and my service has had mutual TLS and p99 dashboards for a year. That’s… actually the nicest thing I can say about a platform tool.
Exam relevance and going further
☺ Like you’re 10: Linkerd is on the exam’s tool list — but its own website is locked during the test, so the shapes must already be in your head.
What to know cold
Linkerd is on the official CNPE tool list, reached mainly through the networking and security competencies. Be able to, without notes:
- Say in one sentence what a service mesh gives you that plain Services and DNS cannot — mTLS, L7 retries/timeouts, weighted splitting, uniform telemetry — and name the cost (a whole extra distributed system).
- State Linkerd’s two differentiators against Istio: an ultralight purpose-built Rust proxy instead of Envoy, and mTLS on by default with far fewer configuration knobs.
- Name the three control-plane components (identity, destination, proxy-injector) and say what each does.
- Mesh a workload two ways: the
linkerd.io/inject: enabledannotation on a namespace or pod template, andlinkerd injectpiped intokubectl apply— and know that annotating a namespace does not retrofit running pods. - Explain the certificate chain — trust anchor → issuer → 24h leaves — and say which parts rotate themselves and which take the mesh down when they expire.
- Name the policy resources (
Server,AuthorizationPolicy,MeshTLSAuthentication,NetworkAuthentication) and know that creating aServermakes that port default-deny. - Reach for
linkerd checkfirst, always; thenlinkerd viz statfor golden metrics andlinkerd viz tapfor live requests.
The documentation allowlist — read this twice
During the CNPE exam the only documentation you may open is kubernetes.io/docs, kubernetes.io/blog, any task-specific docs explicitly linked in the exam’s Quick Reference box, and local man pages and /usr/share docs on the exam machine. linkerd.io/docs is therefore off-limits — you cannot look up the ServiceProfile schema or the policy API group mid-task. If the CRDs happen to be installed on the exam cluster, kubectl explain server.policy.linkerd.io and kubectl api-resources | grep linkerd are legitimate and available; do not count on it. The manifest shapes worth memorising are collected on Know Cold, the wider tool map lives on Tools, and any term here you cannot define belongs in the glossary.
⚖ CNPA vs CNPE — That allowlist is a CNPE-only mechanic: CNPE is hands-on, so it permits those narrow live lookups mid-task. CNPA is stricter, not looser — a fully closed-book, multiple-choice exam with zero external resources and zero lookups of any kind, so neither linkerd.io nor kubernetes.io would be reachable there either. Even so, the concept-level knowledge above — what a service mesh buys you over plain Services and DNS, the identity/destination/proxy-injector split, and the certificate-chain expiry risk — is exactly the kind of thing CNPA’s closed-book recall draws on.
Practically: budget one study session for hand-writing the inject annotation, a Server plus AuthorizationPolicy pair, and a ServiceProfile with a retry budget on a blank page, then checking yourself against Know Cold. Rehearse the diagnosis half with Triage: Networking and the troubleshooting playbook. Reading about Linkerd is not the same as recalling it under a timer.
Official links (for study time, not exam time)
- linkerd.io/docs — the canonical documentation, including the full reference for every annotation and CRD.
- Automatically rotating control-plane TLS credentials — the cert-manager integration you should set up on day one.
- Authorization policy reference and Server policy — the two pages you will actually re-read on the job.
- github.com/linkerd/linkerd2 — source, release notes, and the
emojivotodemo used in the workshop above. - Related on this site: Networking & Service Mesh (the parent lesson), Security & Policy, Multi-Cluster, Observability, and Flagger for what sits on top of the traffic split.
1. Name Linkerd’s three control-plane components and one thing each is responsible for. 2. What is linkerd2-proxy, and how does that choice differ from Istio’s? 3. Describe the certificate chain: which level rotates automatically, and which one takes the whole mesh down if it expires? 4. Give two ways to mesh an existing Deployment — and say why annotating the namespace alone is not enough. 5. What happens the instant you create a Server resource for a port? 6. Why is a retry budget safer than “retry 3 times”? 7. Can you open linkerd.io/docs during the CNPE exam?
Check your answers
- identity (a CA: validates ServiceAccount tokens and issues 24-hour leaf certificates), destination (service discovery plus policy, route, retry and timeout information for the proxies), and proxy-injector (the mutating admission webhook that adds the sidecar when it sees
linkerd.io/inject: enabled). - It is a purpose-built ultralight micro-proxy written in Rust, existing only to serve Linkerd. Istio uses Envoy, a general-purpose proxy with a far larger configuration surface and correspondingly higher memory and latency overhead per pod.
- Trust anchor (self-signed root, distributed to every proxy) → issuer (held by
linkerd-identity, signs on the root’s behalf) → leaf certificates per proxy, valid 24 hours. Leaves rotate automatically; the issuer (and ultimately the anchor) does not, and its expiry stops all new leaves being signed — within a day every meshed connection fails. Rotate it with cert-manager and alert on expiry. - Either annotate the namespace or the pod template with
linkerd.io/inject: enabled, or pipe the manifest throughlinkerd injectbefore applying it. Annotating a namespace only affects newly created pods, so existing workloads need akubectl rollout restart. - That port becomes default-deny — all traffic is refused until an
AuthorizationPolicy(referencing aMeshTLSAuthenticationorNetworkAuthentication) explicitly permits it. This will cut off ingress, probes and scrapers if you have not authorized them first. - A fixed count multiplies load on an already-failing dependency (a retry storm). A budget caps retries at a percentage of normal traffic — e.g.
retryRatio: 0.2means at most 20% extra requests — so retries help without amplifying an outage. - No. The exam allowlist is kubernetes.io/docs, kubernetes.io/blog, task-specific docs given in the Quick Reference box, and local man/
/usr/sharedocs — Linkerd’s own site is not on it, so the shapes must be memorised.