Skip to content

Istio How-to Guides

Scope

Task recipes for Istio 1.29-1.31: installing sidecar and ambient modes, Gateway API CRDs, waypoints, canary upgrades with revision tags, sidecar-to-ambient migration, traffic management, mTLS and authorization, hardening, and troubleshooting. Commands were checked against the istio.io release-1.31 docs on 2026-09-25. Look-up tables (profiles, ports, labels, versions) are in Reference; background is in Explanation.

Artifact locations changed in 1.31

Istio 1.31+ no longer publishes to gcr.io/istio-release, registry.istio.io, or istio-release.storage.googleapis.com. Use Docker Hub images, the Helm repo https://blob.istio.io/istio-release/charts, or OCI charts at ghcr.io/istio/release/charts. Update mirrors and air-gapped pipelines before the final GCP "scream test" (2026-12-08/09).

Deployment Patterns

Installation Profiles

Pick a deployment profile (default, demo, minimal, remote, ambient, empty, preview) plus an optional platform profile (gke, eks, openshift, k3s, ...). The full table of what each profile installs is in Reference: Installation Profiles and Helm Charts.

Install the Gateway API CRDs

Istio does not install the Kubernetes Gateway API CRDs. Install them before using Gateway/HTTPRoute or waypoints, and upgrade them before upgrading Istio (1.30 needs v1.5.x, 1.31 builds against v1.6.0):

kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml

Use experimental-install.yaml instead if you need experimental-channel resources. After upgrading Istio, istioctl analyze reports IST0176 when the installed CRDs are older than the version Istio requires.

Sidecar Mode (istioctl)

# Production install with istioctl
istioctl install --set profile=default \
  --set meshConfig.accessLogFile=/dev/stdout \
  --set values.pilot.resources.requests.memory=2Gi -y

# Enable sidecar injection for a namespace, then restart workloads
kubectl label namespace default istio-injection=enabled
kubectl rollout restart deployment -n default

Auto mTLS (meshConfig.enableAutoMtls) is on by default, so it does not need to be set.

Sidecar Mode with the Istio CNI Node Agent

Removes the privileged istio-init container from every pod:

cat <<EOF > istio-cni.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  components:
    cni:
      namespace: istio-system
      enabled: true
EOF
istioctl install -f istio-cni.yaml -y

With Helm: helm install istio-cni istio/cni -n istio-system --wait, then install istiod with --set pilot.cni.enabled=true.

Ambient Mesh (Sidecar-less)

# Install ambient mode (istiod + istio-cni + ztunnel; no ingress gateway)
istioctl install --set profile=ambient --skip-confirmation

# Add a namespace to the ambient mesh (no pod restart needed)
kubectl label namespace default istio.io/dataplane-mode=ambient

# Deploy a waypoint for L7 and enrol the namespace in it (optional)
istioctl waypoint apply -n default --enroll-namespace
istioctl waypoint status -n default

Ambient Mesh with Helm

helm repo add istio https://blob.istio.io/istio-release/charts
helm repo update

helm install istio-base istio/base -n istio-system --create-namespace --wait
helm install istiod istio/istiod --namespace istio-system --set profile=ambient --wait
helm install istio-cni istio/cni -n istio-system --set profile=ambient --wait
helm install ztunnel istio/ztunnel -n istio-system --wait

# Optional ingress gateway
helm install istio-ingress istio/gateway -n istio-ingress --create-namespace --wait

For native nftables instead of iptables (ambient 1.28+, sidecar 1.27+), add --set values.global.nativeNftables=true (istioctl) or the equivalent global.nativeNftables=true Helm value.

Upgrades

Canary Upgrade with Revision Tags

The recommended upgrade path runs two control-plane revisions side by side and moves a tag between them.

# 0. Check compatibility and read the upgrade notes for the target release
istioctl x precheck

# 1. Install the new control plane as a revision (no traffic moves yet)
istioctl install --revision=1-31-1 --set profile=minimal --skip-confirmation

# 2. Point stable/canary tags at revisions
istioctl tag set prod-stable --revision 1-30-5
istioctl tag set prod-canary --revision 1-31-1

# 3. Move a test namespace to the canary and restart it
kubectl label namespace test-ns istio-injection- istio.io/rev=prod-canary --overwrite
kubectl rollout restart deployment -n test-ns
istioctl proxy-status

