learnclaude .dev
← Kubernetes Operators, Istio, Incident Response & AI

Lesson 6 of 16

Deploy Bookinfo on the mesh

doc

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 runs istio-iptables once. 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 under Init 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 with restartPolicy: 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:

  • Gateway configures the ingress gateway pod (selected by istio: ingressgateway in istio-system) to listen on :80. It's the L4/L7 listener config — what ports, what protocols.
  • VirtualService is the routing rule: incoming requests matching one of those URI patterns go to the productpage service 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 starsreviews-v1 handled this request. v1 doesn't call ratings, so there's nothing to render.
  • Black starsreviews-v2 handled it. v2 calls ratings and renders the score in black.
  • Red starsreviews-v3 handled 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:

  1. The Kubernetes Service reviews has selector app: 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=reviews
    

    Three IPs, one per pod.

  2. istiod watches that endpoint list and pushes it to every Envoy sidecar in the mesh as a single Envoy "cluster" with three endpoints.

  3. productpage's Envoy sidecar is the proxy that actually opens the connection to reviews:9080. With no DestinationRule overriding it, Envoy applies its default load-balancing policy — modern Istio defaults to LEAST_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:

  1. Multiple cooperating servicesproductpage calls both details and reviews; reviews calls ratings. A failure in one cascades visibly. Single-service apps can't show this.
  2. 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.
  3. 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.

View source documentation →