Istio Cheatsheet

Istio CheatsheetAlle Kapitel

Istio Cheatsheet im Volltext

Stand Istio 1.31 · Edition 2026.10

Cheatsheet im Volltext

Derselbe Inhalt wie im PDF, zum Mitlesen, Durchsuchen und direkten Kopieren der YAML-Snippets. Stand: Istio 1.31 (v1.31 · 2026.10).

Architektur

Datapath & Komponenten

Client → Ingress GW → Sidecar(src) → Sidecar(dst) → App. mTLS-Handshake nur beim Connect.

istiod: Pilot/Citadel/Galley vereint, xDS-Push + CA. istio-proxy: Envoy-Sidecar (Mutating Webhook) oder Ambient ztunnel. Gateway: dedizierte Envoys, Deployment + LB-Service.

Envoy-Version = Istio-Version + 8. Istio 1.31 → Envoy 1.39.

Install-Profile

default (prod), minimal (nur istiod), ambient (sidecar-less), remote (MC-Workload-Cluster), demo (laut). Canary via --revision=1-31-1, Namespaces hängen an einem Revision-Tag (istio.io/rev=prod-stable).

istioctl x precheck
istioctl install --set profile=default --revision=1-31-1
istioctl tag set prod-stable --revision 1-31-1 --overwrite

Traffic Management

Gateway

Bindet Ports/Hosts/TLS am Edge-Proxy. servers[].port + tls.mode: SIMPLE, MUTUAL, PASSTHROUGH, ISTIO_MUTUAL. credentialName verweist auf Secret im selben Namespace wie der 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-cheatsheet.de"]
    tls:
      mode: SIMPLE
      credentialName: web-cert

VirtualService

Routing-Regeln. Bindet hosts an gateways (oder mesh für Ost-West). Match-Reihenfolge ist signifikant, erste Treffer-Regel gewinnt. Header-Keys lowercase, Header-Werte und Pfad case-sensitive (Pfad: ignoreUriCase: true).

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 (Versionen) + Traffic-Policy (LB, Connection-Pool, Outlier Detection, TLS). Wirkt erst nach VirtualService-Routing. host muss FQDN oder Short-Name im selben Namespace sein.

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

Externe Hosts in den Mesh-Registry holen (DB, SaaS, REST-APIs). MESH_EXTERNAL + DNS resolution sind die saubere Default-Kombi. Ohne ServiceEntry bleibt der Egress-Traffic BlackHoleCluster wenn 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

