Skip to main content

Documentation Index

Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt

Use this file to discover all available pages before exploring further.

This lesson walks through a concise Istio VirtualService demo using the httpbin sample and a test pod. It shows how VirtualService routes influence traffic only when Envoy sidecars are present, and demonstrates URL rewriting. Follow the steps to reproduce the examples and understand the behavior.

TL;DR

  • Deploy the httpbin sample in the default namespace.
  • Create a test namespace and a simple pod to curl httpbin.
  • Create a VirtualService that initially mirrors routing, then change it to a non-listening port to observe broken traffic.
  • Enable Istio sidecar injection in the test namespace so Envoy enforces the VirtualService routing.
  • Demonstrate URL rewrites and review common VirtualService capabilities.

Prerequisites

  • Kubernetes cluster with Istio installed.
  • kubectl configured to talk to your cluster.
  • istioctl installed (used for analysis).

1) Check namespace injection label

Confirm whether automatic Istio sidecar injection is enabled in the default namespace:
# kubectl get ns --show-labels
NAME                STATUS   AGE   LABELS
default             Active   15m   istio-injection=enabled,kubernetes.io/metadata.name=default
istio-system        Active   12m   kubernetes.io/metadata.name=istio-system
kube-node-lease     Active   15m   kubernetes.io/metadata.name=kube-node-lease
kube-public         Active   15m   kubernetes.io/metadata.name=kube-public
kube-system         Active   15m   kubernetes.io/metadata.name=kube-system

2) Deploy the httpbin sample

Apply the official httpbin sample to the default namespace:
kubectl apply -f https://raw.githubusercontent.com/istio/istio/refs/heads/master/samples/httpbin/httpbin.yaml

# Expected output
# serviceaccount/httpbin created
# service/httpbin created
# deployment.apps/httpbin created
Verify the httpbin pod is running with the Envoy sidecar (READY should show 2/2):
kubectl get pods
# NAME                          READY   STATUS    RESTARTS   AGE
# httpbin-787cdcc9df-hs5q2      2/2     Running   0          7s

3) Create a test namespace and pod (no sidecar initially)

Create a namespace test (not injection-enabled by default) and run a simple nginx pod there:
kubectl create ns test
kubectl run test --image=nginx -n test
kubectl get pods -n test
# NAME   READY   STATUS    RESTARTS   AGE
# test   1/1     Running   0          5s
Exec into the test pod and verify you can reach httpbin in the default namespace (httpbin listens on port 8000):
kubectl exec -ti -n test test -- /bin/bash
# root@test:/# curl httpbin.default.svc:8000/ip
# {
#   "origin": "127.0.0.6:49657"
# }
# root@test:/# curl httpbin.default.svc:8000/user-agent
# {
#   "user-agent": "curl/7.88.1"
# }
# root@test:/# exit
Note: Istio is permissive by default for cross-namespace communication unless you enable strict policies (e.g., mTLS strict mode). This helps avoid accidental service disruption after installing Istio.

4) Create a VirtualService that mirrors default routing

Save the following manifest as vs.yaml — this first version routes HTTP traffic to port 8000 (same as the Kubernetes Service):
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
  namespace: default
spec:
  hosts:
  - httpbin
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: httpbin.default.svc.cluster.local
        port:
          number: 8000
Apply the VirtualService:
kubectl apply -f vs.yaml
# virtualservice.networking.istio.io/httpbin configured
Confirm it exists:
kubectl get virtualservice
# NAME      GATEWAYS   HOSTS      AGE
# httpbin   <none>     httpbin    10m
At this point, you won’t see any change in behavior because the VirtualService matches the service’s default routing.

5) Modify the VirtualService to break traffic (demonstrate effect)

Edit vs.yaml to route to port 9000, where httpbin is not listening:
# vs.yaml (modified to break traffic)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
  namespace: default
spec:
  hosts:
  - httpbin
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: httpbin.default.svc.cluster.local
        port:
          number: 9000
Apply the broken VirtualService:
kubectl apply -f vs.yaml
# virtualservice.networking.istio.io/httpbin configured
You might still be able to curl httpbin from the test pod even after applying the broken VirtualService. Why? Because the test pod currently runs without an Envoy sidecar (its namespace was not injection-enabled) so its outbound traffic bypasses Istio and is unaffected by the VirtualService. Check the namespace labels and run analysis:
kubectl get ns --show-labels
istioctl analyze -n test
# Info [IST0102] (Namespace test) The namespace is not enabled for Istio injection. Run 'kubectl label namespace test istio-injection=enabled' to enable it, or 'kubectl label namespace test istio-injection=disabled' to explicitly mark it as not needing injection.

6) Enable injection and recreate the pod so Envoy gets injected

Enable automatic sidecar injection for test, then delete and recreate the pod so it receives an Envoy sidecar:
kubectl label namespace test istio-injection=enabled
kubectl delete pod test -n test
kubectl run test --image=nginx -n test
kubectl get pods -n test
# NAME   READY   STATUS    RESTARTS   AGE
# test   2/2     Running   0          10s
Now the test pod’s outbound traffic goes through Envoy and is governed by the VirtualService. Exec into the pod and try to reach httpbin:
kubectl exec -ti -n test test -- /bin/bash
# root@test:/# curl -I httpbin.default.svc:8000/ip
# HTTP/1.1 503 Service Unavailable
# ...
# root@test:/# exit
The 503 Service Unavailable is expected because the VirtualService routes to port 9000 (where nothing listens). Fix the VirtualService to route back to port 8000 and re-apply:
# vs.yaml (fixed)
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
  namespace: default
