Istio is a service mesh for Kubernetes: it places an Envoy proxy next to every pod and lets you control traffic, encryption and telemetry between services without changing application code. In this tutorial you will install Istio with istioctl, deploy the Bookinfo sample application with automatic sidecar injection, send a weighted share of traffic to a new version of a service, enforce strict mutual TLS across the mesh and inspect everything in the Kiali dashboard.
Prerequisites
To follow this tutorial, you will need:
- A running Kubernetes cluster on a version supported by the current Istio release (check the support table at istio.io/latest/docs/releases/supported-releases). A CubePath managed Kubernetes cluster or a self-managed cluster on VPS or bare metal both work.
- At least 4 vCPUs and 8 GB of RAM free across the worker nodes. The
istiodcontrol plane alone requests 500m CPU and 2 GiB of memory. kubectlinstalled on your workstation and configured for the cluster, with cluster-admin permissions.- A Linux or macOS workstation with
curl.
Confirm that kubectl can reach the cluster before you start:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
worker-1 Ready <none> 12d v1.33.4
worker-2 Ready <none> 12d v1.33.4
worker-3 Ready <none> 12d v1.33.4
Step 1 - Downloading istioctl
istioctl is the command-line tool that installs, upgrades and inspects Istio. The official download script fetches the latest release, including istioctl and the sample manifests used later in this tutorial.
Download the script, read it, and then run it:
curl -L https://istio.io/downloadIstio -o downloadIstio.sh
less downloadIstio.sh
sh downloadIstio.sh
The script creates a directory named istio-<version>. Change into it and add its bin folder to your PATH for the current shell:
cd istio-*/
export PATH="$PWD/bin:$PATH"
Stay in this directory: the samples/ folder is used in the next steps. Check the client version:
istioctl version --remote=false
client version: 1.27.1
Your version number will likely be newer. Before installing, run the pre-flight check against the cluster:
istioctl x precheck
✔ No issues found when checking the cluster. Istio is safe to install or upgrade!
Step 2 - Installing the Istio control plane
Istio ships several configuration profiles. The default profile installs istiod and an ingress gateway and is the one recommended for production. The demo profile enables extra logging and lower resource requests and is meant for testing only.
Install the default profile. istioctl creates the istio-system namespace for you:
istioctl install --set profile=default -y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete
Verify that the control plane and the gateway are running:
kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
istio-ingressgateway-6b8c5d8f9d-7kq2x 1/1 Running 0 60s
istiod-7d4f9c6b8b-l2m9p 1/1 Running 0 75s
If you want to customize the installation later (resources, access logs, gateways), write an IstioOperator file and apply it with istioctl install -f your_file.yaml -y instead of passing many --set flags.
Step 3 - Enabling sidecar injection and deploying Bookinfo
Istio adds the Envoy sidecar to pods through a mutating webhook. The webhook only acts on namespaces carrying the istio-injection=enabled label, and only on pods created after the label is set.
Label the default namespace, where you will deploy the sample application:
kubectl label namespace default istio-injection=enabled
Deploy Bookinfo, a small application made of four services (productpage, details, reviews and ratings). The reviews service runs in three versions, labeled version: v1, v2 and v3:
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
Wait until all pods are ready, then list them:
kubectl wait --for=condition=Ready pods --all -n default --timeout=180s
kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-65cfcf56f9-4x9zq 2/2 Running 0 70s
productpage-v1-d5789fdfb-6nq7d 2/2 Running 0 70s
ratings-v1-7c9bd4b87f-8m2kc 2/2 Running 0 70s
reviews-v1-6584ddcf65-rkq5z 2/2 Running 0 70s
reviews-v2-6f85cb9b7c-9hxwt 2/2 Running 0 70s
reviews-v3-6f5b775685-5bt2v 2/2 Running 0 70s
READY 2/2 means each pod runs the application container plus the istio-proxy sidecar. Confirm the application responds from inside the mesh:
kubectl exec "$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')" -c ratings -- curl -sS productpage:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
NoteTo keep a single pod out of the mesh in a labeled namespace, add the label
sidecar.istio.io/inject: "false"to the pod template metadata of its Deployment.
Step 4 - Exposing the application through the ingress gateway
Traffic from outside the cluster enters the mesh through the Istio ingress gateway. The Bookinfo sample includes a Gateway that listens on port 80 and a VirtualService that routes /productpage and related paths to the productpage service:
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
Check the external address of the gateway:
kubectl get svc istio-ingressgateway -n istio-system
If your cluster has a load balancer integration, EXTERNAL-IP shows a public address and you can open http://EXTERNAL-IP/productpage in a browser. If it stays <pending>, forward a local port to the gateway for testing:
kubectl port-forward -n istio-system svc/istio-ingressgateway 8080:80
In a second terminal, request the page:
curl -s http://localhost:8080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
Reload http://localhost:8080/productpage in a browser a few times. Because there are no routing rules yet, requests to reviews are spread across v1 (no stars), v2 (black stars) and v3 (red stars).
Step 5 - Routing traffic with DestinationRules and VirtualServices
Two resources control routing inside the mesh:
- A
DestinationRuledefines named subsets of a service, usually by theversionlabel, and traffic policies such as connection limits. - A
VirtualServicedecides which subset receives each request.
Create a file for the reviews routing rules:
nano reviews-routing.yaml
Add the following content. The DestinationRule defines subsets v1 and v2 and adds outlier detection, which temporarily removes a pod from the load balancing pool after five consecutive 5xx errors (a basic circuit breaker). The VirtualService sends 90% of requests to v1 and 10% to v2:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 100
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10
timeout: 5s
retries:
attempts: 2
perTryTimeout: 2s
retryOn: 5xx,connect-failure
Apply it:
kubectl apply -f reviews-routing.yaml
Let istioctl validate the configuration in the namespace:
istioctl analyze
✔ No validation issues found when analyzing namespace: default.
Now reload the product page several times. Most responses show no stars (v1), about one in ten shows black stars (v2), and red stars (v3) no longer appear. To complete a canary rollout, raise the v2 weight step by step and apply the file again each time until v2 receives 100%.
Step 6 - Enforcing strict mutual TLS
By default Istio runs in permissive mode: sidecars encrypt traffic between each other with mutual TLS but still accept plain-text connections from workloads outside the mesh. Strict mode rejects any connection that is not mutual TLS.
First create a client outside the mesh, in a namespace without the injection label, to prove the difference:
kubectl create namespace legacy
kubectl run curl -n legacy --image=curlimages/curl --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/curl -n legacy --timeout=60s
Request the product page from this pod. In permissive mode it succeeds:
kubectl exec -n legacy curl -- curl -s -o /dev/null -w "%{http_code}\n" http://productpage.default:9080/productpage
200
Create a mesh-wide PeerAuthentication policy. A policy named default in the istio-system root namespace applies to the whole mesh:
nano mtls-strict.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
Apply it:
kubectl apply -f mtls-strict.yaml
Repeat the request from the legacy namespace:
kubectl exec -n legacy curl -- curl -s -o /dev/null -w "%{http_code}\n" http://productpage.default:9080/productpage
000
command terminated with exit code 56
The connection is reset because the client has no sidecar and cannot present a mesh certificate. Requests between Bookinfo services and through the ingress gateway keep working, since all of them run Envoy.
WarningBefore enabling
STRICTon a real cluster, make sure every client of your meshed services has a sidecar. Otherwise, apply the policy per namespace (setnamespaceto that namespace) and move namespaces over one at a time.
Step 7 - Visualizing the mesh with Kiali
Kiali draws the service graph from Istio metrics stored in Prometheus. The Istio release includes sample manifests for both. They are fine for learning; for production, run your own Prometheus and install Kiali with its operator or Helm chart.
Install the addons from the release directory:
kubectl apply -f samples/addons/prometheus.yaml
kubectl apply -f samples/addons/kiali.yaml
kubectl rollout status deployment/kiali -n istio-system
deployment "kiali" successfully rolled out
Generate some traffic so the graph has data. With the port-forward from Step 4 still running, run:
for i in $(seq 1 100); do curl -s -o /dev/null http://localhost:8080/productpage; done
Open the dashboard. istioctl forwards a local port and opens your browser:
istioctl dashboard kiali
In Kiali, open Traffic Graph, select the default namespace and enable the Security display option. You will see the path from istio-ingressgateway to productpage and on to reviews, with roughly 90% of reviews traffic going to v1, and a padlock on each edge showing that mutual TLS is in use.
Troubleshooting
- Pods show
1/1instead of2/2. The pods were created before the namespace was labeled, or the label is missing. Check withkubectl get namespace default --show-labels, then recreate the pods withkubectl rollout restart deployment -n default. istioctl analyzereportsReferencedResourceNotFoundfor a subset. TheVirtualServicepoints to a subset that noDestinationRuledefines, or the subset labels match no pods. Compare the subset labels withkubectl get pods --show-labels.- Requests return
503after applying a VirtualService. Usually the same subset mismatch as above. Check the sidecar logs withkubectl logs deploy/productpage-v1 -c istio-proxy. - The ingress gateway
EXTERNAL-IPstays<pending>. The cluster has no load balancer provider. Use aNodePortservice, a bare-metal load balancer such as MetalLB, or put an external load balancer in front of the node ports.
Conclusion
You installed Istio with the default profile, added Envoy sidecars to an application, shifted a controlled share of traffic to a new version with a DestinationRule and a VirtualService, enforced strict mutual TLS and inspected the result in Kiali. From here, you can restrict which services may call each other with AuthorizationPolicy resources, expose HTTPS on the ingress gateway with certificates from cert-manager, or evaluate Istio's ambient mode, which removes the per-pod sidecar.
