Istio is a Kubernetes service mesh providing traffic management, workload identity, mutual TLS, authorization, observability, and advanced resiliency capabilities.
In this guide we’ll install Istio using its traditional sidecar data plane, add an application namespace to the mesh, enforce mTLS, configure traffic routing, perform a canary rollout, configure retries and circuit breaking, and introduce service-to-service authorization.
By the end, our application traffic will look roughly like this:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
subgraph Kubernetes
subgraph PodA["Frontend Pod"]
A[Frontend] --> EA[Envoy]
end
subgraph PodB["API Pod"]
EB[Envoy] --> B[API]
end
EA <-->|mTLS| EB
CP[Istiod]
CP -.->|Configuration| EA
CP -.->|Configuration| EB
endThe application containers do not need to implement the service-mesh functionality themselves.
Envoy handles it on their behalf.
Sidecar Mode and Ambient Mode
Modern Istio supports two data-plane architectures:
Sidecar mode
Pod
├── Application
└── Envoy
Ambient mode
Pod
└── Application
Node
└── ztunnel
Optional L7
└── waypoint proxyThis article deliberately uses sidecar mode.
It remains useful when:
- you already operate an Envoy-sidecar Istio environment
- you want the traditional Istio architecture
- workloads depend on sidecar-specific behavior
- you need predictable per-workload L7 proxying
- you’re learning the relationship between Istio traffic resources and Envoy
Ambient mode removes the requirement to inject an Envoy proxy into every application pod and is worth evaluating for new environments.
We’ll return to it at the end.
Prerequisites
You need:
- a Kubernetes cluster
kubectl- cluster-admin or equivalent installation permissions
- a supported Kubernetes version
- sufficient resources for the Istio control plane and Envoy proxies
Verify the cluster:
kubectl cluster-info
kubectl get nodesAll nodes should be healthy before introducing a service mesh.
We’ll use this application namespace throughout the guide:
kubectl create namespace productionInstall istioctl
istioctl is the primary Istio administration and diagnostic CLI.
On macOS:
brew install istioctlAlternatively, download an Istio release:
curl -L https://istio.io/downloadIstio | sh -Enter the downloaded directory:
cd istio-*Add the CLI to your path:
export PATH=$PWD/bin:$PATHVerify it:
istioctl versionRun the Pre-Installation Check
Before installing Istio:
istioctl x precheckResolve any reported problems before proceeding.
Typical areas include:
- Kubernetes compatibility
- permissions
- webhook support
- required APIs
- platform configuration
Choose an Istio Installation Profile
Istio provides several installation profiles.
Common profiles include:
default
demo
minimal
ambient
remoteFor this guide we’ll use:
defaultThe default profile is the normal starting point for a production sidecar deployment.
The demo profile is useful for evaluation and tutorials but enables settings that are not intended as a production baseline.
The minimal profile only installs the control-plane components and is useful when building a more explicitly separated topology.
Install Istio
Install the default profile:
istioctl install -yOr explicitly:
istioctl install \
--set profile=default \
-yCheck the control plane:
kubectl get pods -n istio-systemVerify the installation:
istioctl verify-installThen:
istioctl proxy-statusThere will not yet be application proxies until workloads are added to the mesh.
Enable Automatic Sidecar Injection
Enable injection on the production namespace:
kubectl label namespace production \
istio-injection=enabledVerify it:
kubectl get namespace production \
--show-labelsExisting pods are not modified.
Restart the deployments:
kubectl rollout restart deployment \
-n productionWatch the rollout:
kubectl get pods -n production -wVerify Envoy Injection
Before injection, a pod may show:
READY
1/1After injection:
READY
2/2Inspect the containers:
kubectl get pod <pod-name> \
-n production \
-o jsonpath='{.spec.containers[*].name}'You should see:
application istio-proxyistio-proxy is the Envoy sidecar.
You can also inspect its configuration:
istioctl proxy-config all \
<pod-name> \
-n productionUnderstand Istio mTLS
Istio automatically uses mTLS when traffic is exchanged between workloads that support Istio mTLS.
Conceptually:
The diagram could not be displayed. Its source is available below.
Diagram source
sequenceDiagram
participant A as Frontend
participant EA as Envoy A
participant EB as Envoy B
participant B as API
A->>EA: HTTP
EA->>EB: mTLS
EB->>B: HTTP
B->>EB: HTTP
EB->>EA: mTLS
EA->>A: HTTPHowever, there is an important distinction between:
using mTLSand:
requiring mTLSBy default, Istio supports incremental mesh adoption. A meshed workload can therefore accept both mTLS and plaintext connections under permissive behavior.
To prevent plaintext traffic, configure STRICT peer authentication.
Enforce Strict mTLS
Create:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICTApply it:
kubectl apply -f peer-authentication.yamlNow workloads in production require incoming connections to use Istio mTLS.
A non-meshed workload attempting to directly access a protected workload should therefore fail.
During a gradual migration, keep workloads permissive until all required clients participate in the mesh.
Define Application Versions
Suppose our API has two versions:
api-v1
api-v2Both deployments are selected by the Kubernetes Service:
apiVersion: v1
kind: Service
metadata:
name: api
namespace: production
spec:
selector:
app: api
ports:
- port: 80
targetPort: 8080The deployments use labels:
app: api
version: v1and:
app: api
version: v2Istio can use those labels to create logical subsets.
Create an Istio DestinationRule
A DestinationRule defines policies applied after Istio has selected a destination.
Create:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api
namespace: production
spec:
host: api
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2Apply it:
kubectl apply -f destination-rule.yamlWe can now route traffic to either the stable or canary subset without creating separate public endpoints.
Configure a Canary Deployment
Create a VirtualService:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api
namespace: production
spec:
hosts:
- api
http:
- route:
- destination:
host: api
subset: stable
weight: 90
- destination:
host: api
subset: canary
weight: 10Apply it:
kubectl apply -f virtual-service.yamlTraffic now looks approximately like:
+--> v1 stable 90%
frontend -> api
+--> v2 canary 10%This makes traffic rollout independent of Kubernetes replica counts.
Gradually Shift Traffic
A release might progress through:
100 / 0
99 / 1
90 / 10
75 / 25
50 / 50
0 / 100You only need to modify the VirtualService weights.
This gives platform and application teams fine-grained control over release exposure.
Header-Based Routing
Istio can also route requests based on HTTP attributes.
For example, requests containing:
x-canary: truecan be sent entirely to the new version:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api
namespace: production
spec:
hosts:
- api
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: api
subset: canary
- route:
- destination:
host: api
subset: stable
weight: 90
- destination:
host: api
subset: canary
weight: 10Developers or automated tests can explicitly exercise the canary:
curl \
-H "x-canary: true" \
http://api.productionwhile ordinary users continue through the weighted rollout.
Configure Request Timeouts
Slow dependencies can cause resource exhaustion upstream.
Set a request timeout:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api
namespace: production
spec:
hosts:
- api
http:
- route:
- destination:
host: api
timeout: 10sRequests taking longer than ten seconds fail rather than waiting indefinitely.
Timeouts should reflect the application’s actual latency requirements rather than being copied blindly between services.
Configure Retries
Transient errors can sometimes be retried automatically:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: api
namespace: production
spec:
hosts:
- api
http:
- route:
- destination:
host: api
timeout: 10s
retries:
attempts: 2
perTryTimeout: 3s
retryOn: 5xx,reset,connect-failureRetries should be used conservatively.
In particular, be careful with non-idempotent operations such as:
POST /payments
POST /orders
POST /transactionsunless the application implements appropriate idempotency semantics.
Configure Connection Pools
Istio’s DestinationRule can control connection behavior:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api
namespace: production
spec:
host: api
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 100
http2MaxRequests: 1000
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2Connection pools help prevent a caller from creating unbounded pressure on a downstream service.
Configure Outlier Detection
Istio can detect failing endpoints and temporarily remove them from load balancing.
For example:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api
namespace: production
spec:
host: api
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 5
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50Conceptually:
api-1 healthy -> receive traffic
api-2 healthy -> receive traffic
api-3 failing -> temporarily ejectedThis can reduce the impact of a malfunctioning instance before Kubernetes itself decides the pod is unhealthy.
Circuit Breaking
Circuit breaking is not a single Istio switch.
It is typically implemented through a combination of:
- connection limits
- pending request limits
- request limits
- outlier detection
For example:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: api
namespace: production
spec:
host: api
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 50
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 50This protects the caller and destination from several failure modes.
Service-to-Service Authorization
mTLS gives workloads authenticated identities.
Authorization policies let us decide what those identities may access.
Suppose:
frontend -> apishould be allowed, while arbitrary workloads should not be able to call the API.
Assume the frontend runs using:
ServiceAccount: frontend
Namespace: productionCreate:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: api
namespace: production
spec:
selector:
matchLabels:
app: api
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/production/sa/frontendOnce an ALLOW policy selects the API workload, requests not matching an allow rule are denied.
This changes security from:
Anything with network connectivity can call the APIto:
Only explicitly authorized workload identities can call the APIRestrict HTTP Operations
Authorization can be made more granular:
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: api
namespace: production
spec:
selector:
matchLabels:
app: api
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/production/sa/frontend
to:
- operation:
methods:
- GET
- POST
paths:
- "/api/v1/*"The frontend identity can now access only selected HTTP operations.
For identity-based authorization like this, use strict mTLS so that source identity is reliably established.
Install Observability Components
Istio produces telemetry that can be consumed by systems such as:
- Prometheus
- Grafana
- Kiali
- Jaeger
- OpenTelemetry
For a test environment, the Istio repository includes sample add-ons.
For example, using manifests that match your installed Istio release:
kubectl apply -f samples/addons/prometheus.yaml
kubectl apply -f samples/addons/kiali.yaml
kubectl apply -f samples/addons/jaeger.yamlThese sample manifests are useful for learning and demonstrations.
They should not be treated as production deployment manifests.
For production, integrate Istio telemetry with your managed observability stack or deploy the individual observability systems using their supported production installation mechanisms.
Open Kiali
If Kiali is installed:
istioctl dashboard kialiKiali provides a topology-oriented view of the mesh.
You can inspect:
- services
- workloads
- request rates
- error rates
- latency
- traffic relationships
- Istio configuration
A service graph is particularly useful for identifying dependencies that may not be documented elsewhere.
Inspect Prometheus Metrics
Envoy exposes Prometheus-compatible telemetry.
Common service-mesh signals include:
Request rate
Error rate
P50 latency
P95 latency
P99 latency
TCP connections
Bytes transferredAvoid retaining every high-cardinality Envoy metric indefinitely. Large Istio installations can produce substantial telemetry volume.
Distributed Tracing
A service mesh can provide telemetry around requests, but tracing still depends on appropriate trace context propagation through the application.
A typical request might look like:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
U[User]
--> I[Ingress]
--> F[Frontend]
--> A[API]
--> D[Database]A distributed trace allows the individual spans to be correlated into a single request path.
If Jaeger is installed for testing:
istioctl dashboard jaegerFor production environments, consider integrating Istio with your existing OpenTelemetry tracing architecture.
Troubleshooting Istio
Istio has extensive diagnostic tooling.
Start with:
istioctl analyzeOr scope it:
istioctl analyze -n productionThis catches many configuration problems before you start inspecting Envoy directly.
Check Proxy Synchronization
Run:
istioctl proxy-statusThis shows whether Envoy proxies are synchronized with istiod.
A proxy that remains stale or disconnected can behave differently from the rest of the mesh.
Inspect Envoy Configuration
To inspect all generated configuration:
istioctl proxy-config all \
deploy/api \
-n productionMore targeted commands are often easier to read.
Clusters:
istioctl proxy-config clusters \
deploy/api \
-n productionListeners:
istioctl proxy-config listeners \
deploy/api \
-n productionRoutes:
istioctl proxy-config routes \
deploy/api \
-n productionEndpoints:
istioctl proxy-config endpoints \
deploy/api \
-n productionThese commands are invaluable when the Kubernetes configuration looks correct but Envoy is not behaving as expected.
Inspect Envoy Logs
kubectl logs deploy/api \
-n production \
-c istio-proxyCheck the application container separately so that proxy failures are not confused with application failures.
Validate Traffic Configuration
Run:
istioctl analyze -n productionThen inspect:
kubectl get virtualservice -n production
kubectl get destinationrule -n production
kubectl get peerauthentication -n production
kubectl get authorizationpolicy -n productionA useful troubleshooting sequence is:
Kubernetes Service
↓
Endpoints
↓
DestinationRule
↓
VirtualService
↓
Envoy configuration
↓
Authentication / AuthorizationDo not start by reading thousands of lines of Envoy logs if the Kubernetes Service has no endpoints.
Production Installation Considerations
For production, treat Istio as part of the platform rather than as an application add-on.
Version control:
- installation profile
- Istio version
- platform-specific settings
- ingress/egress gateway configuration
- resource requests and limits
PeerAuthenticationAuthorizationPolicyVirtualServiceDestinationRule- telemetry configuration
Also define an upgrade procedure before deploying broadly.
Service meshes touch almost every workload in the cluster, so uncontrolled upgrades carry a larger blast radius than ordinary application upgrades.
Roll Out Istio Gradually
Do not start by injecting every namespace.
A safer migration is:
The diagram could not be displayed. Its source is available below.
Diagram source
flowchart LR
A[Install Istio]
--> B[Select Test Namespace]
--> C[Enable Injection]
--> D[Verify Traffic]
--> E[Observe mTLS]
--> F[Enable STRICT mTLS]
--> G[Add Authorization]
--> H[Expand Rollout]Start with:
kubectl label namespace test \
istio-injection=enabledRestart only those workloads.
Observe them before proceeding namespace by namespace.
This makes it much easier to distinguish application problems from mesh-related problems.
What About Istio Ambient Mode?
This guide intentionally uses sidecars, but a new Istio deployment should also evaluate Ambient mode.
With traditional Istio:
Pod
├── Application
└── EnvoyWith Ambient:
Pod
└── Application
Node
└── ztunnelOptional L7 functionality can be provided through waypoint proxies.
Ambient therefore changes the operational model substantially.
Instead of injecting and sizing an Envoy proxy for every application pod, the base secure connectivity layer is provided by node-level ztunnel components.
The installation path is also different:
istioctl install \
--set profile=ambient \
--skip-confirmationAmbient also makes extensive use of Kubernetes Gateway API resources.
Do not mix installation instructions between the two architectures without understanding the resulting data-plane topology.
Final Verification
Run:
istioctl analyzeThen:
istioctl proxy-statusVerify the namespace:
kubectl get pods -n productionVerify security configuration:
kubectl get peerauthentication -n production
kubectl get authorizationpolicy -n productionVerify traffic configuration:
kubectl get virtualservice -n production
kubectl get destinationrule -n productionAt this point the mesh provides the foundation for:
- workload identity
- automatic mTLS
- strict transport security
- service authorization
- weighted traffic routing
- canary releases
- header-based routing
- retries
- timeouts
- connection management
- outlier detection
- circuit breaking
- metrics
- distributed tracing
The most important production step after the technical installation is to move from a connectivity-first model to an identity-first security model: establish strict mTLS, identify legitimate service dependencies, and progressively express those dependencies as authorization policies.



