{"id":279,"date":"2025-11-17T11:44:35","date_gmt":"2025-11-17T11:44:35","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=279"},"modified":"2025-11-17T11:44:35","modified_gmt":"2025-11-17T11:44:35","slug":"setup-istio-on-kubernetes","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=279","title":{"rendered":"How to Set up Istio on Kubernetes Cluster? [Step-by-Step]"},"content":{"rendered":"<p>In this blog, you will learn how to install and configure Istio on Kubernetes using Helm.<\/p>\n<p>By the end, you will understand,<\/p>\n<ol>\n<li>Different Istio installation modes<\/li>\n<li>How to install Istio custom resources and the sidecar proxy (<a href=\"https:\/\/devopscube.com\/kubernetes-daemonset\/\" rel=\"noreferrer\">daemon<\/a>)<\/li>\n<li>How to validate the setup with a demo application &amp; canary release strategy.<\/li>\n<li>Key concepts like <code>DestinationRule<\/code> and <code>VirtualService<\/code> and more..<\/li>\n<\/ol>\n<p>Let&#8217;s get started!<\/p>\n<h2 id=\"what-is-istio\">What is Istio?<\/h2>\n<p><a href=\"https:\/\/istio.io\/?ref=devopscube.com\" rel=\"noreferrer\">Istio<\/a> is a popular <a href=\"https:\/\/devopscube.com\/service-mesh-tools\/\" rel=\"noreferrer\">Service Mesh<\/a>, an infrastructure layer that manages communication between microservices in a Kubernetes cluster. It is used in production by companies like Airbnb, Intuit, eBay, Salesforce, etc.<\/p>\n<p>Everyone starting with Istio will have the following question.<\/p>\n<p><em>Why do we need Istio when Kubernetes offers many microservices functionalities?<\/em><\/p>\n<p>Well, when you have dozens or hundreds of microservices talking to each other, you face <strong>challenges that Kubernetes cant handle. <\/strong><\/p>\n<p>Here are some of those features Kubernetes doesnt offer natively.<\/p>\n<ol>\n<li>Load balancing between service versions.<\/li>\n<li>Implementing Circuit breaking and retry patterns.<\/li>\n<li>Enabling automatic Mutual <a href=\"https:\/\/devopscube.com\/configure-ingress-tls-kubernetes\/\" rel=\"noreferrer\">TLS<\/a> (mTLS) between all services.<\/li>\n<li>Automatic metrics collection for all service-to-service calls<\/li>\n<li>Distributed tracing to follow requests across services and more..<\/li>\n<\/ol>\n<p>Without ISTIO, you will have to build these features into every microservice yourself. But Istio provides them at the infrastructure level using <strong>sidecar proxies<\/strong> (Envoy) that intercept all network traffic without changing your application code.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udccc<\/div>\n<div class=\"kg-callout-text\">Explaining all Istio concepts is beyond the scope of this guide. Here, we will focus on setting up Istio on kubernetes, getting it up and running, and validating the setup using sample applications. You can read the <a href=\"https:\/\/devopscube.com\/istio-architecture\/\" rel=\"noreferrer\">Istio architecture<\/a> blog to dive deep in to Istio internals.<\/div>\n<\/div>\n<p>Now that you have a high level idea of Istio service mesh, lets get started with the setup.<\/p>\n<h2 id=\"setup-prerequisites\">Setup Prerequisites<\/h2>\n<p>To set up Istio, you need the following.<\/p>\n<ol>\n<li>A <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\" rel=\"noreferrer\">kubernetes cluster <\/a><\/li>\n<li><a href=\"https:\/\/devopscube.com\/kubectl-set-context\/\" rel=\"noreferrer\">Kubectl<\/a> [Local workstation]<\/li>\n<li><a href=\"https:\/\/devopscube.com\/create-helm-chart\/\" rel=\"noreferrer\">Helm<\/a> [Local workstation]<\/li>\n<\/ol>\n<p>Lets get started with the setup.<\/p>\n<h2 id=\"kubernetes-node-requirements\">Kubernetes Node Requirements<\/h2>\n<p>To have a basic Istio setup up and running, you need to have the following minimum CPU and memory resources on your worker node.<\/p>\n<ul>\n<li>4 vCPU<\/li>\n<li>16 GB RAM<\/li>\n<\/ul>\n<p>It will be enough for Istio control plane + sidecars + a few sample apps to get started.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\">If you enable <b><strong style=\"white-space: pre-wrap;\">telemetry \/ logging \/ tracing<\/strong><\/b> (Prometheus, Grafana, Jaeger\/Kiali), memory\/CPU overhead will increase.<\/div>\n<\/div>\n<h2 id=\"istio-sidecar-vs-ambient-mode\">Istio Sidecar Vs Ambient Mode<\/h2>\n<p>Before you start setting up Istio, you should know the following two Istio deployment modes.<\/p>\n<h3 id=\"1-sidecar-mode\">1. Sidecar Mode<\/h3>\n<p>In this mode, Istio Deploys an Envoy proxy as a sidecar container alongside each application pod. All the traffic flows through this sidecar proxy (data plane component) and <strong>provides all the L4-L7 features<\/strong> directly within the pod for all inbound and outbound connections.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-90.png\" class=\"kg-image\" alt=\"Istio Sidecar Mode\" loading=\"lazy\" width=\"2000\" height=\"1417\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/11\/image-90.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/11\/image-90.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/11\/image-90.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-90.png 2092w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>It also means, each pod requires additional CPU and memory (typically 100-500MB RAM) for the sidecar container.<\/p>\n<h3 id=\"2-ambient-mode\">2. Ambient Mode<\/h3>\n<p>In this mode, no sidecars are injected into application pods.<\/p>\n<p>Istio deploys a node-level proxy called <a href=\"https:\/\/github.com\/istio\/ztunnel?ref=devopscube.com\" rel=\"noreferrer\">ztunnel<\/a> as a <a href=\"https:\/\/devopscube.com\/kubernetes-daemonset\/\" rel=\"noreferrer\">DaemonSet<\/a> for L4 functionality (mTLS, authN\/authZ at transport layer). This means, all pods on a node share the same ztunnel instance instead of their own sidecar proxies.<\/p>\n<p>If you want L7 features (HTTP, gRPC, etc)  you can run an optional namespace-scoped waypoint proxies.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-91.png\" class=\"kg-image\" alt=\"Istio Ambient Mode ztunnel and Waypoint proxy\" loading=\"lazy\" width=\"1962\" height=\"2050\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/11\/image-91.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/11\/image-91.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/11\/image-91.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-91.png 1962w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>To put is simple, ambient mode is more \u201cevolved\u201d in concept but it is still newer. Meaning there are more trade-offs and some gaps<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\">In this guide we are <b><strong style=\"white-space: pre-wrap;\">focussing on the Istio Sidecar Mode<\/strong><\/b> mode because ambient mode still has a few limitations and relative immaturity compared to the sidecar model.<\/p>\n<p>If you want to learn more about Ambient Mode, please refer to the <a href=\"https:\/\/devopscube.com\/setup-istio-ambient-mode\/\" rel=\"noreferrer\"><b><strong style=\"white-space: pre-wrap;\">Set Up Istio in Ambient Mode<\/strong><\/b><\/a> blog.<\/div>\n<\/div>\n<h2 id=\"install-istio-using-helm\">Install Istio Using Helm<\/h2>\n<p>Helm is the common method followed in organizations to istall and mangage Istio. We will be using Helm to install Istio on the <a href=\"https:\/\/devopscube.com\/production-ready-kubernetes-cluster\/\" rel=\"noreferrer\">Kubernetes cluster<\/a>. <\/p>\n<p>Follow the steps given below for the installation.<\/p>\n<h3 id=\"step-1-add-helm-chart-repo\">Step 1: Add Helm Chart Repo<\/h3>\n<p>First, we need to add and update the official Istio Helm repo on our local machine.<\/p>\n<pre><code class=\"language-bash\">helm repo add istio https:\/\/istio-release.storage.googleapis.com\/charts\nhelm repo update<\/code><\/pre>\n<p>Now, we have added the Istio repo. So we can list the available charts of this repo.<\/p>\n<pre><code class=\"language-bash\">helm search repo istio<\/code><\/pre>\n<p>In the output, we can see all the Istio related chart lists and their latest versions. We will not be using all the charts. <\/p>\n<pre><code class=\"language-bash\">$ helm search repo istio\n\nNAME                    CHART VERSION   APP VERSION     DESCRIPTION                                       \nistio\/istiod            1.28.0          1.28.0          Helm chart for istio control plane                \nistio\/istiod-remote     1.23.6          1.23.6          Helm chart for a remote cluster using an extern...\nistio\/ambient           1.28.0          1.28.0          Helm umbrella chart for ambient                   \nistio\/base              1.28.0          1.28.0          Helm chart for deploying Istio cluster resource...\nistio\/cni               1.28.0          1.28.0          Helm chart for istio-cni components               \nistio\/gateway           1.28.0          1.28.0          Helm chart for deploying Istio gateways           \nistio\/ztunnel           1.28.0          1.28.0          Helm chart for istio ztunnel components  <\/code><\/pre>\n<p>We will be using only those are required for sidecar mode implementation.<\/p>\n<h3 id=\"step-2-install-istio-crds\">Step 2: Install Istio CRD&#8217;s<\/h3>\n<p>Istio adds many custom features to Kubernetes like traffic routing, mTLS, gateways, sidecar rules, policies, and more.<\/p>\n<p>These Istio features do not exist in Kubernetes by default. To support them, Istio defines them as Custom Resource Definitions (CRDs)<\/p>\n<p>We can install the CRD&#8217;s from the Istio chart list. We need to choose the <code>istio\/base<\/code> chart to setup Istio in sidecar mode.<\/p>\n<p>Execute the following command to install istio-base.<\/p>\n<pre><code class=\"language-bash\">helm install istio-base istio\/base -n istio-system --set defaultRevision=default --create-namespace<\/code><\/pre>\n<p>You should get a successful message as follows.<\/p>\n<pre><code class=\"language-bash\">NAME: istio-base\nLAST DEPLOYED: Sat Nov 15 14:58:26 2025\nNAMESPACE: istio-system\nSTATUS: deployed\nREVISION: 1\nTEST SUITE: None\nNOTES:\nIstio base successfully installed!<\/code><\/pre>\n<p>Once the CRD deployment is completed, the Kubernetes API server supports Istio resource types.<\/p>\n<p> We can list the Istio CRDs using the following command. <\/p>\n<pre><code class=\"language-bash\">kubectl get crds | grep istio<\/code><\/pre>\n<p>You will get the following output. It contains CRD&#8217;s realted to security, traffic management, telemetry, extensions and workloads.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get crds | grep istio\n\nauthorizationpolicies.security.istio.io             2025-11-15T06:20:10Z\ndestinationrules.networking.istio.io                2025-11-15T06:20:10Z\nenvoyfilters.networking.istio.io                    2025-11-15T06:20:10Z\ngateways.networking.istio.io                        2025-11-15T06:20:10Z\npeerauthentications.security.istio.io               2025-11-15T06:20:10Z\nproxyconfigs.networking.istio.io                    2025-11-15T06:20:10Z\nrequestauthentications.security.istio.io            2025-11-15T06:20:10Z\nserviceentries.networking.istio.io                  2025-11-15T06:20:10Z\nsidecars.networking.istio.io                        2025-11-15T06:20:10Z\ntelemetries.telemetry.istio.io                      2025-11-15T06:20:10Z\nvirtualservices.networking.istio.io                 2025-11-15T06:20:10Z\nwasmplugins.extensions.istio.io                     2025-11-15T06:20:10Z\nworkloadentries.networking.istio.io                 2025-11-15T06:20:10Z\nworkloadgroups.networking.istio.io                  2025-11-15T06:20:10Z<\/code><\/pre>\n<h3 id=\"customizing-istio-chart-values-optional\">Customizing Istio Chart &amp; Values (Optional)<\/h3>\n<p>In <strong>enterprise environments<\/strong>, you cannot install Istio directly from the public Helm repo.<\/p>\n<p>You will have to host the helm chart in internal helm repos (e.g., Nexus, Artifactory, Harbor, S3 bucket, Git repo).  Also you will customization to the default values to meet the project requirements.<\/p>\n<p>In that case, you can download the chart locally or store it on your own repo, use the following command.<\/p>\n<pre><code class=\"language-bash\">helm pull istio\/base --version 1.28.0 --untar<\/code><\/pre>\n<p>Once you pull the chart, the directory structure looks like this.<\/p>\n<pre><code class=\"language-bash\">base\n  |-- Chart.yaml\n  |-- README.md\n  |-- files\n  |   |-- crd-all.gen.yaml\n  |   |-- profile-ambient.yaml\n  |   |-- ...\n  |   |-- ...\n  |   |-- ...\n  |   |-- profile-remote.yaml\n  |   `-- profile-stable.yaml\n  |-- templates\n  |   |-- NOTES.txt\n  |   |-- crds.yaml\n  |   |-- defaultrevision-validatingadmissionpolicy.yaml\n  |   |-- defaultrevision-validatingwebhookconfiguration.yaml\n  |   |-- reader-serviceaccount.yaml\n  |   `-- zzz_profile.yaml\n  `-- values.yaml<\/code><\/pre>\n<p>Here, you can see the <code>values.yaml<\/code> file, which has the modifiable parameters.<\/p>\n<p>So if you want to do a custom installation, you can modify this file or create a new values file with only the required parameters.<\/p>\n<p>Then run the following command to install the CRDs using the downloaded chart.<\/p>\n<pre><code class=\"language-bash\">helm install istio-base .\/base -n istio-system --create-namespace<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">To use the custom values file, during the Helm installation command, use the <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">-f<\/code> field with the path of the custom file.<\/div>\n<\/div>\n<h3 id=\"step-3-install-istio-daemon-istiod\">Step 3: Install Istio Daemon (Istiod)<\/h3>\n<p>Istiod is the <strong>Istio control plane<\/strong> that manages everything related to Istio, like managing configuration,mTLS, service discovery, certification management etc.<\/p>\n<p>Now, install the Istiod using the <a href=\"https:\/\/devopscube.com\/create-helm-chart\/\" rel=\"noreferrer\">Helm chart<\/a>. For that, we need to select the <code>istio\/istiod<\/code> chart from the list.<\/p>\n<pre><code class=\"language-bash\">helm install istiod istio\/istiod -n istio-system --wait<\/code><\/pre>\n<p>Wait a few seconds to complete the installation.<\/p>\n<p>This will install the Istiod control plane as a deployment and other required objects like services, configmaps, secrets, etc.<\/p>\n<h3 id=\"customizing-istio-daemon-chart-values-optional\">Customizing Istio Daemon Chart &amp; Values (Optional)<\/h3>\n<p>For a custom installation, pull the charts to local, create a custom values file, or modify the default <code>values.yaml<\/code> file, then install the charts downloaded locally.<\/p>\n<p>Use the following command to download the chart locally.<\/p>\n<pre><code class=\"language-bash\">helm pull istio\/istiod --version 1.28.0 --untar<\/code><\/pre>\n<p>The following is the structure of the chart.<\/p>\n<pre><code class=\"language-bash\">istiod\n  |-- Chart.yaml\n  |-- README.md\n  |-- files\n  |   |-- gateway-injection-template.yaml\n  |   |-- grpc-agent.yaml\n  |   |-- grpc-simple.yaml\n  |   |-- ...\n  |   |-- ...\n  |   |-- ...\n  |   |-- profile-preview.yaml\n  |   |-- profile-remote.yaml\n  |   |-- profile-stable.yaml\n  |   `-- waypoint.yaml\n  |-- templates\n  |   |-- NOTES.txt\n  |   |-- _helpers.tpl\n  |   |-- autoscale.yaml\n  |   |-- ...\n  |   |-- ...\n  |   |-- ...\n  |   |-- validatingwebhookconfiguration.yaml\n  |   |-- zzy_descope_legacy.yaml\n  |   `-- zzz_profile.yaml\n  `-- values.yaml<\/code><\/pre>\n<p>This is how the Istiod Helm chart is structured, and the following are the images used on this chart.  <\/p>\n<ul>\n<li><code>busybox:1.28<\/code><\/li>\n<li><code>docker.io\/istio\/pilot:1.28.0<\/code><\/li>\n<\/ul>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">In <b><strong style=\"white-space: pre-wrap;\">project envionments<\/strong><\/b>, you will have to <b><strong style=\"white-space: pre-wrap;\">host these images in internal private registries<\/strong><\/b> and use that image urls in the helm chart.<\/div>\n<\/div>\n<p>Then run the following command to install Istiod using the downloaded chart.<\/p>\n<pre><code>helm install istiod .\/istiod -n istio-system --wait\n<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">By default, Istiod is deployed with Horizontal Pod Autoscaler, so when the Pod&#8217;s CPU threshold reaches 80%, the replication will be increased.<br \/>The default max replication is 5.<\/div>\n<\/div>\n<h2 id=\"validating-istio-installation\">Validating Istio Installation<\/h2>\n<p>Run the following command to list the pod, deployment, and service created by the Helm chart.<\/p>\n<pre><code class=\"language-bash\">kubectl -n istio-system get po,deploy,svc<\/code><\/pre>\n<p>You will see the following objects.<\/p>\n<pre><code class=\"language-bash\">$ kubectl -n istio-system get po,deploy,svc\n\nNAME                         READY   STATUS    RESTARTS   AGE\n\npod\/istiod-fd9bbfdf8-wd5sw   1\/1     Running   0          5m20s\n\nNAME                     READY   UP-TO-DATE   AVAILABLE   AGE\ndeployment.apps\/istiod   1\/1     1            0           5m20s\n\nNAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                                 AGE\nservice\/istiod  ClusterIP  10.102.128.197  &lt;none&gt;       15010\/TCP,15012\/TCP,443\/TCP,15014\/TCP   5m20s<\/code><\/pre>\n<p>The output ensures that the installation is successful without any issues.<\/p>\n<h2 id=\"install-istioctl-local-machine\">Install Istioctl [Local Machine]<\/h2>\n<p><strong><code>Istioctl<\/code><\/strong> is a command-line tool to access and manage the Istio Daemon from the local machine.<\/p>\n<p>Use the following command to install Istioctl.<\/p>\n<p>For Linux:<\/p>\n<pre><code class=\"language-bash\">curl -sL https:\/\/istio.io\/downloadIstioctl | sh -\nexport PATH=$HOME\/.istioctl\/bin:$PATH\necho 'export PATH=$HOME\/.istioctl\/bin:$PATH' &gt;&gt; ~\/.bashrc\nsource ~\/.bashrc<\/code><\/pre>\n<p>For MAC:<\/p>\n<pre><code class=\"language-bash\">brew install istioctl<\/code><\/pre>\n<p>For other installation options, you can use this <a href=\"https:\/\/istio.io\/latest\/docs\/ops\/diagnostic-tools\/istioctl\/?ref=devopscube.com\" rel=\"noreferrer\">official documentation<\/a> for the installation.<\/p>\n<p>Verify the installtion using the following command. It will shows the Istio clinent and control plane version.<\/p>\n<pre><code class=\"language-bash\">$ istioctl version  \n\nclient version: 1.28.0\ncontrol plane version: 1.28.0\ndata plane version: none<\/code><\/pre>\n<p>Now, we need to test Istio with applications.<\/p>\n<h2 id=\"quick-istio-demo-deploy-apps-inject-sidecars-and-split-traffic\">Quick Istio Demo: Deploy Apps, Inject Sidecars, and Split Traffic<\/h2>\n<p>Now that we have completed the installation, let&#8217;s validate the setup using a sample deployment that uses Istio features.<\/p>\n<p>Here is what we are going to do.<\/p>\n<ul>\n<li>Create a namespace and label it for automatic Istio sidecar injection.<\/li>\n<li>Then deploy two sample applications versions (V1 &amp; V2) in the istio enabled namespace.<\/li>\n<li>Configure Destination rule &amp; Virtual Service to split traffic between two versions of app using weighted routing.<\/li>\n<li>Then test the setup by creating a client pod and sends requests to the application service endpoint.<\/li>\n<\/ul>\n<p>The following image shows what we are going to build in this test setup.<\/p>\n<figure class=\"kg-card kg-image-card kg-card-hascaption\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-89.png\" class=\"kg-image\" alt=\"\" loading=\"lazy\" width=\"1943\" height=\"2047\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/11\/image-89.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/11\/image-89.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/11\/image-89.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-89.png 1943w\" sizes=\"auto, (min-width: 720px) 720px\"><figcaption><span style=\"white-space: pre-wrap;\">Application deployment on Istio mesh to perform canary routing<\/span><\/figcaption><\/figure>\n<p>Let&#8217;s get started with the setup.<\/p>\n<h3 id=\"create-a-namespace-label\">Create a Namespace &amp; Label<\/h3>\n<p>For testing, we use a dedicated namespace<\/p>\n<pre><code class=\"language-bash\">kubectl create ns istio-test<\/code><\/pre>\n<p>Now we need to add the istio specific lable to enable automatic Istio sidecar injection.<\/p>\n<pre><code class=\"language-bash\">kubectl label namespace istio-test istio-injection=enabled<\/code><\/pre>\n<p>When you set <code>istio-injection=enabled<\/code> on a namespace, Istio&#8217;s <strong><code>MutatingAdmissionWebhook<\/code><\/strong> automatically injects an Envoy proxy sidecar container into any new pods deployed in that namespace.<\/p>\n<p>Verify the label.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get namespace istio-test --show-labels\n\nNAME         STATUS   AGE    LABELS\nistio-test   Active   4m2s   istio-injection=enabled,kubernetes.io\/metadata.name=istio-test<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\u26a0\ufe0f<\/div>\n<div class=\"kg-callout-text\">If you add the <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">istio-injection=enabled<\/code> label to an existing namespace with pods, you will need to restart the pods for the sidecar injection to happen.<\/div>\n<\/div>\n<h3 id=\"deploy-two-sample-apps\">Deploy Two Sample Apps<\/h3>\n<p>For our validation, we are deploying two demo <strong>http echo<\/strong> applications and then test the traffic routing.<\/p>\n<p>You can directly copy and paste the following manifests on your terminal to deploy the apps.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt; EOF &gt; demo-deploy.yaml\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: backend-v1\n  namespace: istio-test\nspec:\n  replicas: 3\n  selector:\n    matchLabels: { app: backend, version: v1 }\n  template:\n    metadata:\n      labels: { app: backend, version: v1 }\n    spec:\n      containers:\n      - name: echo\n        image: hashicorp\/http-echo\n        args: [\"-text=hello from backend v1\"]\n        ports:\n        - containerPort: 5678\n---\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: backend-v2\n  namespace: istio-test\nspec:\n  replicas: 2\n  selector:\n    matchLabels: { app: backend, version: v2 }\n  template:\n    metadata:\n      labels: { app: backend, version: v2 }\n    spec:\n      containers:\n      - name: echo\n        image: hashicorp\/http-echo\n        args: [\"-text=hello from backend v2\"]\n        ports:\n        - containerPort: 5678\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: backend\n  namespace: istio-test\n  labels:\n    app: backend\n    service: backend\nspec:\n  ports:\n  - name: http\n    port: 80\n    targetPort: 5678\n  selector:\n    app: backend\nEOF<\/code><\/pre>\n<p>Once you deploy the pod in the labelled namespace, <strong>Istio will inject the proxy sidecar<\/strong> on each pod.<\/p>\n<p>Run the following command to check the status of the sample applications and backend <strong><code>svc<\/code><\/strong>.<\/p>\n<pre><code class=\"language-bash\">kubectl -n istio-test get po,svc<\/code><\/pre>\n<p>You will get the following output. <\/p>\n<pre><code class=\"language-bash\">$ kubectl -n istio-test get po,svc\n\nNAME                              READY   STATUS    RESTARTS   AGE\npod\/backend-v1-7c88547fc6-6krk8   2\/2     Running   0          60s\npod\/backend-v1-7c88547fc6-lvntb   2\/2     Running   0          60s\npod\/backend-v1-7c88547fc6-x49kr   2\/2     Running   0          60s\npod\/backend-v2-86c767bf6b-blf56   2\/2     Running   0          59s\npod\/backend-v2-86c767bf6b-zwzp7   2\/2     Running   0          59s\n\nNAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE\nservice\/backend   ClusterIP   10.100.134.45   &lt;none&gt;        80\/TCP    55s<\/code><\/pre>\n<p>If you notice, the output says 2\/2 ready. It is the application pod + the sidecar proxy.<\/p>\n<p>To verify this, describe any of the pods from the namespace, and you will see the sidecar container.<\/p>\n<pre><code class=\"language-bash\">kubectl -n istio-test describe po &lt;pod-name&gt;<\/code><\/pre>\n<p>You can see the main container and Istios container added as a sidecar as shown below.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-88.png\" class=\"kg-image\" alt=\"the described output to see the istio injected sidecar container\" loading=\"lazy\" width=\"2000\" height=\"1725\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/11\/image-88.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/11\/image-88.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/11\/image-88.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/11\/image-88.png 2180w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Also, you can use <code>istioctl<\/code> to check the status of the proxies using the following command.<\/p>\n<pre><code class=\"language-bash\">istioctl -n istio-test proxy-status<\/code><\/pre>\n<p>And, you will get the following output.<\/p>\n<pre><code class=\"language-bash\">$ istioctl -n istio-test proxy-status\n\nNAME                                       CLUSTER        ISTIOD                     VERSION     SUBSCRIBED TYPES\nbackend-v1-7c88547fc6-6krk8.istio-test     Kubernetes     istiod-fd9bbfdf8-dzcsp     1.28.0      4 (CDS,LDS,EDS,RDS)\nbackend-v1-7c88547fc6-lvntb.istio-test     Kubernetes     istiod-fd9bbfdf8-dzcsp     1.28.0      4 (CDS,LDS,EDS,RDS)\nbackend-v1-7c88547fc6-x49kr.istio-test     Kubernetes     istiod-fd9bbfdf8-dzcsp     1.28.0      4 (CDS,LDS,EDS,RDS)\nbackend-v2-86c767bf6b-blf56.istio-test     Kubernetes     istiod-fd9bbfdf8-dzcsp     1.28.0      4 (CDS,LDS,EDS,RDS)\nbackend-v2-86c767bf6b-zwzp7.istio-test     Kubernetes     istiod-fd9bbfdf8-dzcsp     1.28.0      4 (CDS,LDS,EDS,RDS)<\/code><\/pre>\n<p>In the output, we can see two Envoy proxies, which are connected with the Istio daemon (Control Plane).<\/p>\n<p>Each side-car Envoy proxy is subscribed to specific discovery feeds via <strong>gRPC or streaming connection<\/strong> to the Istio control plane to decide how the traffic should be routed.<\/p>\n<ol>\n<li><strong>LDS (Listener Discovery Service)<\/strong> &#8211; This tells the Envoy to which IP and Ports to listen, and what to do when the traffic arrives.<\/li>\n<li><strong>RDS (Route Discovery Service) <\/strong>&#8211; After traffic is received, it tells Envoy where it should be sent.<\/li>\n<li><strong>CDS (Cluster Discovery Service)<\/strong> &#8211; Defines the services on other clusters and how to connect them.<\/li>\n<li><strong>EDS (Endpoint Discovery Service) <\/strong>&#8211; List the Pod IPs and Ports inside each cluster so Envoy can reach the correct destination.<\/li>\n<\/ol>\n<p>This means <strong>at runtime, Envoy is receiving updates<\/strong> for what listeners to use, what routes to send traffic on.<\/p>\n<p>Now, our demo deployments are ready, so we can now create the Destination Rule.<\/p>\n<h3 id=\"create-a-destination-rule\">Create a Destination Rule<\/h3>\n<p>As the name indicates, destination rule basically defines the rules on how the traffic should be handled at the destination (pod).<\/p>\n<p>When we say rules, it means, <\/p>\n<ul>\n<li>Load balancing method <\/li>\n<li>Connection limits <\/li>\n<li>Circuit breaking <\/li>\n<li>TLS settings <\/li>\n<li>Retry policies and more..<\/li>\n<\/ul>\n<p>For example, if you want to enable mTLS, or LEAST_REQUEST load balancing for the destination pod, you define all those in the <strong><code>DestinationRule<\/code><\/strong> CRD.<\/p>\n<p>All the <strong><code>DestinationRule<\/code><\/strong> <strong>configurations get translated into Envoy proxy<\/strong> (pod sidecar) configuration.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">(istiod) pushes <b><code spellcheck=\"false\" style=\"white-space: pre-wrap;\"><strong>DestinationRule<\/strong><\/code><\/b> <b><strong style=\"white-space: pre-wrap;\">EVERY proxy in the mesh<\/strong><\/b>, regardless of whether they&#8217;ll ever call that service or not.<\/div>\n<\/div>\n<p>In our example, we are going to <strong>perform a traffic split<\/strong> between two versions of applications using <strong>weighted routing<\/strong>. It is also useful for canary routing.<\/p>\n<p>For that we need to create a <strong><code>DestinationRule<\/code><\/strong>that creates two subsets.<\/p>\n<ul>\n<li><strong>Subset v1:<\/strong> To group all pods with label <strong><code>version: v1<\/code><\/strong> <\/li>\n<li><strong>Subset v2:<\/strong> Groups all pods with label <strong><code>version: v2<\/code><\/strong><\/li>\n<\/ul>\n<p>When traffic reaches the dataplane proxy, it routes between the label-based subsets of one service.<\/p>\n<p>The following is the sample Destination rule configuration that creates two subsets.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt;EOF &gt; destination-rule.yaml\napiVersion: networking.istio.io\/v1alpha3\nkind: DestinationRule\nmetadata:\n  name: backend\n  namespace: istio-test\nspec:\n  host: backend.istio-test.svc.cluster.local\n  subsets:\n  - name: v1\n    labels:\n      version: v1\n  - name: v2\n    labels:\n      version: v2\nEOF<\/code><\/pre>\n<p>To apply this, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f destination-rule.yaml<\/code><\/pre>\n<p>In the above config, we have used<\/p>\n<ul>\n<li><strong><code>spec.host: backend.istio-test.svc.cluster.local<\/code><\/strong> &#8211; The domain name of the backend service that we created in the previous step. It defines that the traffic should route to this backend service.<\/li>\n<li><strong><code>subsets<\/code><\/strong> &#8211; Defines the backend application deployments based on labels. We have two deployments under one service so created two subsets.<\/li>\n<\/ul>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">Istio tells all Envoy proxies, &#8220;<i><em class=\"italic\" style=\"white-space: pre-wrap;\">When you see traffic going to<\/em><\/i> <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">backend.istio-test.svc.cluster.local<\/code>, <i><em class=\"italic\" style=\"white-space: pre-wrap;\">apply these rules (subsets v1 and v2)<\/em><\/i>.<\/div>\n<\/div>\n<p>To list the Destination Rule Custom Resources, use the following command.<\/p>\n<pre><code>kubectl -n istio-test get destinationrules<\/code><\/pre>\n<p>You will get the following output.<\/p>\n<pre><code class=\"language-bash\">$ kubectl -n istio-test get destinationrules\n\nNAME      HOST                                   AGE\n\nbackend   backend.istio-test.svc.cluster.local   6s<\/code><\/pre>\n<p>Now, we need to create a Virtual service.<\/p>\n<h3 id=\"create-a-virtual-service\">Create a Virtual Service<\/h3>\n<p><strong>Virtual Service<\/strong> is a Custom Resource of the Istio, where we define where to route the traffic based on conditions such as host, path, weight, canary, etc.<\/p>\n<p>So why do we need <strong><code>VirtualServic<\/code>e<\/strong> when we already have <strong><code>DestinationRule<\/code><\/strong>?<\/p>\n<p>Well, they serve completely different purposes in traffic management.<\/p>\n<p>Think of it this way.<\/p>\n<ul>\n<li><strong>VirtualService<\/strong> = WHAT traffic goes WHERE (Eg, path\/host-matching, canary\/weight, splits, header-based routing etc.)<\/li>\n<li><strong>DestinationRule<\/strong> = HOW to handle traffic at the destination (connection policies)<\/li>\n<\/ul>\n<p>Lets create a Virtual Service that does <strong>weighted routing (canary style)<\/strong> to the two subsets we deigned in the Destination rule.<\/p>\n<p>To create a Virtual Service, use the following manifet.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt;EOF &gt; virtual-service.yaml\napiVersion: networking.istio.io\/v1beta1\nkind: VirtualService\nmetadata:\n  name: backend\n  namespace: istio-test\nspec:\n  hosts:\n  - backend.istio-test.svc.cluster.local\n  http:\n  - route:\n    - destination:\n        host: backend.istio-test.svc.cluster.local\n        subset: v1\n      weight: 50\n    - destination:\n        host: backend.istio-test.svc.cluster.local\n        subset: v2\n      weight: 50\nEOF<\/code><\/pre>\n<p>Here, we have defined the hostname of the service to route the traffic.<\/p>\n<p><strong><code>spec.http.route<\/code><\/strong> &#8211; This defines how to spread the traffic to the subsets. Here, we use the canary method, so the<strong> 50% traffic route to the v1<\/strong> service and the <strong>remaining 50% will be routed to the v2<\/strong> service.<\/p>\n<p>To apply this, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f virtual-service.yaml<\/code><\/pre>\n<p>To list the available virtual services, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl -n istio-test get virtualservices<\/code><\/pre>\n<p>You will get the following output.<\/p>\n<pre><code class=\"language-bash\">$ kubectl -n istio-test get virtualservices\n\nNAME      GATEWAYS   HOSTS                                      AGE\n\nbackend              [\"backend.istio-test.svc.cluster.local\"]   5s<\/code><\/pre>\n<p>Now, out setup is ready. Now we need to test the canary routing using a client Pod.<\/p>\n<h3 id=\"validate-traffic-routing-with-a-client\">Validate Traffic Routing With A Client<\/h3>\n<p>For validation, we are going to create a client pod to send a request to the sample applications.<\/p>\n<p>To create a client pod, use the following manifest. We are deploying a curl image to send curl request.<\/p>\n<pre><code class=\"language-yaml\">cat &lt;&lt;EOF | kubectl apply -f - \napiVersion: v1\nkind: Pod\nmetadata:\n  name: sleep\n  namespace: istio-test\n  labels:\n    app: sleep\nspec:\n  containers:\n  - name: curl\n    image: curlimages\/curl:8.8.0\n    command:\n    - sh\n    - -c\n    - sleep 3650d\nEOF<\/code><\/pre>\n<p>Wait until the pod gets ready.<\/p>\n<p>The sidecar gets injected for this pod as well. Check the status of the proxies using <code>Istioctl<\/code> again.<\/p>\n<pre><code class=\"language-bash\">istioctl -n istio-test proxy-status<\/code><\/pre>\n<p>You can see the new pod has also been added as a proxy in Istiod.<\/p>\n<pre><code class=\"language-bash\">istioctl -n istio-test proxy-status\n\nNAME                                       CLUSTER        ISTIOD                      VERSION     SUBSCRIBED TYPES\n\nbackend-v1-6cf9fdbd56-99lkf.istio-test     Kubernetes     istiod-5d5696f494-pzqxs     1.28.0      4 (CDS,LDS,EDS,RDS)\nbackend-v2-776557dbfd-www9b.istio-test     Kubernetes     istiod-5d5696f494-pzqxs     1.28.0      4 (CDS,LDS,EDS,RDS)\nsleep.istio-test                           Kubernetes     istiod-5d5696f494-pzqxs     1.28.0      4 (CDS,LDS,EDS,RDS)<\/code><\/pre>\n<p>Now, lets make10 requests to the backend service from the client and see <strong>how many times<\/strong> the traffic <strong>hits the v1 and v2<\/strong> services.<\/p>\n<pre><code class=\"language-bash\">for i in $(seq 1 10); do\n  kubectl exec -n istio-test -it sleep -- curl -s http:\/\/backend.istio-test.svc.cluster.local\n  echo\ndone<\/code><\/pre>\n<p>You can see that the requests are is split among each service.<\/p>\n<pre><code class=\"language-bash\">$  for i in $(seq 1 10); do\n    kubectl exec -n istio-test -it sleep -- curl -s http:\/\/backend.istio-test.svc.cluster.local\n    echo\n  done\n\nhello from backend v2\n\nhello from backend v2\n\nhello from backend v2\n\nhello from backend v1\n\nhello from backend v1\n\nhello from backend v2\n\nhello from backend v1\n\nhello from backend v1\n\nhello from backend v1\n\nhello from backend v1<\/code><\/pre>\n<p>Thats it! You have deployed Istio and tested a sample app for canary routing.<\/p>\n<h2 id=\"exposing-applications-outside-cluster\">Exposing Applications Outside Cluster<\/h2>\n<p>The next step in the setup would be setting up a gateway to expose Istio enabled services outside the cluster.<\/p>\n<p>The standard way to expose applications in Istio is using Ingress Gateways combined with a <strong>VirtualService<\/strong>.<\/p>\n<p>The <a href=\"https:\/\/devopscube.com\/kubernetes-gateway-api\/\" rel=\"noreferrer\">Kubernetes Gateway API<\/a> will be the default API for traffic management in the future.<\/p>\n<p>We have covered the entire setup in a separate blog.<\/p>\n<p>Please refer <a href=\"https:\/\/devopscube.com\/istio-ingress-kubernetes-gateway-api\/\" rel=\"noreferrer\">Istio Ingress With Kubernetes Gateway API<\/a> for the detailed setup guide.<\/p>\n<h2 id=\"cleanup\">Cleanup<\/h2>\n<p>Once your setup is done and if you dont want the resources to be running, cleanup the resources so that you can save CPU and memory resources.<\/p>\n<p>To delete the deployments and services of the application, use the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl -n istio-test delete deploy backend-v1 backend-v2\nkubectl -n istio-test delete svc backend\nkubectl -n istio-test delete po sleep<\/code><\/pre>\n<p>Now, we can delete the Namespace<\/p>\n<pre><code class=\"language-bash\">kubectl delete ns istio-test<\/code><\/pre>\n<p>To remove the Istio Daemon, use the following Helm command.<\/p>\n<pre><code class=\"language-bash\">helm -n istio-system uninstall istiod<\/code><\/pre>\n<p>To uninstall the Istio Custom Resource Definitions<\/p>\n<pre><code class=\"language-bash\">helm -n istio-system uninstall istio-base<\/code><\/pre>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>When it comes to Istio setup in organizations, Helm is the preferred method for setting up Istio. <\/p>\n<p><strong>Helm + GitOps<\/strong> is a strong pattern for mature teams. This ways you get versioned installs, clear change history, rollback ability.<\/p>\n<p>In this blog, we walked through installing Istio custom resources, the sidecar proxy (daemon), and using <code>istioctl<\/code> for setup and basic configuration. We also showed how to test the installation with a sample app.<\/p>\n<p>There is more you can do with Istio, like routing rules, timeouts, mutual authentication, etc., and we will cover these topics one by one in the upcoming posts.<\/p>\n<p>If you have any trouble getting started with Istio setup, do let us know in the comments. We will help you out!<\/p>\n<p><!--kg-card-begin: html--><br \/>\n <iframe loading=\"lazy\" src=\"https:\/\/embeds.beehiiv.com\/2a495ef4-3de7-4600-8a0d-de5dc968b372\" data-test-id=\"beehiiv-embed\" width=\"100%\" height=\"320\" frameborder=\"0\" scrolling=\"no\" style=\"border-radius: 4px; border: 2px solid #e5e7eb; margin: 0; background-color: transparent;\"><\/iframe><br \/>\n<!--kg-card-end: html--><\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/setup-istio-on-kubernetes\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Set up Istio on Kubernetes Cluster? [Step-by-Step] \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/setup-istio-on-kubernetes\/<\/p>\n","protected":false},"author":1,"featured_media":280,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-279","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/279","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=279"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/279\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/280"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=279"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=279"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=279"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}