A service mesh solves three problems and costs you an operator
Mutual TLS, fine-grained traffic control and uniform telemetry without touching application code. What each one actually costs in latency, memory and operational load, and the cheaper things that cover two out of three.
The usual way a service mesh arrives is that someone needs weighted traffic splitting for a canary, installs Istio on a Friday, and by Monday the platform team owns a control plane, a certificate authority, a sidecar injector and a new class of incident where a pod is healthy but cannot talk to anything because the proxy started after the application did.
That is not an argument against meshes. It is an argument for knowing which of the three problems you are buying, because a mesh is not a feature you enable. It is a piece of infrastructure with an on-call rotation attached.
The three problems, and only these three
Encrypted, authenticated traffic between services. Every pod gets a short-lived workload certificate, the proxies negotiate mTLS, and identity becomes something you can write policy against: the payments service account may call the ledger service on this port and nothing else. Without a mesh you either terminate TLS in every application, in every language, with every team's own certificate handling, or you accept plaintext inside the cluster and rely on network policy to fence it. The mesh version is the only one that gives you a cryptographic identity rather than an IP address, which matters the moment an auditor asks how you enforce service-to-service authorisation.
Traffic control at request granularity. Weight-based splitting, header-based routing, retries with budgets, timeouts, circuit breaking, fault injection. Kubernetes Services give you round-robin over ready endpoints and nothing else. If you want 5 percent of traffic on a new version, or all traffic from users with a beta header on a preview build, that logic has to live somewhere. A mesh puts it in configuration instead of in a library that every service must import.
Uniform telemetry. Every hop produces the same four golden signals with the same label names, whether the service is Go, Java or a vendor binary you cannot instrument. This is the benefit teams underrate before they have it and refuse to give up afterwards, because it is what makes a dependency graph honest.
Notice what is not on the list. A mesh does not fix a slow database, does not replace an ingress controller, and does not give you distributed tracing for free: the proxies propagate trace headers, but the application still has to forward them across its own internal calls or your traces come out as disconnected single spans.
Picking one
Istio in ambient mode is where the project has spent its recent effort, and it changes the calculus. Instead of a sidecar per pod, a per-node ztunnel handles mTLS and L4 authorisation, and you only deploy a waypoint proxy for the namespaces that need L7 features. Encryption and identity across the whole mesh stop costing a container per workload. Sidecar mode is still there and still the most feature-complete path, but if you are starting now, start with ambient and add waypoints where you need routing.
Linkerd is the choice when you want the three problems solved and nothing else on the menu. Its proxy is small, the defaults are sane, mTLS is on from install, and the operational surface is genuinely smaller. The trade is a narrower feature set and a licensing model worth reading before you commit: stable releases are distributed under terms that differ from the old fully-open model, so check what applies to your organisation.
Cilium makes sense when it is already your CNI. You get identity-based policy and much of the observability from eBPF in the datapath, with Envoy only inserted where L7 is required. One component instead of two, and no second identity system. If you are running Cilium for networking already, evaluate its mesh features before you install anything else.
What it costs
Be honest about the numbers, and measure them yourself rather than trusting a benchmark blog.
Latency: a sidecar adds a hop on each side of a call. In the low milliseconds at p99 for a well-tuned Envoy, sub-millisecond for Linkerd's proxy, effectively removed at L4 with ambient or eBPF. For a request that already takes 200 ms this is noise. For an internal call in a chain of twelve, it is not.
Resources: sidecar mode means a proxy container per pod, and on a cluster with a thousand pods that is real memory and real CPU request headroom, plus a proportional increase in what the control plane has to push. Per-node architectures amortise this.
Operations: this is the one that actually bites. Certificate rotation, control plane upgrades that must be sequenced against data plane upgrades, sidecar startup ordering, and the debugging tax of an extra proxy in every path. Budget a named owner, not a spare afternoon.
The cheaper things that cover two out of three
Before installing anything, check whether you already have enough:
- Gateway API with an implementation that supports traffic splitting gives you weighted routing at the edge, which is what most canaries actually need. Argo Rollouts and Flagger drive it directly, no mesh required.
- NetworkPolicy gives you L3/L4 segmentation. Not identity, but it stops the flat-network problem.
- A shared instrumentation library plus the OpenTelemetry Collector gives you uniform telemetry if you control all the services and they are in one or two languages.
- Node-to-node encryption from your CNI (WireGuard or IPsec) covers traffic on the wire without per-workload identity, which may be exactly what your compliance requirement says.
If those three cover you, you have solved two of the problems for a fraction of the operational cost.
Things people forget
- The proxy has to start before the application. Native sidecar containers in modern Kubernetes fix the startup and shutdown ordering that used to break jobs and init containers. If you are on sidecar mode, use them.
- Retries multiply load. A mesh-level retry on every hop of a five-service chain turns one client request into dozens during a partial outage. Set retry budgets, not retry counts.
- mTLS in permissive mode is not mTLS. Most installs start permissive so nothing breaks, and then nobody goes back to enforce strict. Track the percentage of namespaces in strict mode as a metric.
- Multi-cluster is a separate project. Cross-cluster mesh means trust domain federation, endpoint discovery and a much larger blast radius. Do not treat it as a config flag.
- Egress is usually the actual audit requirement. Controlling which external hosts a workload may reach is a mesh capability people install a mesh for and then never configure.
When the answer is no
Fewer than roughly twenty services, one language, one cluster, no regulatory requirement for in-cluster encryption, and no team that would own the mesh: do not install one. You will pay the full operational cost for the fraction of the benefit you can use, and the first incident will be caused by the thing you installed to prevent incidents. We look at this explicitly when we size a platform in a cloud engagement, and the recommendation is regularly to wait.
What to do this week
List your services and mark each one with whether it terminates TLS internally, whether it emits the same request metrics as its neighbours, and whether it needs weighted traffic control. If the third column is empty and the second is mostly yes, you do not have a mesh problem yet. Save the file; it is the business case either way.