Skip to content
Hogin Hogin
Go back

Sidecar-free service mesh: Cilium Service Mesh next to Istio Ambient

10 мин чтения

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

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:

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.

Istio Ambient: ztunnel and waypoint as separate components vs. Cilium: mTLS and L7 on one shared eBPF datapath

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:

Request path with mTLS: two hops through sidecar/waypoint vs. a single pass through the node's eBPF datapath

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 IstioIstio AmbientCilium Service Mesh
Proxy in the podEnvoy sidecarnonenone
Separate mesh componentsEnvoy sidecarsztunnel + waypointnone — same eBPF datapath
mTLSvia sidecarvia ztunnelvia eBPF + SPIFFE identity
L7 policyvia sidecarvia waypoint (extra hop)via per-node Envoy
Traffic shaping (canary, header routing)fullfullnone
What to run beyond the CNIIstio control planeIstio control plane + ztunnel/waypointnothing, 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:

  1. 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.
  2. Pick one non-critical namespace, remove sidecar injection (istio-injection: disabled on the namespace, restart the pods without the sidecar), and enable a CiliumNetworkPolicy with authentication.mode: required on it — traffic from that namespace now goes through eBPF mTLS instead of the Envoy sidecar.
  3. 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.
  4. 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.


Share this post:

Next Post
Flux 2.8: CEL-based health checks and what they change for release gating