Lesson 6 of 16
Deploy Bookinfo on the mesh
For the rest of the course we need a multi-service application running behind a service mesh. Without a mesh you can't observe inter-service traffic; without multiple services there's nothing meaningful to break. Istio's Bookinfo sample gives us four services and three versions of one of them — enough surface area for every incident-response scenario later in the course.
Working directory check
Stay in the workspace — the manifest you write later in this lesson belongs in ~/incident-cluster/ alongside kind-incident.yaml:
cd ~/incident-cluster
Install Istio
istioctl install --set profile=demo -y
The demo profile installs istiod (the control plane), an ingress gateway, an egress gateway, and turns on permissive mTLS. It is not production-tuned — that's deliberate; we want generous defaults so we can focus on operator behaviour, not Istio sizing.
Auto-inject sidecars in demo
kubectl create namespace demo
kubectl label namespace demo istio-injection=enabled
The label is the trigger. From now on, every pod created in demo gets a mutating webhook applied at admission time that adds two extra containers to the pod spec — we'll inspect them in a moment.
Deploy the Bookinfo sample
kubectl apply -n demo \
-f https://raw.githubusercontent.com/istio/istio/release-1.29/samples/bookinfo/platform/kube/bookinfo.yaml
Six pods come up — one each for details, productpage, ratings, and three versions of reviews. Watch them go ready:
kubectl get pods -n demo -w
-w streams updates. Wait until every pod shows 2/2 Running. Ctrl-C to exit.
What's inside one of those pods
Pick any pod and describe it:
kubectl describe pod -n demo -l app=ratings
Three containers, not one:
istio-init— an init container that runsistio-iptablesonce. It writes iptables rules into the pod's network namespace that silently redirect every TCP packet to Envoy on:15001(outbound) and:15006(inbound). Then it exits. The app container has no idea this happened.istio-proxy— the Envoy data-plane proxy. Notice it appears underInit Containers:even though it runs for the pod's lifetime. That's the native sidecar pattern (KEP-753, GA in Kubernetes 1.29+): sidecars are declared as init containers withrestartPolicy: Always, so they start before the app and shut down after it. This fixes two long-standing pain points — the proxy not being ready when the app's first request fires, and the proxy dying mid-shutdown while the app is still draining.ratings(or whichever app the pod is for) — the actual workload. Plain HTTP on:9080. No mesh awareness.
The app and the proxy share a network namespace, so the proxy can intercept traffic transparently.
Expose Bookinfo through the ingress gateway
Save this as bookinfo-gateway.yaml in ~/incident-cluster/:
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: demo
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: bookinfo
namespace: demo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri: { exact: /productpage }
- uri: { prefix: /static }
- uri: { exact: /login }
- uri: { exact: /logout }
- uri: { prefix: /api/v1/products }
route:
- destination:
host: productpage
port:
number: 9080
kubectl apply -f bookinfo-gateway.yaml
Two distinct objects:
Gatewayconfigures the ingress gateway pod (selected byistio: ingressgatewayinistio-system) to listen on:80. It's the L4/L7 listener config — what ports, what protocols.VirtualServiceis the routing rule: incoming requests matching one of those URI patterns go to theproductpageservice on:9080. Anything else returns 404. That's the security model — you only expose what you list.
Note the mix of exact: and prefix:. Use exact for single fixed URLs (/login, /productpage); use prefix when you want everything under a path (/static/..., /api/v1/products/...). Be aware: prefix is a string prefix, not a path-segment prefix — prefix: /api matches /apivalue too. Istio 1.22+ has pathSeparatedPrefix if you need segment-aligned matching.
Hit it
kind doesn't have a real load balancer, so port-forward the ingress gateway from your laptop:
kubectl -n istio-system port-forward svc/istio-ingressgateway 8080:80
Leave that running and open another terminal:
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8080/productpage
HTTP 200 means the full path works: laptop → port-forward → ingress gateway pod → productpage service → productpage Envoy → productpage app. Open http://localhost:8080/productpage in a browser to see the rendered UI.
Watch the three review versions cycle
Refresh http://localhost:8080/productpage in the browser five or six times. The Book Reviews panel rotates through three states:
- No stars —
reviews-v1handled this request. v1 doesn't callratings, so there's nothing to render. - Black stars —
reviews-v2handled it. v2 callsratingsand renders the score in black. - Red stars —
reviews-v3handled it. Same data as v2, just rendered in red.
The review text is identical across all three; only the stars tell you which version served the request. That visual difference is exactly why Bookinfo is the canonical Istio teaching app — "which version got this traffic?" is something you answer with your eyes, not by reading logs. Later lessons that intentionally break one version rely on you being able to spot the change at a glance.
Where is that round-robin actually configured?
Look at the two objects we've applied. The Gateway listens on port 80; the VirtualService routes a handful of URI patterns to the productpage service. Neither one mentions reviews at all. So where is the load-balancing across v1/v2/v3 coming from?
Three layers, none of them an Istio routing rule we wrote:
The Kubernetes Service
reviewshas selectorapp: reviews. All three deployments —reviews-v1,-v2,-v3— carry that label, so every pod across all three versions is an endpoint of the same service. Confirm it:kubectl get endpointslices -n demo -l kubernetes.io/service-name=reviewsThree IPs, one per pod.
istiodwatches that endpoint list and pushes it to every Envoy sidecar in the mesh as a single Envoy "cluster" with three endpoints.productpage's Envoy sidecar is the proxy that actually opens the connection toreviews:9080. With noDestinationRuleoverriding it, Envoy applies its default load-balancing policy — modern Istio defaults toLEAST_REQUEST, which under our trickle of browser-refresh traffic spreads requests fairly evenly across the three endpoints. Each refresh of/productpage= one new HTTP call from productpage to reviews, so each refresh can land on a different version.
That's the baseline mesh behaviour: a Kubernetes Service with multiple labelled deployments load-balances across all of them, equally, by default. To change that — pin a specific version, weight a 90/10 canary, send header-tagged users to v2 — you'd add a DestinationRule declaring v1/v2/v3 as named subsets and a VirtualService that routes to those subsets. We'll do that deliberately later when an operator decides to shift traffic away from a misbehaving version. For now, the round-robin you're seeing is "what you get for free" — there's no reviews-specific routing config anywhere in the cluster.
Acceptance test
This is the lab the rest of the course depends on. Before continuing, verify:
kubectl get pods -n demo # 6 pods, all 2/2 Running
kubectl get pods -n istio-system # istiod + ingress gateway Running
kubectl get gateway,virtualservice -n demo # both objects present
curl -s http://localhost:8080/productpage | head # HTML, not "connection refused"
If any of those fails, fix it now. Later lessons will assume this stack is healthy and instead deliberately break parts of it — that's only a useful exercise if your starting point is known good.
Why this lab specifically
Three things make Bookinfo on Istio the right teaching stack for incident response:
- Multiple cooperating services —
productpagecalls bothdetailsandreviews;reviewscallsratings. A failure in one cascades visibly. Single-service apps can't show this. - Three versions of
reviews— gives us a built-in canary surface. We can use Istio traffic policy to send 1% of traffic to a "broken" version and watch how detection + rollback play out. - Sidecar mesh by default — every request is mediated by Envoy, which means we have universal telemetry and the ability to inject faults (delays, errors, abort) declaratively. Operators in later lessons will read this telemetry and act on it.
Keep the cluster running between lessons. The next lesson covers a clean teardown — port-forward, cluster, and Docker Desktop — so you can stop the lab without leaving anything running in the background.