Istio
AKS Istio Mesh Setup (Central US)
1) Variables
RG="rg-aks-istio-centralus"
AKS="aks-istio-centralus"
LOCATION="centralus"
2) Prerequisites
2.1 Ensure Azure CLI is up to date
Azure requires a recent Azure CLI for the Istio add-on workflow. (Microsoft Learn)
az version
2.2 Login and select subscription (if needed)
az login
az account show
# az account set --subscription "<subscription-id>"
3) Create Resource Group
az group create --name "$RG" --location "$LOCATION"
4) Pick an Istio revision available in Central US
Azure publishes “Istio revisions” per region. Always pick from the output of this command. (Microsoft Learn)
az aks mesh get-revisions --location "$LOCATION" -o table
Pick one revision from the list (example: asm-1-27 or whatever you see in your output):
REVISION="asm-1-27"
5) Create AKS cluster with Istio add-on enabled
Option A: Create new cluster with mesh enabled
This is the standard path. (Microsoft Learn)
az aks create \
--resource-group "$RG" \
--name "$AKS" \
--location "$LOCATION" \
--enable-asm \
--revision "$REVISION" \
--generate-ssh-keys
Option B: Enable mesh on an existing cluster (if you already have one)
Azure provides az aks mesh enable for existing clusters. (Microsoft Learn)
az aks mesh enable --resource-group "$RG" --name "$AKS"
6) Get kubeconfig
az aks get-credentials --resource-group "$RG" --name "$AKS" --overwrite-existing
7) Verify Istio control plane is running
AKS-managed Istio control plane is in aks-istio-system. (Microsoft Learn)
kubectl get pods -n aks-istio-system
Also confirm mesh mode from Azure:
az aks show -g "$RG" -n "$AKS" --query "serviceMeshProfile.mode" -o tsv
az aks show -g "$RG" -n "$AKS" --query "serviceMeshProfile.istio.revisions" -o tsv
8) Create an app namespace and enable sidecar injection (revision-based)
AKS add-on uses revision label for injection (not istio-injection=enabled). (Microsoft Learn)
Example for Bookinfo:
kubectl create namespace bookinfo
kubectl label namespace bookinfo istio.io/rev="$REVISION" --overwrite
kubectl get ns bookinfo --show-labels
9) Enable Istio Ingress Gateway (External)
Azure supports external or internal ingress gateways. For Internet access, enable external. (Microsoft Learn)
az aks mesh enable-ingress-gateway \
--resource-group "$RG" \
--name "$AKS" \
--ingress-gateway-type external
Verify gateway service:
kubectl get svc -n aks-istio-ingress
kubectl get svc aks-istio-ingressgateway-external -n aks-istio-ingress -o wide
Azure documents the aks-istio-ingress namespace and the gateway enablement flow. (Microsoft Learn)
10) Get the external gateway IP (for later Bookinfo access)
INGRESS_HOST=$(kubectl -n aks-istio-ingress get svc aks-istio-ingressgateway-external -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
INGRESS_PORT=$(kubectl -n aks-istio-ingress get svc aks-istio-ingressgateway-external -o jsonpath='{.spec.ports[0].port}')
echo "Gateway IP: $INGRESS_HOST Port: $INGRESS_PORT"
11) (Optional) Mesh-wide config uses shared ConfigMap per revision
If later you configure mesh-wide settings, AKS add-on expects a shared configmap named istio-shared-configmap-<revision> in aks-istio-system. (Microsoft Learn)
Introduction
Now that Bookinfo is working on AKS with the Istio add-on enabled, this is the right time to deeply understand traffic management in Istio.
In this session, we will not just talk about theory. We will apply practical experiments using kubectl so that you clearly see how traffic decisions are controlled inside the mesh.
By the end of this tutorial, you will understand:
- How Istio routes traffic
- How canary deployments work
- How header-based routing works
- How to inject faults
- How to configure retries and timeouts
- How circuit breaking protects services
- How mirroring works
- How to debug traffic behavior
Pre-requisites
- AKS cluster with Istio add-on enabled
- Bookinfo deployed in namespace
bookinfo - External ingress gateway enabled
- kubectl configured and working
Step 0: Get Gateway URL
First, get the external gateway IP and port.
INGRESS_HOST=$(kubectl -n aks-istio-ingress get svc aks-istio-ingressgateway-external -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
INGRESS_PORT=$(kubectl -n aks-istio-ingress get svc aks-istio-ingressgateway-external -o jsonpath='{.spec.ports[0].port}')
export GATEWAY_URL="$INGRESS_HOST:$INGRESS_PORT"
echo "http://$GATEWAY_URL/productpage"
Test that Bookinfo is accessible:
curl -s "http://$GATEWAY_URL/productpage" | grep -o "<title>.*</title>"
If this works, we are ready to experiment.
Architecture Refresher
When a request flows through Bookinfo, this is what happens:
Client AKS Load Balancer Istio Ingress Gateway productpage (with Envoy sidecar) reviews (with Envoy sidecar) ratings (with Envoy sidecar) details (with Envoy sidecar)
Every request goes through Envoy proxies. Istio control plane pushes routing rules to these proxies.
Step 1: Create Subsets for Reviews Service
Before doing version-based routing, we must define subsets.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews-destination
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
EOF
Verify:
kubectl get destinationrule -n bookinfo
This maps subset names to pod labels.
Step 2: Reset to Stable (100% v1)
Always start from a clean baseline.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 100
EOF
Now all traffic goes to v1.
Step 3: Canary Deployment (80% v1, 20% v2)
This is how controlled rollout works.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 80
- destination:
host: reviews
subset: v2
weight: 20
EOF
Test multiple requests:
for i in {1..30}; do
curl -s "http://$GATEWAY_URL/productpage" | grep -q "glyphicon-star" && echo "stars" || echo "no-stars"
done | sort | uniq -c
You will see mixed results.
This is exactly how production systems gradually shift traffic.
Step 4: Header-Based Routing
We will route a specific user to v2.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
EOF
Test:
curl -H "end-user: jason" http://$GATEWAY_URL/productpage
Now only that user sees v2.
This is how feature flags and beta user testing are implemented.
Step 5: Fault Injection (Delay Testing)
Simulate a slow service.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- fault:
delay:
percentage:
value: 100
fixedDelay: 5s
route:
- destination:
host: reviews
subset: v1
EOF
Test:
time curl -s http://$GATEWAY_URL/productpage > /dev/null
You should see around 5 seconds delay.
This is useful for resilience testing.
Step 6: Timeout Configuration
Prevent long waits.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- timeout: 2s
route:
- destination:
host: reviews
subset: v1
EOF
Now the request fails quickly instead of waiting.
Step 7: Retries
Handle transient failures.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- timeout: 2s
retries:
attempts: 3
perTryTimeout: 1s
retryOn: 5xx,connect-failure,refused-stream,reset
route:
- destination:
host: reviews
subset: v1
EOF
Retries are useful when services fail intermittently.
Step 8: Traffic Mirroring
Send traffic to v2 silently while users still see v1.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
mirror:
host: reviews
subset: v2
mirrorPercentage:
value: 100
EOF
Generate traffic:
for i in {1..20}; do curl -s http://$GATEWAY_URL/productpage > /dev/null; done
Check logs:
kubectl logs -n bookinfo -l app=reviews,version=v2
This is used for shadow deployments.
Step 9: Circuit Breaking
Protect downstream services.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: reviews-cb
spec:
host: reviews
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 5s
baseEjectionTime: 30s
maxEjectionPercent: 100
EOF
This prevents overload and ejects unhealthy instances.
Step 10: Reset to Stable
Always reset to normal after experiments.
kubectl delete virtualservice reviews -n bookinfo
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 100
EOF
Final Understanding
Istio traffic management gives you centralized, declarative control over:
- Version rollout
- User targeting
- Resilience
- Testing
- Failure handling
- Production protection
All without modifying application code.
This is why service meshes are powerful in microservices architectures.
Perfect. Now that traffic experiments are clear, let us move to security and enable mTLS (Mutual TLS) in your Bookinfo mesh.
I will guide you step by step and also show you how to test and prove that mTLS is actually working.
This assumes:
- AKS with Istio add-on
- Namespace
bookinfo - Sidecar injection already enabled
What is mTLS in Istio?
mTLS means:
- Client verifies server identity
- Server verifies client identity
- Traffic between services is encrypted
- Certificates are issued automatically by Istio
In Istio, this happens at the Envoy sidecar level, not inside application code.
Step 1: Check Current mTLS Mode
By default, Istio may run in PERMISSIVE mode.
Check existing PeerAuthentication:
kubectl get peerauthentication -A
If nothing is defined, mesh may still allow plaintext traffic.
Step 2: Enable STRICT mTLS in Bookinfo Namespace
We will enable STRICT mode for namespace bookinfo.
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: STRICT
EOF
Verify:
kubectl get peerauthentication -n bookinfo
kubectl describe peerauthentication default -n bookinfo
Now:
All service-to-service communication in this namespace must use mTLS.
Step 3: Verify Sidecars Have Certificates
Pick one pod:
kubectl get pods -n bookinfo
Exec into productpage pod:
kubectl exec -it <productpage-pod> -n bookinfo -- ls /etc/certs
You should see:
- cert-chain.pem
- key.pem
- root-cert.pem
This confirms Istio issued certificates.
Step 4: Functional Test (Application Still Works)
Test Bookinfo from outside:
curl -s http://$GATEWAY_URL/productpage | grep -o "<title>.*</title>"
It should still work.
Reason:
- Gateway also participates in mTLS
- Internal traffic is encrypted automatically
Step 5: Prove Plaintext Traffic is Blocked
This is the important test.
We will try to call a service directly from inside the cluster without mTLS.
Create a temporary debug pod WITHOUT sidecar:
kubectl run tmp-shell -n bookinfo --rm -it --image=curlimages/curl -- sh
Inside that shell, try:
curl http://reviews:9080/reviews/1
Expected result:
Connection should fail.
Why?
Because:
- STRICT mode requires mTLS
- This pod has no sidecar
- No certificate is presented
- Connection rejected
Exit the pod.
Step 6: Compare with Sidecar Pod
Now exec into a pod that has sidecar:
kubectl exec -it <productpage-pod> -n bookinfo -- sh
Inside:
curl http://reviews:9080/reviews/1
This should succeed.
Reason:
- Sidecar injects mTLS
- Certificates exchanged
- Request allowed
This is the strongest proof that mTLS is active.
Step 7: Inspect mTLS Status Using istioctl (Optional but Recommended)
If you have istioctl installed:
istioctl authn tls-check <productpage-pod>.bookinfo
You should see:
- Mode: ISTIO_MUTUAL
This confirms encrypted traffic.
Step 8: Enable STRICT mTLS for Entire Namespace (Explicit Policy)
You can scope it explicitly:
kubectl apply -f - <<'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: bookinfo-mtls
namespace: bookinfo
spec:
mtls:
mode: STRICT
EOF
Now all workloads must use mTLS.
Step 9: What Actually Happens Internally
When productpage calls reviews:
- Envoy intercepts request
- Envoy establishes TLS handshake
- Certificate exchanged
- Identity verified
- Encrypted channel created
- Request forwarded
All of this without modifying application code.
Step 10: Reset mTLS (If Needed)
If something breaks and you want to revert:
kubectl delete peerauthentication default -n bookinfo
Or switch to PERMISSIVE:
kubectl apply -n bookinfo -f - <<'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
spec:
mtls:
mode: PERMISSIVE
EOF
What You Just Achieved
You now:
- Enforced encrypted service-to-service traffic
- Verified certificate issuance
- Proved plaintext calls are blocked
- Understood how Envoy handles TLS automatically
This is zero-trust networking inside Kubernetes.