# 4. Promote: move the stable tag, restart the remaining namespaces
istioctl tag set prod-stable --revision 1-31-1 --overwrite
kubectl rollout restart deployment -n app-ns-1

# 5. Remove the old control plane once no proxies use it
istioctl uninstall --revision 1-30-5 -y

Revision names must be DNS-label safe (use 1-31-1, not 1.31.1). The default tag (istioctl tag set default --revision 1-31-1) controls which revision serves istio-injection=enabled and cluster-wide validation.

Upgrade checklist for 1.30 and 1.31

  • 1.30: upgrade Gateway API CRDs to v1.5.x first, or TLSRoute/ReferenceGrant become invisible and TLS passthrough listeners show attachedRoutes: 0. XDS debug endpoints on 15010 need auth (istioctl --plaintext breaks; ENABLE_DEBUG_ENDPOINT_AUTH=false restores the old behaviour). CNI config files are now mode 0600.
  • 1.31: update artifact locations (see above). Istio now sends unhealthy endpoints unless outlierDetection.minHealthPercent is set (PILOT_AUTO_SEND_UNHEALTHY_ENDPOINTS=false to revert). Large ambient meshes (about 40,000+ workloads) should raise ISTIO_GPRC_MAXRECVMSGSIZE on istiod. Previously auto-registered WorkloadEntry objects need the networking.istio.io/tunnel=http label or re-registration to use HBONE.
  • Use compatibilityVersion (for example 1.30) to keep previous defaults while you test.

Upgrade with Helm

helm repo update
helm upgrade istio-base istio/base -n istio-system
helm upgrade istiod istio/istiod -n istio-system --set profile=ambient --wait
helm upgrade istio-cni istio/cni -n istio-system --set profile=ambient --wait
helm upgrade ztunnel istio/ztunnel -n istio-system --wait

Upgrade the CNI agent before or together with istiod; since 1.29 the agent reconciles in-pod redirect rules for existing ambient pods automatically. For revisioned Helm installs, follow the Helm upgrade guide.

Migrate from Sidecar to Ambient

The migration guide (added with 1.30) is gradual and reversible, one namespace at a time:

  1. Install ztunnel and switch istio-cni to the ambient profile; sidecar workloads keep working.
  2. Migrate policies: VirtualService to HTTPRoute (VirtualService in ambient is alpha), subsets to version-specific Services, L7 AuthorizationPolicy, RequestAuthentication and WasmPlugin to waypoints via targetRefs. L4 policies need no change. EnvoyFilter has no ambient equivalent.
  3. Per namespace: deploy a waypoint if needed, label istio.io/dataplane-mode=ambient and istio.io/use-waypoint, remove the injection label, restart pods.

L7 policy gap

With L7 policies, zero-downtime migration is not currently possible: between removing selector-based sidecar policies and applying waypoint-attached ones, L7 rules are unenforced. Plan a maintenance window.

Traffic Management

Canary Deployment (Istio API)

Subsets v2/v3 must be defined in a DestinationRule for reviews.

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v3
  - route:
    - destination:
        host: reviews
        subset: v2
      weight: 90
    - destination:
        host: reviews
        subset: v3
      weight: 10

Circuit Breaking

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: backend
spec:
  host: backend
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

Since 1.31, meshConfig.defaultTrafficPolicy can set a mesh-wide baseline connectionPool and outlierDetection that every DestinationRule inherits.

Security

Peer Authentication (mTLS)

A PeerAuthentication in the root namespace (istio-system) applies mesh-wide:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT  # STRICT | PERMISSIVE | DISABLE

Roll out PERMISSIVE first, confirm with telemetry that no plaintext clients remain, then switch to STRICT. For a namespace-wide policy, create the same resource in that namespace.

Harden a Multi-Tenant Mesh

  • Restrict EnvoyFilter create/update to mesh administrators with Kubernetes RBAC (ISTIO-SECURITY-2026-006).
  • Prefer Gateway API routes over VirtualService with the mesh gateway in namespace-based multi-tenancy (ISTIO-SECURITY-2026-002); limit who can create VirtualService, DestinationRule, and ServiceEntry.
  • Restrict who can create RequestAuthentication (JWKS SSRF, CVE-2026-41413).
  • Keep debug endpoint auth on (ENABLE_DEBUG_ENDPOINT_AUTH=true, default since 1.30).
  • Optionally deploy default NetworkPolicies for istiod, istio-cni, and ztunnel with global.networkPolicy.enabled=true (1.29+); allow TCP 15008 for ambient pods.
  • For FIPS environments on 1.31, set COMPLIANCE_POLICY=fips-140-3.
  • Stay on the latest patch of a supported minor; see Reference: Security Bulletins.

