OMNI52
Istio Quick Reference OMNI52™ GmbH

Istio service mesh
on a single sheet.

A dense reference for senior platform engineers and SREs. Traffic management, security, observability, performance, multi-cluster and the failure modes you actually hit in production. No beginner slides.

Preview (3 pages, A4 landscape)

Cheatsheet page 1: architecture, traffic management, security
Cheatsheet page 2: observability, performance, multi-cluster, diagnostics
Cheatsheet page 3: Gateway API, anti-patterns, contact & license

Download the PDF

Direct download, no email required. CC BY-SA 4.0 — copying, printing and redistributing is explicitly allowed as long as the attribution stays visible.

Istio Quick Reference (PDF, ~100 KB)

What’s inside

Traffic

Gateway, VirtualService, DestinationRule, ServiceEntry, Sidecar scoping — with outlier detection and connection-pool defaults.

Security

PeerAuthentication, AuthorizationPolicy with JWT claims, RequestAuthentication, SPIFFE identity, migration pattern for mTLS STRICT.

Observability

Telemetry API, the metrics that matter from the Envoy stack, tracing header propagation, OTel logging with filters.

Performance

Sidecar sizing rules of thumb, Pilot tuning for push storms, ambient mode (1.30 GA) as the sidecar-less option.

Multi-cluster

Primary-remote, multi-primary, external control plane. Trust-domain setup and endpoint discovery in one block.

Diagnostics

istioctl tools in the order you reach for them during an incident. Typical 503 UC/NR/UF codes with root cause.

Full-text cheatsheet

The same content as the PDF — to read along, search and copy the YAML snippets directly. As of: Istio 1.30 (v1.30 · 2026.07).

Architecture

Datapath & components

Client → ingress GW → sidecar(src) → sidecar(dst) → app. Hop ≈ 0.5–2 ms p99. mTLS handshake only on connect.

istiod: Pilot/Citadel/Galley combined, xDS push + CA. istio-proxy: Envoy sidecar (mutating webhook) or ambient ztunnel. Gateway: dedicated Envoys, Deployment + LB service.

Envoy version = Istio version + 8. Istio 1.30 → Envoy 1.38.

Install profiles

default (prod), minimal (istiod only), ambient (sidecar-less), remote (MC workload cluster), demo (noisy). Canary via --revision=1-30-1.

istioctl install --set profile=default \
  --set values.global.proxy.resources.requests.cpu=100m
istioctl verify-install

Traffic Management

Gateway

Binds ports/hosts/TLS at the edge proxy. servers[].port + tls.mode: SIMPLE, MUTUAL, PASSTHROUGH, ISTIO_MUTUAL. credentialName points to a Secret in the same namespace as the gateway pod.

apiVersion: networking.istio.io/v1
kind: Gateway
metadata: {name: web-gw, namespace: istio-system}
spec:
  selector: {istio: ingressgateway}
  servers:
  - port: {number: 443, name: https, protocol: HTTPS}
    hosts: ["www.istio-quickref.de"]
    tls:
      mode: SIMPLE
      credentialName: web-cert

VirtualService

Routing rules. Binds hosts to gateways (or mesh for east-west). Match order is significant — the first matching rule wins. Header match is case-insensitive, path match is case-sensitive.

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: v2}
  - route:
    - destination: {host: reviews, subset: v1}
      weight: 90
    - destination: {host: reviews, subset: v3}
      weight: 10
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream
    timeout: 10s

DestinationRule

Subsets (versions) + traffic policy (LB, connection pool, outlier detection, TLS). Takes effect only after VirtualService routing. host must be an FQDN or a short name in the same namespace.

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata: {name: reviews}
spec:
  host: reviews
  trafficPolicy:
    connectionPool:
      tcp: {maxConnections: 100}
      http:
        http1MaxPendingRequests: 64
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 60s
    loadBalancer:
      consistentHash:
        httpHeaderName: x-user-id
  subsets:
  - name: v1
    labels: {version: v1}
  - name: v2
    labels: {version: v2}

ServiceEntry

