Lesson Β inΒ  Kubelings β€” Learn Kubernetes the Rustlings Way

Incident file β€” how we failed to integrate Istio (Exponea)

Read-through of Exponea's cited write-up: a service-mesh rollout abandoned after the sidecar broke Jobs, StatefulSets, gRPC load balancing and graceful shutdown one after another. No tasks; this is the platform-engineering judgement call β€” what a mesh actually costs, which of your workloads it changes, and how to decide whether to adopt one at all.

Incident file (guided reading). No lab: a mesh install plus a fleet to put behind it is a platform decision, not a namespace. What transfers is the shape of the cost β€” every failure below is a consequence of one architectural fact, and recognising that fact is what makes the adopt/don't-adopt call answerable.

Sources: Exponea β€” Sailing with Istio through the shallow water Β· Airbnb β€” 10 more weird ways to blow up your Kubernetes

What happened

Exponea (GKE, microservices) adopted Istio for the usual, good reasons: traffic insight, mTLS between services, retries/timeouts moved out of application code. They started with GKE's managed Istio, found it configurable only for mTLS mode, went self-managed, and then met the sidecar β€” repeatedly:

  • Port names became load-bearing. Istio routes by protocol inferred from the port name (http, grpc, tcp-…). A port named wrongly β€” or not at all β€” silently changed how traffic was handled, and services failed at startup.
  • Jobs stopped completing. The app container exits; the sidecar proxy does not β€” it's a server, it runs forever. The pod never reaches Completed, so the Job never finishes and the pipeline waiting on it never moves. (M2 incident-job-restartpolicy is the same organ from the other side: what "finished" means for a Job.)
  • StatefulSets misbehaved. Service discovery didn't track pod IP changes on recreation, so connections were routed to addresses that no longer existed.
  • gRPC client-side load balancing fought the mesh. Headless Services exist so clients can balance across endpoints themselves; the sidecar wants to do that job, and the two answers to "who balances?" don't compose.
  • Graceful shutdown regressed. The sidecar exited immediately on SIGTERM, cutting off app containers that needed their full grace period to finish work in flight. (M4 incident-graceful-shutdown β€” now with a second process whose lifecycle you also have to get right.)
  • Upgrades had no good story. Sidecar version rollout across a fleet, plus unsupported multi-tenancy, made production adoption a project rather than a step.

They concluded the value of "Istio in a few services" didn't cover the cost, and postponed the integration.

The one fact underneath all of it

A mesh puts a second process inside every pod and a second network hop in every call. Every symptom above follows:

ConsequenceSymptom class
Two containers, one pod lifecycleJobs never complete; shutdown ordering; startup races (app talks before proxy is ready)
Traffic is now parsed, not just forwardedprotocol/port-name sensitivity, header rules, mTLS handshake failures
The mesh owns discovery and balancingconflicts with headless Services, client-side LB, StatefulSet identity
The mesh is a control plane you now operateupgrades, CRD sprawl, its own outages (a broken sidecar injector = M6 incident-webhook-outage)
Every request pays proxy CPU/latencytail-latency budget, per-pod memory floor across the whole fleet

None of this makes meshes wrong β€” it makes them large. The 2019 write-up predates a lot of maturation (sidecar holdApplicationUntilProxyStarts, native sidecar containers in Kubernetes 1.29+ which fix the Job-never-completes problem outright, and ambient mode, which removes the per-pod sidecar entirely). The judgement it teaches is the durable part.

Concept checks

  • Your batch Jobs hang at 1/2 Ready after a mesh rollout. Two fixes exist, one modern and one historical β€” name both. (Modern: Kubernetes sidecar containers β€” initContainers with restartPolicy: Always β€” so the proxy is terminated when the main container exits. Historical: call the proxy's /quitquitquit endpoint at the end of the job, or exclude Jobs from injection.)
  • Why does a service mesh make the port name a correctness concern when plain Kubernetes does not? (Plain Services forward bytes; a mesh terminates and parses the protocol to do routing, retries and telemetry β€” so it must be told which protocol it's holding, and the name is the declaration.)
  • What would you buy a mesh for if you already have NetworkPolicy, an ingress controller, and OpenTelemetry? (Honest answers: mTLS identity between services without app changes, L7 retries/traffic splitting for progressive delivery, and uniform golden signals. If none of those are unsolved problems for you, the mesh is cost without benefit.)

What the industry took from it

  • Adopt a mesh workload-class by workload-class, not cluster-wide. Stateless HTTP services first; Jobs, StatefulSets and anything doing client-side balancing last, deliberately, with their own tests.
  • Exclude by default, include on purpose β€” namespace/pod injection labels are the blast-radius control, and they're free until you use them.
  • Budget the tax before the pilot: per-pod CPU/memory of the proxy Γ— fleet size, plus added p99. If the number is uncomfortable at 50 services, it will be unacceptable at 500.
  • Prefer the smallest thing that solves your actual problem. Gateway API (M4 gateway-api) for north-south routing, NetworkPolicy (M4/M6) for segmentation, OTel (M8 otel-collector-pipeline) for traces β€” reach for the mesh when you need service identity and L7 policy between services and you're prepared to run a control plane to get them.

No check β€” study, then advance.

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  
   β”‚sidecar injected β”‚  
   β”‚                 β”‚  
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  
           β”‚            
 second process in pod  
           β”‚            
           β–Ό            
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” 
  β”‚proxy runs forever β”‚ 
  β”‚                   β”‚ 
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 
           β”‚            
app exits, pod does not 
           β”‚            
           β–Ό            
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚Job never completes β”‚
  β”‚                    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