spec:
  hosts:
  - httpbin
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: httpbin.default.svc.cluster.local
        port:
          number: 8000
kubectl apply -f vs.yaml
# virtualservice.networking.istio.io/httpbin configured
Now curl from the test pod should work again.
You must enable Istio sidecar injection in a namespace for Envoy to enforce VirtualService routing from pods in that namespace. Pods without a sidecar send traffic directly and are not affected by VirtualService rules.

URL rewrites

VirtualServices can rewrite request URIs before forwarding. Add a second HTTP match that rewrites /hello to /:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin
  namespace: default
spec:
  hosts:
  - httpbin
  http:
  - match:
    - uri:
        prefix: /hello
    rewrite:
      uri: /
    route:
    - destination:
        host: httpbin.default.svc.cluster.local
        port:
          number: 8000
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: httpbin.default.svc.cluster.local
        port:
          number: 8000
Before applying the rewrite, requesting /hello returns 404 because httpbin has no /hello endpoint. After applying the VirtualService with the rewrite, requests to /hello are rewritten to / and succeed:
kubectl apply -f vs.yaml
kubectl exec -ti -n test test -- /bin/bash
# root@test:/# curl httpbin.default.svc:8000/ip
# {
#   "origin": "127.0.0.6:36879"
# }
# root@test:/# curl httpbin.default.svc:8000/hello
# 404 page not found   # (before apply)
#
# # After apply:
# root@test:/# curl httpbin.default.svc:8000/hello
# { ... httpbin response for / ... }
# root@test:/# exit
The httpbin sample exposes many useful endpoints — for example, /ip, /user-agent, /status/<code>, and /dump/request. The following is an example HTML snippet from the httpbin project to illustrate a typical response:
<h3 id="-curl-http-httpbin-org-dump-request">$ curl https://httpbingo.org/dump/request?foo=bar</h3>

<pre><code>GET /dump/request?foo=bar HTTP/1.1
Host: httpbingo.org
Accept: */*
User-Agent: curl/7.64.1
</code></pre>

<h3 id="-curl-I-http-httpbin-org-status-418">$ curl -I https://httpbingo.org/status/418</h3>

<pre><code>HTTP/1.1 418 I'm a teapot
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
X-More-Info: http://tools.ietf.org/html/rfc2324
Date: Tue, 13 Jul 2021 13:12:37 GMT
Content-Length: 0
</code></pre>

<h2 id="AUTHOR">AUTHOR</h2>

<p>Ported to Go by <a href="https://github.com/mccutchen">Will McCutchen</a>.</p>
<p>From <a href="https://httpbin.org/">the original</a> <a href="https://kennethreitz.org/">Kenneth Reitz</a> project.</p>

VirtualService capabilities (quick reference)

Here’s a short reference of common VirtualService features and what they’re used for.
CapabilityPurpose
TimeoutsSet per-route request timeouts (e.g., timeout: 5s)
Header-based matchesMatch traffic by request headers and URI (e.g., headers.end-user.exact: jason)
Rewrite / RedirectRewrite the request URI or issue redirects to different path/authority
RetriesConfigure retry attempts, per-try timeout, and conditions (e.g., attempts: 3)
Examples of these capabilities are shown below for easy copy/paste during exams or troubleshooting.
  • Timeouts example:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: my-productpage-rule
  namespace: istio-system
spec:
  hosts:
  - productpage.prod.svc.cluster.local
  http:
  - timeout: 5s
    route:
    - destination:
        host: productpage.prod.svc.cluster.local
  • Header-based matches example:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings-route
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - match:
    - headers:
        end-user:
          exact: jason
      uri:
        prefix: "/ratings/v2/"
    route:
    - destination:
        host: ratings.prod.svc.cluster.local
  • Rewrite and redirect examples:
# Rewrite example:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings-rewrite
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - match:
    - uri:
        prefix: /ratings
    rewrite:
      uri: /v1/bookRatings
    route:
    - destination:
        host: ratings.prod.svc.cluster.local
        subset: v1

# Redirect example:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings-redirect
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - match:
    - uri:
        exact: /v1/getProductRatings
    redirect:
      uri: /v1/bookRatings
      authority: newratings.default.svc.cluster.local
  • Retries example:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings-retries
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - route:
    - destination:
        host: ratings.prod.svc.cluster.local
        subset: v1
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream
A screenshot of the Istio website documentation showing the "Virtual Service" page, with the article text in the center, a left navigation menu, and a right-side table of contents. The browser tabs and URL bar are visible at the top.

Closing notes

This minimal demo demonstrates how VirtualService routing rules can change or break traffic when Envoy sidecars are present. In real deployments, VirtualServices are used widely together with DestinationRules, Gateways, and policies for traffic shaping, retries, timeouts, redirects, and header manipulation. Keep the official Istio docs handy for reference and canonical examples:

Watch Video

Practice Lab