Pulls external hosts into the mesh registry (DB, SaaS, REST APIs). MESH_EXTERNAL + DNS resolution is the clean default combo. Without a ServiceEntry, egress traffic stays a BlackHoleCluster when outboundTrafficPolicy=REGISTRY_ONLY.

apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata: {name: stripe-api}
spec:
  hosts: ["api.stripe.com"]
  ports:
  - {number: 443, name: https, protocol: HTTPS}
  resolution: DNS
  location: MESH_EXTERNAL

Sidecar

Limits what a workload sees of the mesh registry — critical for memory footprint and push latency in large clusters. Default: every sidecar gets config for all services. Mandatory above > 200 services.

apiVersion: networking.istio.io/v1
kind: Sidecar
metadata: {name: default, namespace: prod}
spec:
  egress:
  - hosts:
    - "./*"             # own namespace only
    - "istio-system/*"
    - "shared/*"

Security

PeerAuthentication

mTLS mode between sidecars. STRICT, PERMISSIVE, DISABLE. Scope: mesh (in istio-system), namespace, or workload via selector. Migrating to STRICT: first mesh-wide PERMISSIVE, then switch namespace by namespace.

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: default, namespace: prod}
spec:
  mtls: {mode: STRICT}
---
# Port exception: NLB health check without mTLS
spec:
  selector: {matchLabels: {app: legacy}}
  mtls: {mode: STRICT}
  portLevelMtls:
    8080: {mode: PERMISSIVE}

AuthorizationPolicy

L7 access control. action: ALLOW (default), DENY (precedence), AUDIT, CUSTOM (ext authz). Empty rules: blocks/allows everything (depending on action). Multiple policies combine AND within, OR across policies.

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: {name: reviews-allow, namespace: prod}
spec:
  selector: {matchLabels: {app: reviews}}
  action: ALLOW
  rules:
  - from:
    - source:
        principals:
        - cluster.local/ns/prod/sa/productpage
    to:
    - operation:
        methods: [GET]
        paths: ["/reviews/*"]
    when:
    - key: request.auth.claims[groups]
      values: ["reader", "admin"]

RequestAuthentication (JWT)

Validates the JWT and fills request.auth.* for the AuthorizationPolicy. Important: without an additional DENY policy with notRequestPrincipals=["*"], unauthenticated requests can still get through.

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata: {name: jwt, namespace: prod}
spec:
  selector: {matchLabels: {app: api}}
  jwtRules:
  - issuer: "https://auth.example.com"
    jwksUri: "https://auth.example.com/jwks"
    audiences: ["api.example.com"]
    forwardOriginalToken: true

Identity

SPIFFE URI spiffe://<trust>/ns/<ns>/sa/<sa>. Trust domain defaults to cluster.local; in multi-cluster it must be unique per cluster. Workload cert rotation every 24 h, automatic via Citadel.

Observability

Telemetry API

Metrics/logs/traces per workload or namespace. Replaces EnvoyFilter telemetry mods and the old values.telemetry.v2.* settings.

apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata: {name: trace-prod, namespace: prod}
spec:
  tracing:
  - providers: [{name: tempo}]
    randomSamplingPercentage: 5.0
  metrics:
  - providers: [{name: prometheus}]
    overrides:
    - match: {metric: REQUEST_COUNT}
      tagOverrides:
        request_protocol: {operation: REMOVE}
  accessLogging:
  - providers: [{name: otel}]
    filter:
      expression: "response.code >= 400"

Metrics & tracing

RED counter: istio_requests_total, latency: istio_request_duration_milliseconds_bucket. The reporter label = source/destination — in dashboards always aggregate on destination (otherwise double counting).

Istio propagates B3/W3C, it does not generate them. The app must re-set incoming trace headers (x-request-id, x-b3-*, traceparent) on the way out.

Performance & Tuning

Sidecar sizing

Default 100m CPU / 128Mi RAM: not production-grade at high RPS. Rule of thumb: 1 mCore per 100 RPS, +50 MiB per 1000 endpoints in the registry. Sidecar resource: trims the registry → typically -70 % memory.

Pilot tuning

