1660 words
8 minutes
Getting Started with Istio: Installing the Service Mesh & Running the Bookinfo Demo on Kubernetes

One of the most common next steps after setting up a Kubernetes cluster is adding a service mesh. Istio is the most widely adopted service mesh in the CNCF ecosystem — it gives you automatic mTLS encryption, traffic routing, observability, and resilience features without changing a single line of application code.

This guide walks through the full workflow:

  1. Installing istioctl and the Istio control plane (default profile)
  2. Enabling automatic Envoy sidecar injection
  3. Deploying and exploring the Bookinfo sample application
  4. Exposing the app on a bare-metal / single-node cluster (no cloud LoadBalancer required)
  5. Visualizing your service mesh with the Kiali dashboard
  6. Testing traffic routing with VirtualServices and DestinationRules

Official reference: Istio Installation Guide


Configuration Placeholders#

PlaceholderDescriptionExample
<NODE_IP>IP address of your Kubernetes node192.168.1.100
<NODE_PORT>NodePort assigned by Kubernetes32192
<ISTIO_VERSION>Istio release version downloaded1.31.0

Environment Specifications#

ComponentDetails
KubernetesSingle-node cluster provisioned with kubeadm
CNICalico
Istio Version1.31.x
Install Methodistioctl (CLI binary)
Profiledefault

Note: Since this is a bare-metal single-node cluster without a cloud LoadBalancer (not GKE/EKS/AKS), services of type LoadBalancer created by the Istio IngressGateway will never receive an EXTERNAL-IP. This guide includes alternative access methods via port-forward and NodePort.


Prerequisites#

Before starting, make sure the following are in place:

  • A single-node Kubernetes cluster in Ready state (see Kubernetes Single-Node Setup on AlmaLinux 9)
  • kubectl configured and able to access the cluster (kubectl get nodes shows Ready)
  • Sufficient resources on the node to run the Istio control plane + 4 Bookinfo microservices (at least 2 CPU / 4 GB RAM free is recommended)
  • Internet access from the node (to download the Istio binary and pull container images)

Step 1: Download and Install istioctl#

1.1 Download the Istio Release#

Terminal window
curl -L https://istio.io/downloadIstio | sh -

Output:

Downloading istio-<ISTIO_VERSION> from https://github.com/istio/istio/releases/download/<ISTIO_VERSION>/istio-<ISTIO_VERSION>-linux-amd64.tar.gz ...
Istio <ISTIO_VERSION> download complete!
The Istio release archive has been downloaded to the istio-<ISTIO_VERSION> directory.
To configure the istioctl client tool for your workstation,
add the /home/user/istio-<ISTIO_VERSION>/bin directory to your environment path variable with:
export PATH="$PATH:/home/user/istio-<ISTIO_VERSION>/bin"
Begin the Istio pre-installation check by running:
istioctl x precheck

Navigate into the extracted directory:

Terminal window
cd istio-*/

1.2 Add istioctl to PATH#

Terminal window
export PATH=$PWD/bin:$PATH
# To make it permanent, append to your shell profile
echo 'export PATH=$HOME/istio-<ISTIO_VERSION>/bin:$PATH' >> ~/.bashrc

Tip: Replace <ISTIO_VERSION> with the actual version that was downloaded (e.g., 1.31.0).

1.3 Verify istioctl#

Terminal window
istioctl version

Output:

Istio is not present in the cluster: no running Istio pods in namespace "istio-system"
1.31.0

The first line is expected — Istio hasn’t been installed on the cluster yet. The important thing is that the istioctl client version is printed.


Step 2: Install Istio with the Default Profile#

Terminal window
istioctl x precheck

Output:

✔ No issues found when checking the cluster. Istio is safe to install or upgrade!
To get started, check out https://istio.io/latest/docs/setup/getting-started/.

Make sure there are no critical warnings or errors before proceeding.

2.2 Install the Default Profile#

Terminal window
istioctl install --set profile=default -y

This command installs:

  • istiod (the control plane) in the istio-system namespace
  • istio-ingressgateway as the default ingress gateway

Output:

✔ Istio core installed ⛵️
✔ Istiod installed 🧠
✔ Ingress gateways installed 🛬
✔ Installation complete

Step 3: Verify the Installation#

3.1 Check Pods in istio-system#

Terminal window
kubectl get pods -n istio-system

Output:

NAME READY STATUS RESTARTS AGE
istio-ingressgateway-6dcdbcd758-z6mtb 1/1 Running 0 2m10s
istiod-84568cdf7c-tv9mt 1/1 Running 0 2m24s

3.2 Check Control Plane Version#

Terminal window
istioctl version

Output:

client version: 1.31.0
control plane version: 1.31.0
data plane version: 1.31.0 (1 proxies)

Now istioctl can read both the client and control plane versions.

