Istio Ambient was, in 2024–2025, considered the final answer to the “sidecar tax” — the proxy container left every pod, replaced by two new component types, ztunnel and waypoint. That’s no longer a sidecar, but it’s still a separate proxy layer on top of the cluster. Cilium, if it’s already running for CNI and Hubble, solves the same problem without a single extra proxy process — mTLS and L7 policy live on the same eBPF datapath as ordinary network policy. Let’s find where the real architectural line sits between “no sidecar” and “no proxy at all,” and what that means in practice.
Table of contents
Open Table of contents
- From sidecar to Ambient — and further
- The architectural difference: eBPF datapath vs. ztunnel/waypoint
- What Cilium covers natively, and what it doesn’t
- Comparison at a glance
- Honest limitations
- Migrating off sidecar-based Istio with no downtime
- What it takes to turn on mTLS in Cilium
- How to verify it works
- Bottom line
From sidecar to Ambient — and further
A classical service mesh (Istio in sidecar mode, Linkerd before its recent versions) puts an Envoy proxy in every pod. The proxy intercepts all inbound and outbound traffic via an iptables redirect and builds mTLS, retries, circuit breaking, and L7 routing on top of that. The price is an extra container per pod: its own CPU and memory, a separate lifecycle to manage, and added latency on every extra proxy hop.
Istio Ambient removes exactly that sidecar, but it doesn’t remove the proxy as a class. Instead of one proxy per pod, two new component types show up:
- ztunnel — a DaemonSet, one per node. Handles L4: the mTLS tunnel between pods and basic identity verification. A thin layer with no L7 logic.
- waypoint — a dedicated Envoy pod per namespace or service account, enabled only where L7 policy is actually needed (HTTP-header-based routing, retries, traffic splitting). Traffic that needs L7 takes an extra hop through waypoint.
That’s a genuine step forward: no sidecar in the pod, no per-pod memory/CPU tax. But the mesh control plane still consists of dedicated proxy components that have to be installed, upgraded, and operated separately from the rest of the cluster — and traffic that needs L7 logic still takes that extra jump through waypoint.
The architectural difference: eBPF datapath vs. ztunnel/waypoint
Cilium comes at this from a different angle. It already has an eBPF datapath that all pod traffic passes through — ordinary network policy, kube-proxy replacement, and Hubble are all built on it. Service Mesh mode doesn’t add parallel infrastructure; it extends that same datapath: the mTLS handshake and identity check happen directly in eBPF programs on the node, and L7 parsing (HTTP methods, paths, gRPC) goes through Envoy, which Cilium also embeds — but as a shared per-node process, not a dedicated per-namespace pod that traffic has to be routed through.
The difference shows up in the number of moving parts. Ambient has three: CNI, ztunnel, waypoint — each with its own lifecycle and upgrade path. Cilium has one component, the one you’re already operating. That doesn’t make Cilium mesh “better” in every scenario — it solves a narrower problem with fewer moving parts, while Ambient targets full Istio-grade traffic management.
What Cilium covers natively, and what it doesn’t
Of the classic “service mesh functionality” list, Cilium covers the following natively, with no additional components:
- mTLS between pods — based on SPIFFE-compatible identity. We already covered SPIFFE/SPIRE as a standalone identity standard — Cilium Mutual Authentication uses exactly that mechanism: SPIRE issues a short-lived SVID, and the eBPF datapath presents and verifies it at handshake time, with no round trip into the application.
- L7 policy — filtering by HTTP method and path, gRPC methods, Kafka topics. It runs through an embedded per-node Envoy, but is configured as an ordinary
CiliumNetworkPolicy, not a separate mesh control-plane CRD. - Transit encryption — WireGuard or IPsec between nodes, when you need a just-works transport layer without per-service identity.
What Cilium doesn’t cover, and isn’t trying to — complex traffic management: percentage-based canary rollouts, header-based routing between service versions, retry/circuit-breaking policy at the mesh level. That’s deliberately left to Istio (in either mode) and Gateway API implementations on top of it — Cilium isn’t competing on full traffic shaping, it’s covering identity and L7 visibility where those are already needed for observability.
One important nuance: Cilium can act as a Gateway API controller (Ingress/Gateway API), and at that layer some traffic-management primitives do show up — weighted routing between service versions, header-based matching for inbound edge traffic. But that operates at the cluster boundary, not as a policy between arbitrary services inside the mesh — don’t conflate “Cilium as a Gateway API controller” with “Cilium as a full traffic-management mesh.” They’re different perimeters of the same underlying technology.
Comparison at a glance
| Sidecar Istio | Istio Ambient | Cilium Service Mesh | |
|---|---|---|---|
| Proxy in the pod | Envoy sidecar | none | none |
| Separate mesh components | Envoy sidecars | ztunnel + waypoint | none — same eBPF datapath |
| mTLS | via sidecar | via ztunnel | via eBPF + SPIFFE identity |
| L7 policy | via sidecar | via waypoint (extra hop) | via per-node Envoy |
| Traffic shaping (canary, header routing) | full | full | none |
| What to run beyond the CNI | Istio control plane | Istio control plane + ztunnel/waypoint | nothing, if Cilium is already there |
Honest limitations
Two places where Cilium Service Mesh promises less than it might seem at first glance — worth knowing before you build it into an architecture.
Multi-cluster mesh. Cilium has Cluster Mesh — a mechanism that joins multiple clusters into a shared network space: pods in one cluster see services in another over the same eBPF datapath, with no extra gateway. But that solves connectivity and load balancing, not full multi-cluster traffic management in the Istio sense — for example, a unified cross-cluster failover policy based on latency or on the service’s own health check, rather than just network reachability, isn’t something Cluster Mesh gives you out of the box. If you need active-active or active-passive strategy at the service level, not just the network level, that has to be built on top — either at the Istio layer or with your own.
Zero-trust identity in full. mTLS based on SPIFFE identity within a single cluster is something Cilium covers honestly, but full zero-trust also means identity federation across clusters and external systems, trust-bundle rotation, and trust between multiple SPIRE servers. Cilium deploys SPIRE as part of its own install, but that doesn’t remove the fact that SPIRE is a separate system with its own operational complexity: cross-cluster federation is configured by hand, not enabled by a single Helm flag out of the box. If zero-trust identity needs to be a platform-level concern in its own right, not just something running under the hood of a mesh, running and operating SPIRE deserves deliberate attention — not treatment as a side effect of one flag.
Migrating off sidecar-based Istio with no downtime
If a cluster already runs sidecar-based Istio and Cilium sits alongside it purely as CNI, the migration is done incrementally, namespace by namespace, with no cluster-wide downtime window:
- Turn on Cilium Service Mesh (
authentication.mutual.spire) in the cluster, leaving the existing Istio sidecars in place — these are two independent mTLS layers, and they don’t conflict; traffic is simply double-encrypted for the duration of the migration. - Pick one non-critical namespace, remove sidecar injection (
istio-injection: disabledon the namespace, restart the pods without the sidecar), and enable aCiliumNetworkPolicywithauthentication.mode: requiredon it — traffic from that namespace now goes through eBPF mTLS instead of the Envoy sidecar. - Use Hubble to confirm the handshake succeeds and traffic isn’t being dropped by policy (see the verification section below) before repeating the step on the next namespace.
- Where the mesh actually relies on L7 traffic shaping (header-based canary, weighted routing), leave those namespaces on Istio — there’s no need to move them to Cilium mesh, since it doesn’t cover that functionality.
This namespace-by-namespace cutover avoids downtime precisely because both mTLS layers can run in parallel — you never turn one mechanism off before the other is on.
What it takes to turn on mTLS in Cilium
You need a cluster with Cilium already installed (see the Hubble and eBPF observability walkthrough if the CNI isn’t in place yet) and Helm. Cilium Mutual Authentication deploys a SPIRE server and agent as part of the chart:
helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values \
--set authentication.mutual.spire.enabled=true \
--set authentication.mutual.spire.install.enabled=true \
--set hubble.enabled=true
cilium status --wait
Now require mutual authentication for one specific service — the rest of the traffic in the cluster keeps working as before:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: require-mtls-backend
namespace: default
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
authentication:
mode: "required"
toPorts:
- ports:
- port: "8080"
protocol: TCP
From this point on, a pod labeled app: frontend can reach backend:8080 only by presenting a valid SPIFFE SVID at handshake time — with no sidecar container in either pod.
How to verify it works
First, confirm the SPIRE components are up and Cilium sees them:
kubectl get pods -n cilium-spire
cilium status | grep -i "mutual auth"
# Mutual authentication: Ready
Now watch live flows between frontend and backend and look for the authentication fact in the flow:
hubble observe --namespace default --protocol http -f
# frontend -> backend:8080 FORWARDED http-request auth=SPIRE
If you remove the app: frontend label from the client pod (or spin up a pod without a valid identity) and repeat the request, Hubble shows a DROPPED verdict with a reason pointing at a failed authentication — the same kind of “sidecar-free trace” Cilium is known for on ordinary network policy, now applied to the mTLS handshake itself.
Bottom line
Istio Ambient honestly solves the sidecar tax, but in exchange it introduces two new types of mesh components that have to be operated on their own — the right call if you genuinely need full traffic management: percentage-based canary, header-based routing, retry policy. Cilium Service Mesh solves a narrower problem — mTLS and L7 visibility — but does it as a configuration flag on a datapath that’s almost certainly already running in the cluster for CNI and Hubble. If your entire requirement list reads “encrypt traffic between services and see what’s happening at L7,” a separate service mesh may just be an extra component; if you need full traffic shaping, Cilium mesh won’t cover it, and there’s no getting around Istio Ambient (or something like it) there.