PILOT_PUSH_THROTTLE: default 100. PILOT_DEBOUNCE_AFTER: default 100 ms. PILOT_DEBOUNCE_MAX: default 10 s. On push storms (many pod restarts) raise debounce; raise throttle for faster convergence above > 5000 workloads.

Ambient mode (1.30 GA)

No more sidecar injection. ztunnel: node DaemonSet, L4 mTLS. Waypoint proxy: optional, namespace-scoped, L7. Opt-in: label istio.io/dataplane-mode=ambient per namespace. Saves RAM at high pod counts, costs complexity when debugging.

Jobs & CronJobs (sidecar lifecycle)

App before proxy ready → connection refused. App done, sidecar still running → the job hangs.

Native sidecar (K8s 1.33+ GA, Istio 1.19+): proxy as an initContainer (restartPolicy: Always), proper lifecycle. Mesh-wide via istiod env ENABLE_NATIVE_SIDECARS=true.

Per pod (Istio 1.24+, takes precedence over the mesh flag): annotation sidecar.istio.io/nativeSidecar: "true" in the pod template ("false" forces the classic sidecar).

Pre-native: holdApplicationUntilProxyStarts against the start race, trap with POST :15020/quitquitquit on EXIT against shutdown hangs (fires on crash/signal too, not only on success).

# Native per pod (Istio 1.24+, beats mesh flag): pod template
metadata:
  annotations:
    sidecar.istio.io/nativeSidecar: "true"
---
# Pre-native fallback (without native sidecars):
metadata:
  annotations:
    proxy.istio.io/config: |                 # against start race
      { "holdApplicationUntilProxyStarts": true }
spec:
  containers:
  - name: worker
    command: ["/bin/sh","-c"]
    args:
    - |
      trap 'curl -fsS -XPOST localhost:15020/quitquitquit||true' EXIT
      ./run-task

Graceful drain: EXIT_ON_ZERO_ACTIVE_CONNECTIONS

On SIGTERM the sidecar only drains for terminationDrainDuration (default 5 s), then hard-exits — long-lived connections (gRPC streams, WebSockets, DB pools) are cut mid-flight (503/reset on rollout/scale-down).

Fix: EXIT_ON_ZERO_ACTIVE_CONNECTIONS=true (via proxyMetadata or proxy.istio.io/config) — pilot-agent polls active Envoy connections and stops the proxy as soon as they reach 0 instead of on a fixed timer.

Caveat: if a connection hangs (client never closes), the proxy blocks until terminationGracePeriodSeconds → SIGKILL. Raise the grace period accordingly. Mesh-wide via meshConfig.defaultConfig.proxyMetadata.

Multi-Cluster

Topologies & setup

Primary-remote: one istiod, several workload clusters. Multi-primary: istiod per cluster, shared root CA. External control plane: istiod on the outside.

Required: shared trustDomain (or trustDomainAliases), a network label per cluster, endpoint discovery via istioctl create-remote-secret.

istioctl create-remote-secret \
  --context=cluster-b \
  --name=cluster-b \
  | kubectl apply --context=cluster-a -f -

Diagnostics

First istioctl tool during an incident

istioctl proxy-status shows the sync state of every sidecar. SYNCED = config current, STALE = push in progress, NOT SENT = istiod sent nothing → check the Pilot logs.

istioctl proxy-status
istioctl proxy-config routes <pod>.<ns> -o json
istioctl proxy-config clusters <pod>.<ns>
istioctl proxy-config listeners <pod>.<ns>
istioctl proxy-config endpoints <pod>.<ns>
istioctl proxy-config secrets <pod>.<ns>

# Static lint: VirtualService/DR conflicts etc.
istioctl analyze -n prod

# Full bug report incl. config + logs (anonymised)
istioctl bug-report

Live log of a sidecar

istioctl proxy-config log <pod> --level debug sets the log level live without a pod restart. Component-specific, e.g. --level rbac:debug,jwt:debug. After diagnosis, set it back to warning.

Common failure modes

503 UC: upstream connect fail, app port does not match the service target.
503 NR: no route, the VirtualService does not match (wrong host match).
503 UF: upstream failure, TLS mismatch (PeerAuth STRICT vs. a client without a sidecar).
404: path match missed, check the order of the http[] rules.