3.3 Analyze Configuration (optional)#

Terminal window
istioctl analyze -A

Use this to detect potential configuration issues across all namespaces before deploying any applications.


Step 4: Enable Automatic Sidecar Injection#

Istio automatically injects an Envoy sidecar proxy into every pod deployed in namespaces labeled with istio-injection=enabled.

Terminal window
kubectl label namespace default istio-injection=enabled

Verify the label:

Terminal window
kubectl get namespace -L istio-injection

Output:

NAME STATUS AGE ISTIO-INJECTION
default Active 4h29m enabled
istio-system Active 3m26s
kube-system Active 4h29m
...

Step 5: Deploy the Bookinfo Application#

Bookinfo is Istio’s official sample application, consisting of 4 microservices (productpage, details, reviews, ratings) with 3 different versions of the reviews service (v1, v2, v3) — perfect for demonstrating traffic routing capabilities.

5.1 Apply the Bookinfo Manifest#

Terminal window
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

Make sure you are still inside the extracted Istio directory (istio-<ISTIO_VERSION>/) so that the samples/bookinfo/... path resolves correctly.

5.2 Verify Services and Pods#

Terminal window
kubectl get services

Output:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
details ClusterIP 10.102.162.188 <none> 9080/TCP 7s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h30m
productpage ClusterIP 10.111.175.168 <none> 9080/TCP 7s
ratings ClusterIP 10.108.180.245 <none> 9080/TCP 7s
reviews ClusterIP 10.99.206.200 <none> 9080/TCP 7s
Terminal window
kubectl get pods

Make sure each pod shows 2/2 in the READY column — the 2 indicates that the Envoy sidecar was successfully injected alongside the application container.

Output:

NAME READY STATUS RESTARTS AGE
details-v1-65bd58d55f-svxqk 2/2 Running 0 7m32s
productpage-v1-7498b5bd44-4s76z 2/2 Running 0 7m32s
ratings-v1-9bff6cb79-7zpjv 2/2 Running 0 7m32s
reviews-v1-54465994f9-47h5d 2/2 Running 0 7m32s
reviews-v2-577579b4c5-6vvfl 2/2 Running 0 7m32s
reviews-v3-6d7944df59-tlr6r 2/2 Running 0 7m32s

If the READY column shows 1/1, the sidecar was not injected. Double-check the namespace label from Step 4, then restart the deployments with kubectl rollout restart deployment.

5.3 Test from Inside the Cluster#

Terminal window
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>"

Output:

<title>Simple Bookstore App</title>

5.4 Create the Istio Gateway#

Terminal window
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml

Output:

gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created

Verify:

Terminal window
kubectl get gateway

Output:

NAME AGE
bookinfo-gateway 24s

Step 6: Expose Bookinfo on a Single-Node Cluster#

This is where the setup diverges from the official Istio documentation, which assumes your cluster has a cloud LoadBalancer. On a bare-metal single-node cluster, istio-ingressgateway will remain stuck in Pending:

Terminal window
kubectl get svc istio-ingressgateway -n istio-system

Output:

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.100.98.219 <pending> 15021:32283/TCP,80:32192/TCP,443:32707/TCP 12m

Choose one of the options below to access Bookinfo from outside the cluster.

Option A — Port Forward (fastest for demos)#

Terminal window
kubectl port-forward -n istio-system svc/istio-ingressgateway 8080:80

Keep this terminal running, then access the app from your browser or via curl:

http://<NODE_IP>:8080/productpage

If accessing from the server itself, use http://localhost:8080/productpage. If accessing from another machine (e.g., your laptop), make sure port 8080 is open in the firewall and use the server’s IP address.

Option B — Patch to NodePort (more permanent)#

Terminal window
kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec": {"type": "NodePort"}}'

Check the NodePort assigned to port 80:

Terminal window
kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.ports[?(@.port==80)].nodePort}'

Then access via:

http://<NODE_IP>:<NODE_PORT>/productpage

Once accessible, you should see the Bookinfo product page:

Bookinfo Product Page


Step 7: Confirm the App is Working#

Use curl to verify the application is accessible (example using port-forward from Option A):

Terminal window
curl -s "http://localhost:8080/productpage" | grep -o "<title>.*</title>"

Output:

<title>Simple Bookstore App</title>

Open the page in your browser and refresh several times — you’ll notice the reviews section cycling through different versions in round-robin fashion: no stars, black stars, and red stars. This happens because no routing rules have been applied yet, so traffic is distributed equally across all three versions.

Bookinfo Reviews Round Robin — versions cycling without routing rules


Step 8: Define Service Versions with DestinationRules#

Before configuring traffic routing between reviews versions (e.g., request routing or traffic shifting), Istio needs to know which subsets/versions are available. This is done through DestinationRule resources.

Terminal window
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

Output:

destinationrule.networking.istio.io/productpage created
destinationrule.networking.istio.io/reviews created
destinationrule.networking.istio.io/ratings created
destinationrule.networking.istio.io/details created

Verify:

Terminal window
kubectl get destinationrules -o yaml

The default profile already enables auto mutual TLS, so you don’t need to set up mTLS manually for this demo.


Step 9: Kiali Dashboard — Visualizing Your Service Mesh#

Kiali is the official observability console for Istio. It provides a real-time visual representation of your service mesh topology, traffic flow, health status, and configuration validation. Istio ships Kiali as part of a bundle of observability addons alongside Prometheus (metrics), Grafana (dashboards), and Jaeger (distributed tracing).

9.1 Install the Observability Addons#

Install all addons included in the Istio samples directory:

Terminal window
kubectl apply -f samples/addons

Output:

serviceaccount/grafana created
configmap/grafana created
service/grafana created
deployment.apps/grafana created
configmap/istio-grafana-dashboards created
configmap/istio-services-grafana-dashboards created
deployment.apps/jaeger created
service/tracing created
serviceaccount/kiali created
configmap/kiali created
clusterrole.rbac.authorization.k8s.io/kiali created
clusterrolebinding.rbac.authorization.k8s.io/kiali created
service/kiali created
deployment.apps/kiali created
serviceaccount/prometheus created
configmap/prometheus created
service/prometheus created
deployment.apps/prometheus created
...

9.2 Wait for Kiali to Be Ready#

Terminal window
kubectl rollout status deployment/kiali -n istio-system

Output:

Waiting for deployment "kiali" rollout to finish: 0 of 1 updated replicas are available...
deployment "kiali" successfully rolled out

9.3 Access the Kiali Dashboard#

To access the dashboard, patch the Kiali service to NodePort — the same approach used to expose the IngressGateway:

Terminal window
kubectl patch svc kiali -n istio-system -p '{"spec": {"type": "NodePort"}}'

Check the NodePort assigned to port 20001:

Terminal window
kubectl get svc kiali -n istio-system -o jsonpath='{.spec.ports[?(@.port==20001)].nodePort}'

Then open the dashboard in your browser at http://<NODE_IP>:<NODE_PORT>:

Kiali Dashboard Overview

Navigate to the Graph section — Kiali displays a live visualization of your service mesh, showing the relationships between services in the Bookinfo application. It also provides filters to help you analyze traffic flow across the mesh.

Kiali Graph — service mesh topology and traffic flow


Step 10: Test Traffic Routing with VirtualServices#

Since the DestinationRules (with subsets v1/v2/v3) were applied in the previous step, you can now create a VirtualService to route 100% of traffic to a specific version. Let’s route all reviews traffic to v2 (black stars):

Terminal window
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v2
EOF

Verify the routing rule:

Terminal window
kubectl get virtualservice reviews -o yaml

Refresh /productpage several times — now the reviews section will always show black stars (v2 fetches rating data but displays stars in black, unlike v3 which shows red stars). No more round-robin cycling between versions.

Bookinfo pinned to Reviews v2 — consistent black stars

You can also confirm this in the Kiali graph — traffic to the reviews service now flows exclusively to the v2 subset, with no traffic going to v1 or v3:

Kiali verification — all traffic routed to Reviews v2


Cleanup#

Once you’re done exploring, clean up all the resources:

Terminal window
# Remove Bookinfo and its gateway
samples/bookinfo/platform/kube/cleanup.sh
# (Optional) Revert services back to ClusterIP if you patched them to NodePort
kubectl patch svc istio-ingressgateway -n istio-system -p '{"spec": {"type": "ClusterIP"}}'
# Uninstall Istio from the cluster
istioctl uninstall --purge -y
# Delete the istio-system namespace
kubectl delete namespace istio-system

Troubleshooting#

SymptomCauseResolution
Sidecar not injected (pods show 1/1 instead of 2/2)Namespace missing istio-injection labelRun kubectl get namespace default --show-labels and re-apply the label, then kubectl rollout restart deployment -n default
istio-ingressgateway stuck in <pending>No cloud LoadBalancer on bare-metalUse Option A (port-forward) or Option B (NodePort) from Step 6
productpage returns 503 / upstream connect errorSidecar not fully ready or DestinationRules not appliedCheck kubectl get pods -n default and inspect logs with kubectl logs <pod> -c istio-proxy
istiod in CrashLoopBackOffInsufficient CPU/RAM on the nodeCheck kubectl describe pod -n istio-system -l app=istiod and kubectl top nodes

References#

Getting Started with Istio: Installing the Service Mesh & Running the Bookinfo Demo on Kubernetes
https://im-gatan.com/posts/istio-service-mesh-kubernetes-setup/
Author
Gatan
Published at
2026-09-14
License
CC BY-NC-SA 4.0