Istio Hands-on Part 3 - Understanding Sidecar Injection and Traffic Flow
Objective
This part covers the data plane: the sidecar proxy. You will:
- Compare the two Istio deployment modes, sidecar and ambient
- Enable sidecar injection for a namespace
- Deploy a frontend and a backend
- Confirm that each pod got a sidecar
- Follow one request through both proxies
- Read Envoy configuration and access logs

Step 1: Deployment modes
Istio has two modes.
| Feature | Sidecar mode | Ambient mode |
|---|---|---|
| Architecture | One Envoy container in each pod | One ztunnel proxy per node |
| Overhead | Higher, one proxy per pod | Lower, one proxy per node |
| Layer support | Full L7: HTTP, gRPC, routing, retries, fault injection | L4 (TCP, TLS) by default |
| mTLS | Done by the sidecar in each pod | Done by ztunnel for the node |
| Upgrades | Pods restart | No pod restart |
This series uses sidecar mode. Ambient mode gets its own post later.
Step 2: What a sidecar is
A sidecar is an Envoy container that runs in the same pod as your application container. It receives all inbound and outbound traffic of the pod.
The sidecar:
- Routes traffic, retries failed calls, and injects faults when told to
- Encrypts service-to-service calls with mTLS
- Emits metrics, logs, and traces
All sidecars together are the data plane. Istiod is the control plane. It sends configuration and certificates to each sidecar.
Step 3: Enable automatic injection
Label the namespace:
kubectl label namespace default istio-injection=enabledAfter this, a mutating webhook adds the Envoy sidecar to every new pod in the namespace.
Step 4: Deploy a sample application
The application has two services. frontend calls backend every five seconds.
4.1 Backend
cat <<EOF | kubectl apply -f -apiVersion: apps/v1kind: Deploymentmetadata: name: backendspec: replicas: 1 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: hashicorp/http-echo args: ["-text=Hello from Backend v1"] ports: - containerPort: 5678---apiVersion: v1kind: Servicemetadata: name: backendspec: selector: app: backend ports: - port: 80 targetPort: 5678EOF4.2 Frontend
cat <<EOF | kubectl apply -f -apiVersion: apps/v1kind: Deploymentmetadata: name: frontendspec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: curlimages/curl command: ["sh", "-c"] args: ["while true; do curl -s http://backend; sleep 5; done"]---apiVersion: v1kind: Servicemetadata: name: frontendspec: selector: app: frontend ports: - port: 80 targetPort: 80EOF4.3 Confirm injection
kubectl get podsExpected output:
NAME READY STATUS RESTARTS AGEbackend-684d96759f-p8psg 2/2 Running 0 35sfrontend-b7674d6f8-jjc42 2/2 Running 0 26sEach pod shows 2/2: the application container and istio-proxy.
Describe a pod to see the injection annotations:
kubectl describe pod $(kubectl get pods |grep backend |head -1 |awk '{print $1}')Look for:
Annotations: istio.io/rev: default sidecar.istio.io/status: {"initContainers":["istio-init","istio-proxy"], ...}The sidecar is in place.
Step 5: Follow one request
When frontend calls backend, the request passes through two proxies.
- The frontend container sends an HTTP request to
backend. - The frontend Envoy intercepts the outbound call on port 15001.
- Envoy sends the request to the backend Envoy over mTLS.
- The backend Envoy checks the certificate, decrypts, and forwards to the backend container.
- The response takes the same path back.
The diagram below shows both modes and the request path.
Check the mTLS mode of the backend pod:
kubectl get pod $(kubectl get pods |grep backend |head -1 |awk '{print $1}') -o jsonpath='{.metadata.labels.security\.istio\.io/tlsMode}'Expected output:
istioTraffic between the two pods is encrypted and authenticated with Istio certificates.
Step 6: Read the Envoy configuration
istioctl can show the live Envoy configuration of a pod.
Routes:
istioctl proxy-config routes $(kubectl get pods |grep backend |head -1 |awk '{print $1}')Listeners:
istioctl proxy-config listeners $(kubectl get pods |grep backend |head -1 |awk '{print $1}')Sync status of all proxies:
istioctl proxy-statusBoth frontend and backend show SYNCED. Their sidecars have the latest configuration from Istiod.
Step 7: Read the access logs
kubectl logs deploy/frontend -c istio-proxy --tail=10Expected output:
[2025-11-06T12:28:43.548Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 22 1 1 "-" "curl/8.17.0" "abccfcd4-8804-9de0-9cf8-86ac2937e85b" "backend" "10.244.0.12:5678" outbound|80||backend.default.svc.cluster.local 10.244.0.16:59050 10.96.245.140:80 10.244.0.16:33832 - default[2025-11-06T12:28:48.563Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 22 1 1 "-" "curl/8.17.0" "fb8782e3-211b-9d94-a6de-f0f179dedcc3" "backend" "10.244.0.12:5678" outbound|80||backend.default.svc.cluster.local 10.244.0.16:59050 10.96.245.140:80 10.244.0.16:33842 - default[2025-11-06T12:28:53.578Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 22 2 2 "-" "curl/8.17.0" "967db056-a4b4-9dde-a2b1-f730baf36b6b" "backend" "10.244.0.12:5678" outbound|80||backend.default.svc.cluster.local 10.244.0.16:58050 10.96.245.140:80 10.244.0.16:59848 - defaultEach line is one request from frontend to backend, as seen by the frontend sidecar.
The mesh works. Both services talk through Envoy. Part 4 uses that to control where traffic goes.
Comments