OUTPUT_CERTS scraping: read: connection reset by peer

App-originated mTLS (Prometheus/Alloy federation via ISTIO_META_OUTPUT_CERTS + /etc/istio-certs/{cert-chain,key,root-cert}.pem) breaks with an RST even though the target PeerAuthentication is PERMISSIVE and allow-all matches.

Almost always the cause: the client sidecar wraps the app mTLS in a second ISTIO_MUTUAL tunnel. A DR with host: "*.local" + exportTo: ["*"] (a typical STRICT migration helper in istio-system) matches direct pod-IP calls too via EndpointSlice lookup. Result: TLS-in-TLS. Target 15006 terminates only the outer layer; the inner TLS bytes land as plain text on the app port, the backend expects HTTP and closes.

PERMISSIVE is an inbound decision after transport termination — it heals nothing that happens outbound.

Fix: excludeOutboundPorts on the scraper pod (port-scoped) or a dedicated DR with tls.mode: DISABLE on the target service (more specific than *.local, so it wins).

# does the client sidecar wrap? alpn=istio* + sni=outbound_ = proof:
istioctl pc cluster <pod>.<ns> --fqdn <target> -o json \
  | jq '.[].transport_socket.typed_config|{sni,alpn_protocols}'
# which *.local DR matches?
kubectl get dr -A -o json | jq -r '.items[]|
  select(.spec.host|test("\\*\\.local"))|
  .metadata.namespace+"/"+.metadata.name'

Gateway API (v1, kubernetes-sigs)

Status in 1.30

Istio implements Gateway API v1 (Gateway, HTTPRoute, GRPCRoute) as an equal alternative to Gateway/VirtualService. New projects: Gateway API. Existing setups: networking.istio.io stays supported, the two can coexist.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: web, namespace: istio-system}
spec:
  gatewayClassName: istio
  listeners:
  - name: https
    hostname: www.istio-quickref.de
    port: 443
    protocol: HTTPS
    tls:
      certificateRefs:
      - {name: web-cert}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: site, namespace: web}
spec:
  parentRefs: [{name: web, namespace: istio-system}]
  hostnames: ["www.istio-quickref.de"]
  rules:
  - matches: [{path: {type: PathPrefix, value: /}}]
    backendRefs: [{name: nginx, port: 80}]

Anti-Patterns

What you should not do

Default sidecar resources in prod: OOM, push throttle.
Mesh VirtualService without a Sidecar resource: every sidecar gets every rule, push storm.
STRICT without migration: non-injected workloads break (jobs, external health checks).
EnvoyFilter as the default tool: reach for the Telemetry API, WasmPlugin, AuthorizationPolicy first. EnvoyFilter breaks between minors.
Tracing without app header propagation: spans tear apart.
Multi-cluster without a shared root CA: mTLS fails.
OUTPUT_CERTS without excludeOutboundPorts: the client sidecar wraps app mTLS twice (TLS-in-TLS) — read: connection reset by peer despite a PERMISSIVE PA.

License & redistribution

CC BY-SA 4.0. You may copy, redistribute, print and quote this cheatsheet in your own materials. Condition: the attribution „Istio Quick Reference — OMNI52 GmbH, istio-quickref.de“ stays visible, and derivative works are licensed under the same terms (share-alike).

Not allowed: using the logo or trademarks, or creating the impression that the content originates from you or that OMNI52 GmbH sponsors the reuse.

Full license text: creativecommons.org/licenses/by-sa/4.0/.

Related sheets in the same dense senior quality:
istio-cheatsheet.de — Istio in depth (German)
istio-spickzettel.de — Istio cheatsheet (German)
service-mesh-cheatsheet.de — Istio + Linkerd + Cilium compared
kubernetes-cheatsheet.de — Kubernetes Core

Istio is a trademark of The Linux Foundation. OMNI52™ is a trademark of OMNI52 GmbH (filed, not yet registered). This website is operated by OMNI52 GmbH and is not affiliated with, endorsed by, or sponsored by The Linux Foundation, the CNCF, or the Istio project. “Istio” is used in a nominative/descriptive sense to indicate the technology this reference describes.