1. The Sidecar Pattern & Data/Control Plane
A service mesh works by injecting a small proxy container — Envoy,
in both Istio and (in Linkerd's case, a lighter Rust proxy) — into every Pod,
alongside your application container. Every request your app sends or receives is
transparently intercepted by that sidecar first. Your application code makes a
perfectly ordinary HTTP call to another service's normal address; it has no idea a
proxy is involved at all. The sum of every sidecar across the cluster is the mesh's
data plane — it's what actually handles traffic. A separate
control plane (istiod for Istio) configures every
sidecar's behavior centrally and pushes updates out to them.
kubectl label namespace production istio-injection=enabled
# any Deployment applied to this namespace from now on
# automatically gets an Envoy sidecar added to its Pods
kubectl apply -f k8s/orders-deployment.yaml -n production
kubectl get pods -n production
# NAME READY STATUS
# orders-7d9f6b8c4d-x2k9p 2/2 Running <- 2 containers: app + istio-proxy
That 2/2 is the whole pattern in one line: nothing in
orders-deployment.yaml mentions Istio at all, yet every Pod it creates
now runs an Envoy proxy alongside the app container, transparently. This is the
central appeal of a mesh over building the same capabilities into every service's
code — mTLS, retries, and traffic routing become a platform concern configured once,
not a library every team has to import and keep in sync.
Every sidecar is another container consuming CPU and memory, another hop of latency, and another moving part to debug when something breaks. A service mesh earns its keep in a system with many services talking to each other in ways that need consistent mTLS, retries and observability — a five-service app rarely needs one yet, and it's worth being able to say so in an interview rather than reaching for a mesh by default.
2. mTLS, Retries, Timeouts & Circuit Breaking
With sidecars in place, Istio can enforce mutual TLS (mTLS) between every service in the mesh — both sides of a connection present and verify a certificate, not just the server — without a single line of application code changing. Certificates are issued and rotated automatically by the control plane.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # reject any plaintext traffic between meshed Pods
On top of encryption, the mesh can add resilience patterns directly at the proxy layer — retries on transient failures, timeouts so a slow dependency doesn't stall its caller forever, and circuit breaking so one struggling service doesn't get hammered by callers retrying into it and making the outage worse:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: inventory-service
spec:
host: inventory-service
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 20
maxRequestsPerConnection: 10
outlierDetection: # circuit breaking
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s # eject an unhealthy Pod for 30s
maxEjectionPercent: 50
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: inventory-service
spec:
hosts: [inventory-service]
http:
- timeout: 2s
retries:
attempts: 3
perTryTimeout: 500ms
retryOn: 5xx,connect-failure
route:
- destination: { host: inventory-service }
outlierDetection is the circuit breaker: after 5 consecutive 5xx
responses, that specific Pod is ejected from the load-balancing pool for 30 seconds,
which gives it room to recover (or restart) instead of continuing to receive traffic
it's already failing to serve. The 2-second overall timeout combined
with a 500ms perTryTimeout and 3 retries means a caller gets a definitive
answer within 2 seconds no matter what — never an indefinite hang waiting on a
dependency that's stopped responding.
Retrying a request to an already-overloaded service multiplies the load it's receiving instead of relieving it — three retries on every failing call can mean 4x the traffic hitting a service that was already struggling. Pair retries with circuit breaking (outlier detection) so a consistently failing Pod gets pulled out of rotation instead of retried into indefinitely.
3. Traffic Shifting for Canary Releases
Week 15's rolling updates replace old Pods with new ones gradually, but every request
still eventually reaches the new version — there's no way to say "send only 10% of
traffic there and watch it." A mesh's VirtualService can split traffic
by weight across two versions of the same service, running side by side, which is
what makes a genuine canary release possible.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: checkout-service
spec:
hosts: [checkout-service]
http:
- route:
- destination:
host: checkout-service
subset: v1
weight: 90
- destination:
host: checkout-service
subset: v2 # the canary
weight: 10
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: checkout-service
spec:
host: checkout-service
subsets:
- name: v1
labels: { version: v1 }
- name: v2
labels: { version: v2 }
Both v1 and v2 run as ordinary Deployments distinguished
only by a version label — the VirtualService is what
decides how traffic splits between them, and that weight is just a number you can
change: watch the canary's error rate and latency in your Week 13 dashboards at 10%,
then move to 25%, 50%, and 100% only once you trust it, or set the weight back to 0
and delete the canary Deployment in seconds if something looks wrong — no rollback of
a Deployment's rollout history required, because the old version never stopped
running.
Shifting 10% of traffic to a new version is worthless if nobody is comparing its error rate and latency against the stable version while it runs. Tag metrics and logs with the version label from the DestinationRule so your Grafana dashboard can show v1 and v2 side by side, and decide the next weight change from that comparison — not from "it's been running for a while and nothing paged."
4. Hands-on Exercise
Mesh two services and run a weighted canary between them
Install Istio on your local cluster and put the mesh's core features to work against two versions of the same app.
Requirements:
- Install Istio with
istioctl install, label your namespace for sidecar injection, and confirm a redeployed Pod shows2/2containers running. - Apply a mesh-wide
STRICTPeerAuthenticationand confirm withistioctl x describe podthat traffic between two meshed services is mTLS-encrypted. - Deploy a service with a
DestinationRulesetting outlier detection, then deliberately make it return 5xx errors and confirm it gets ejected from the load-balancing pool. - Deploy
v1andv2of a small app behind a weightedVirtualServicestarting at 90/10, send a burst of requests with a loop, and confirm the observed split roughly matches the configured weights. - Shift the weight to 100/0 in favor of
v2and confirm all new requests land on the new version with no Pod restarts required.
for i in $(seq 1 100); do curl -s checkout-service/version; done | sort | uniq -c from a debug Pod inside the mesh is a fast way to see the actual observed traffic split against your configured weights.
5. Knowledge Check
Three quick questions. Expand each to check your answer.
Q1
Why does the application container never need to know a sidecar proxy exists?
Why does the application container never need to know a sidecar proxy exists?
The mesh transparently intercepts network traffic at the Pod level using iptables rules set up when the sidecar is injected, so every outbound and inbound connection is routed through Envoy without the application changing its code or even its target address. That transparency is what lets a mesh add mTLS, retries and traffic shifting to existing services without touching a single line of their code.
Q2
Why is pairing retries with circuit breaking (outlier detection) important, rather than configuring retries alone?
Why is pairing retries with circuit breaking (outlier detection) important, rather than configuring retries alone?
Retries alone can amplify load onto a service that's already struggling — each failed call now generates multiple retried calls against the same overloaded target. Outlier detection ejects a consistently failing Pod from the pool for a cooldown period, so retries land on healthy Pods instead of continuing to hammer the one that's already failing.
Q3
What can a weighted VirtualService canary do that a Deployment's rolling update alone cannot?
What can a weighted VirtualService canary do that a Deployment's rolling update alone cannot?
A rolling update eventually routes all traffic to the new version regardless of how it's performing — there's no way to hold it at a fixed, small percentage of live traffic for observation. A weighted VirtualService keeps both versions running simultaneously and lets you set and adjust the exact traffic split, so a problem shows up against only 10% of real users instead of the entire fleet.