Begrenzt, was ein Workload aus der Mesh-Registry sieht, kritisch für Memory-Footprint und Push-Latenz in großen Clustern. Default: jeder Sidecar bekommt Config für alle Services. Ab 1.31 zieht ein ~-Präfix Namespaces ab: */* + ~ns1/*.

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

Security

PeerAuthentication

mTLS-Modus zwischen Sidecars. STRICT, PERMISSIVE, DISABLE. Geltung: Mesh (in istio-system), Namespace, oder Workload via selector. Migration zu STRICT: zuerst Mesh-weit PERMISSIVE, dann namespace-weise umstellen.

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: default, namespace: prod}
spec:
  mtls: {mode: STRICT}
---
# Port-Ausnahme (Container-Port): NLB-Health-Check ohne mTLS
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: {name: legacy, namespace: prod}
spec:
  selector: {matchLabels: {app: legacy}}
  mtls: {mode: STRICT}
  portLevelMtls:
    8080: {mode: PERMISSIVE}

AuthorizationPolicy

L7-Zugriffskontrolle. Auswertung CUSTOM → DENY → ALLOW, AUDIT entscheidet nichts. Gibt es ALLOW-Policies, muss eine matchen, sonst Deny. Ohne rules matcht eine Policy nie: ALLOW ohne rules = deny-all, rules: [{}] = allow-all. Regeln OR, from/to/when je Regel AND.

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)

Validiert JWT, füllt request.auth.* für AuthorizationPolicy. Wichtig: ohne zusätzliche DENY-Policy mit notRequestPrincipals=["*"] können unauthentifizierte Requests weiterhin durch.

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 default cluster.local, im Multi-Cluster-Mesh in allen Clustern gleich (eindeutig ist multiCluster.clusterName). Workload-Cert: 24 h Laufzeit (SECRET_TTL), istio-agent erneuert bei 50 % (SECRET_GRACE_PERIOD_RATIO) per CSR an istiod.

Observability

Telemetry-API

Metriken/Logs/Traces pro Workload oder Namespace. Ersetzt EnvoyFilter-Telemetry-Mods und die alten 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"

Metriken & Tracing

RED-Counter: istio_requests_total, Latency: istio_request_duration_milliseconds_bucket. Label reporter = source/destination, in Dashboards immer auf destination aggregieren (sonst Doppelzählung).

Der Sidecar startet Traces und schreibt Spans, kann aber eingehende und ausgehende Requests der App nicht verknüpfen. App muss eingehende Trace-Header (x-request-id, x-b3-*, traceparent) beim Ausgang weitergeben.

Performance & Tuning

Sidecar-Sizing

Default-Requests 100m / 128Mi (Limits 2000m / 1024Mi): bei hohem RPS zu knapp.
Richtwert (Istio-Benchmark 1.24, 1 KB, 2 Worker, 1000 RPS): Sidecar ≈ 0,2 vCPU + 60 MB, ztunnel ≈ 0,06 vCPU + 12 MB.
Memory wächst mit Listenern/Clustern/Routes, Sidecar-Resource schneidet.

Pilot-Tuning

PILOT_PUSH_THROTTLE: default auto = min(15+5n, 100). PILOT_DEBOUNCE_AFTER: default 100 ms. PILOT_DEBOUNCE_MAX: default 10 s. Bei Push-Storms (viele Pod-Restarts) Debounce hochziehen. Das n im Throttle-Default ist GOMAXPROCS von istiod, im Chart aus dem CPU-Limit (ohne Limit: Node-CPUs). Mehr CPU für istiod oder den Wert explizit setzen.

Ambient Mode (GA seit 1.24)

Kein Sidecar-Inject. ztunnel: Node-DaemonSet, L4 mTLS (HBONE). Waypoint: optional, L7, per istio.io/use-waypoint an Namespace, Service oder Pod; Policies dort per targetRefs, selector-Policies ignoriert der Waypoint. Opt-in: Label istio.io/dataplane-mode=ambient je Namespace oder Pod. Spart RAM bei vielen Pods, kostet Komplexität beim Debugging.

Jobs & CronJobs (Sidecar-Lifecycle)

App vor proxy ready → Connect-Refused. App fertig, Sidecar läuft → Job hängt.

Native Sidecar (GA ab K8s 1.33): Proxy läuft als initContainer mit restartPolicy: Always, sauberer Lifecycle. Default seit Istio 1.27: ENABLE_NATIVE_SIDECARS=auto injiziert nativ, wenn alle Nodes mindestens Kubelet 1.33 melden. Ein älterer Node schaltet es clusterweit ab. Per Pod (Vorrang): sidecar.istio.io/nativeSidecar: "true"|"false".

Pre-Native: holdApplicationUntilProxyStarts gegen Start-Race, trap mit POST :15020/quitquitquit auf EXIT gegen Shutdown-Hänger (feuert auch bei Crash/Signal, nicht nur bei Success).

# Pre-Native-Fallback: Start-Race + Shutdown-Hänger
metadata:
  annotations:
    proxy.istio.io/config: |
      { "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

Klassischer Sidecar: bei SIGTERM drainiert er nur terminationDrainDuration (Default 5 s), dann Hard-Exit, lang lebende Verbindungen (gRPC-Streams, WebSockets, DB-Pools) werden gekappt (503/Reset beim Rollout/Scale-Down). Native Sidecar (Default seit 1.27): preStop startet den Drain, SIGTERM kommt erst nach Ende der App-Container.

Fix: EXIT_ON_ZERO_ACTIVE_CONNECTIONS=true (via proxyMetadata bzw. proxy.istio.io/config): pilot-agent pollt aktive Envoy-Connections und beendet den Proxy sobald sie 0 erreichen statt nach fixem Timer.

Caveat: hängt eine Verbindung (Client schließt nie), blockt der Proxy bis terminationGracePeriodSeconds → SIGKILL. Grace-Period entsprechend hochsetzen. Mesh-weit via meshConfig.defaultConfig.proxyMetadata.

Multi-Cluster

Topologien & Setup

Primary-Remote: ein istiod, mehrere Workload-Cluster. Multi-Primary: istiod pro Cluster, gemeinsame Root-CA. External Control-Plane: istiod außerhalb.

Pflicht: gemeinsame Root-CA und trustDomain, gleiche meshID, eindeutiger multiCluster.clusterName. network im flachen Netz gleich, bei Multi-Network je Netz verschieden (plus East-West-Gateway). Endpoint-Discovery via istioctl create-remote-secret.

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

Diagnose

Erstes istioctl-Werkzeug bei Vorfall

istioctl proxy-status zeigt Sync-Stand jedes Sidecars. SYNCED = ACK erhalten, NOT SENT = nichts zu senden (meist normal), STALE = gesendet, kein ACK → Netz istiod/Proxy oder Bug, ERROR = NACK → Config prüfen.

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>

# Statisches Lint: VirtualService/DR-Konflikte etc.
istioctl analyze -n prod

# Bug-Report: Konfig + Logs,
# Secret-Inhalte nur mit --full-secrets
istioctl bug-report

Live-Log eines Sidecars

istioctl proxy-config log <pod> --level debug setzt das Log-Level live ohne Pod-Restart. Komponenten-spezifisch z.B. --level rbac:debug,jwt:debug. Nach Diagnose zurück auf warning.

Häufige Fehlerbilder

404 NR: keine Route, Host- oder Path-Match greift nicht, Reihenfolge der http[] prüfen.
503 UF: Connect zum Upstream scheitert, App-Port ≠ targetPort oder TLS-Mismatch (DR DISABLE gegen STRICT).
503 UC: Upstream schließt bestehende Verbindung, oft Idle-Timeout der App kürzer als Envoy-Pool.
503 UH: kein gesunder Endpoint (Readiness, Outlier Detection).

OUTPUT_CERTS-Scraping: read: connection reset by peer

App-originated mTLS (Prometheus/Alloy-Federate via ISTIO_META_OUTPUT_CERTS + /etc/istio-certs/{cert-chain,key,root-cert}.pem) bricht mit RST, obwohl Ziel-PeerAuthentication PERMISSIVE ist und Allow-all greift.

Ursache fast immer: Client-Sidecar wrapt den App-mTLS in einen zweiten ISTIO_MUTUAL-Tunnel. Eine DR mit host: "*.local" + exportTo: ["*"] (typisch STRICT-Migration-Helper in istio-system) matcht via EndpointSlice-Lookup auch direkte Pod-IP-Calls. Ergebnis: TLS-in-TLS. Ziel-15006 terminiert nur outer; inner TLS-Bytes landen als Klartext auf dem App-Port, Backend erwartet HTTP, schließt.

PERMISSIVE ist Inbound-Entscheidung nach der Transport-Terminierung und heilt nichts, was outbound passiert.

Fix: excludeOutboundPorts am Scraper-Pod (port-scoped) oder eigene DR mit tls.mode: DISABLE auf den Ziel-Service (spezifischer als *.local, gewinnt).

# wraps der Client-Sidecar?
# alpn=istio* + sni=outbound_ = Beweis:
istioctl pc cluster <pod>.<ns> --fqdn <ziel> -o json \
  | jq '.[]|(.transportSocket,
    .transportSocketMatches[]?.transportSocket)
    |select(.).typedConfig
    |{sni,alpn:.commonTlsContext.alpnProtocols}'
# welche *.local-DR greift?
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.31

Istio 1.31 baut auf Gateway API v1.6 (HTTPRoute, GRPCRoute, TLSRoute, ListenerSet, BackendTLSPolicy, TCPRoute jetzt v1). CRDs vor dem Istio-Upgrade einspielen: zu alte CRDs ignoriert istiod still (TLSRoute < v1.5, TCPRoute < v1.6), istioctl analyze meldet IST0176. Neue Projekte: Gateway API, networking.istio.io bleibt supportet.

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

Release & Support

Kadenz & Support-Fenster

Rund ein Minor pro Quartal. Ein Minor wird bis sechs Wochen nach dem N+2-Release gepflegt, danach keine Backports mehr, auch nicht für Security-Issues.

1.31: seit 31.08.2026, zuletzt 1.31.1 (21.09.2026), K8s 1.32–1.37.
1.30: supportet bis sechs Wochen nach 1.32, zuletzt 1.30.5 (21.09.2026).
1.29: EOL am 12.10.2026, zuletzt 1.29.8.

Praktisch: mehr als zwei Minors Rückstand heißt ungepatchte CVEs im Datenpfad. In-place nur Minor für Minor, per Revision (Canary) bis zu zwei Minors in einem Schritt (1.29 → 1.31). compatibilityVersion=1.30 führt Istio-Gateways aus anderen Namespaces wieder in Gateway-API-Gateways zusammen und schickt Unhealthy-Endpoints nicht mehr per EDS.

Registry: 1.30 zieht per Default von registry.istio.io/release, also hub: docker.io/istio oder Pull-Through-Cache setzen. Ab 1.31 Images nur auf docker.io/istio. Charts aller Versionen liegen auf blob.istio.io/istio-release/charts bzw. ghcr.io/istio/release/charts. gcr.io/istio-release, registry.istio.io und istio-release.storage.googleapis.com gehen im Dez. 2026 vom Netz, Scream-Tests 13.10., 17.11., 08.–09.12.2026. Signaturprüfung ab 1.31.1 mit istio-key-v2.pub.

Anti-Patterns

Was du nicht tun solltest

Default-Sidecar-Requests in Prod: 100m/128Mi überbuchen Nodes, ohne Sidecar-Scoping droht OOM am 1024Mi-Limit.
Mesh-VirtualService ohne Sidecar-Resource: jeder Sidecar bekommt jede Regel, Push-Storm.
STRICT ohne Migration: nicht injizierte Workloads brechen (Jobs, externe Health-Checks).
EnvoyFilter als Default-Tool: erst Telemetry-API, AuthorizationPolicy, TrafficExtension (Wasm/Lua, alpha seit 1.30). EnvoyFilter bricht zwischen Minors.
Tracing ohne App-Header-Propagation: Spans zerreißen.
Multi-Cluster ohne gemeinsame Root-CA: mTLS scheitert.