Troubleshooting

Symptom Diagnosis Fix
Sidecar not injected kubectl get ns --show-labels; istioctl analyze kubectl label ns <ns> istio-injection=enabled (or istio.io/rev=<tag>), then restart pods
Ambient pod not captured istioctl ztunnel-config workloads shows protocol TCP instead of HBONE Check namespace/pod labels, istio-cni agent logs, and that the pod is not in excludeNamespaces
503 errors istioctl analyze; istioctl proxy-config cluster <pod> Check DestinationRule subsets, VirtualService/HTTPRoute backends
mTLS handshake failures istioctl proxy-config secret <pod>; istioctl ztunnel-config certificates Check PeerAuthentication mode and that both sides share a root
L7 policy ignored in ambient istioctl waypoint status -n <ns> Deploy a waypoint and add istio.io/use-waypoint; attach policy with targetRefs
TLS passthrough stops after upgrade to 1.30 kubectl get gateway -o yaml shows attachedRoutes: 0; istioctl analyze IST0176 Upgrade Gateway API CRDs to v1.5.x+
ztunnel reconnect loop, ResourceExhausted (1.31, very large meshes) istiod logs Raise ISTIO_GPRC_MAXRECVMSGSIZE on istiod
High latency istioctl proxy-status; Envoy stats Check proxy CPU limits and worker threads; scope config with Sidecar
Config rejected istioctl validate -f config.yaml Fix YAML and apiVersion
# Debug toolkit
istioctl analyze --namespace default
istioctl proxy-status
istioctl proxy-config routes <pod>
istioctl proxy-config listeners <pod>
kubectl logs -l app=istiod -n istio-system

Resource Requirements

Starting resource requests and the official per-proxy measurements are in Reference: Resource and Performance Figures. Size istiod from the number of proxies and the rate of config changes, and scale it horizontally.


Commands & Recipes

Installation (Ambient Mode)

# Download istioctl (pin a version; the script defaults to the latest release)
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.31.1 sh -
export PATH=$PWD/istio-1.31.1/bin:$PATH

# Install the ambient profile
istioctl install --set profile=ambient -y

# Enable ambient for a namespace
kubectl label namespace default istio.io/dataplane-mode=ambient

# Verify
istioctl version
kubectl get pods -n istio-system

Traffic Management (Gateway API)

# Canary deployment (90/10 split) for in-mesh traffic (GAMMA: parentRef is a Service)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: myapp-canary
spec:
  parentRefs:
    - group: ""
      kind: Service
      name: myapp
      port: 8080
  rules:
    - backendRefs:
        - name: myapp-v1
          port: 8080
          weight: 90
        - name: myapp-v2
          port: 8080
          weight: 10

In ambient mode this route is enforced by the waypoint serving myapp, so myapp (or its namespace) must use a waypoint.

# Waypoint proxy (opt-in L7 for a namespace); equivalent to `istioctl waypoint apply -n default`
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: waypoint
  namespace: default
  labels:
    istio.io/waypoint-for: service
spec:
  gatewayClassName: istio-waypoint
  listeners:
    - name: mesh
      port: 15008
      protocol: HBONE

Security Policies

# PeerAuthentication: require mTLS mesh-wide
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
# AuthorizationPolicy: L7 rules (sidecar mode: selector; ambient: use targetRefs to the waypoint/Service)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: allow-frontend
spec:
  selector:
    matchLabels:
      app: backend
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/default/sa/frontend"]
      to:
        - operation:
            methods: ["GET"]
            paths: ["/api/*"]

Diagnostics

# Proxy sync status
istioctl proxy-status

# Analyze configuration issues in all namespaces
istioctl analyze -A

# Debug Envoy config for a workload
istioctl proxy-config routes deploy/myapp
istioctl proxy-config clusters deploy/myapp

# Ambient: what ztunnel knows
istioctl ztunnel-config workloads
istioctl ztunnel-config certificates
kubectl logs -n istio-system -l app=ztunnel -f

Sources