#!/usr/bin/env bash # T16: connectionPool.tcp.idleTimeout cuts an idle connection despite tcpKeepalive being active/successful. # # NOTE on parameters vs the original spec: the shared mock.istio-verify-ext backend # (mendhak/http-https-echo, Node.js/Express) closes idle upstream connections itself after # ~5-6s (Node's default http.Server keepAliveTimeout=5000ms + ~1s headers grace). The spec's # original idleTimeout=20s / tcpKeepalive(time=3s,interval=2s,probes=10) / hold=30s can NEVER # observe Envoy's own idle_timeout firing, because the backend kills the connection first # (confirmed empirically: upstream_cx_destroy_remote incremented, upstream_cx_length_ms ~6050ms, # upstream_cx_idle_timeout stayed 0). This script uses idleTimeout=4s / tcpKeepalive(time=1s, # interval=1s,probes=10) / hold=10s instead -- comfortably below the backend's ~6s ceiling -- # so Envoy's own idle timer gets the chance to fire and be observed. set -euo pipefail NS="istio-vt-t16" DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cleanup() { echo "[cleanup] deleting namespace ${NS}" kubectl delete namespace "${NS}" --wait=false --ignore-not-found } trap cleanup EXIT echo "[setup] creating namespace ${NS}" kubectl create namespace "${NS}" --dry-run=client -o yaml | kubectl apply -f - kubectl label namespace "${NS}" istio-injection=enabled --overwrite echo "[apply] applying manifest.yaml" kubectl apply -f "${DIR}/manifest.yaml" echo "[wait] waiting for client pod Ready (sidecar-injected, must reach 2/2)" kubectl -n "${NS}" wait --for=condition=Ready pod/client --timeout=120s echo "[wait] waiting for echo deployment rollout" kubectl -n "${NS}" rollout status deploy/echo --timeout=120s echo "[observe] cluster config confirms idleTimeout/tcpKeepalive mapping" kubectl -n "${NS}" exec client -c istio-proxy -- curl -s localhost:15000/config_dump \ | grep -A2 'idle_timeout\|tcp_keepalive' || true echo "[observe] before_idle: upstream_cx_idle_timeout baseline" kubectl -n "${NS}" exec client -c istio-proxy -- curl -s localhost:15000/stats \ | grep 'outbound|80||mock.istio-verify-ext.svc.homelab.local.*upstream_cx_idle_timeout' \ | tee /tmp/before_idle.txt echo "[observe] opening idle TCP connection with keep-alive header, holding idle for 10s (background)" kubectl -n "${NS}" exec client -c curl -- sh -c \ '(printf "GET / HTTP/1.1\r\nHost: mock.istio-verify-ext.svc.homelab.local\r\nConnection: keep-alive\r\n\r\n"; sleep 10) | nc mock.istio-verify-ext.svc.homelab.local 80 > /tmp/t16_nc_out.txt 2>&1' & BGPID=$! echo "[observe] sleeping 15s (idleTimeout=4s window + margin)" sleep 15 wait "${BGPID}" || true echo "[observe] background connection output (from inside client pod)" kubectl -n "${NS}" exec client -c curl -- cat /tmp/t16_nc_out.txt || true echo "[observe] after_idle: upstream_cx_idle_timeout after hold" kubectl -n "${NS}" exec client -c istio-proxy -- curl -s localhost:15000/stats \ | grep 'outbound|80||mock.istio-verify-ext.svc.homelab.local.*upstream_cx_idle_timeout' \ | tee /tmp/after_idle.txt echo "[observe] supporting stats: destroy_local / destroy_remote / total / length_ms" kubectl -n "${NS}" exec client -c istio-proxy -- curl -s localhost:15000/stats \ | grep -E 'outbound\|80\|\|mock.istio-verify-ext.svc.homelab.local;\.(upstream_cx_destroy_local:|upstream_cx_destroy_remote:|upstream_cx_total:|upstream_cx_length_ms)' echo "[observe] istio-proxy access logs (last 1m)" kubectl -n "${NS}" logs client -c istio-proxy --since=1m | tail -20 echo "[result] before vs after:" echo " before: $(cat /tmp/before_idle.txt)" echo " after: $(cat /tmp/after_idle.txt)"