#!/usr/bin/env bash # T62 - Envoy Admin API ground-truth vs istioctl view, 15000 vs 15021 port separation. set -euo pipefail NS=istio-vt-t62 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] create namespace $NS with istio-injection=enabled" kubectl create namespace "$NS" kubectl label namespace "$NS" istio-injection=enabled echo "[setup] apply manifest.yaml" kubectl apply -f "$DIR/manifest.yaml" kubectl -n "$NS" wait --for=condition=Ready pod/client --timeout=90s kubectl -n "$NS" rollout status deploy/echo --timeout=90s echo "[test] scale echo to 0 replicas, expect CDS-alive / EDS-empty -> 503" kubectl -n "$NS" scale deploy/echo --replicas=0 sleep 5 kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null -w 'during_zero_replicas=%{http_code}\n' \ "http://echo.$NS.svc.homelab.local/" echo "[note] the FQDN above (svc.homelab.local) does not match Istio's RDS vhost domains" \ "(which default to svc.cluster.local regardless of the real kubelet --cluster-domain)," \ "so this specific request is routed via PassthroughCluster (UF), not the named echo" \ "cluster's EDS path. Re-test with the short name, which DOES match RDS and exercises the" \ "intended CDS-alive/EDS-empty -> 503 UH path:" kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null -w 'short_name_during_zero=%{http_code}\n' \ "http://echo/" echo "[observe] config_dump still has the cluster (CDS retained) while /clusters shows 0 endpoint hosts (EDS emptied)" kubectl -n "$NS" exec client -c istio-proxy -- \ curl -s 'localhost:15000/config_dump?resource=dynamic_active_clusters' \ | grep -c "outbound|80||echo.$NS.svc.cluster.local" kubectl -n "$NS" exec client -c istio-proxy -- curl -s localhost:15000/clusters \ | grep "outbound|80||echo.$NS.svc.cluster.local" || true echo "[note] the raw Envoy stat 'upstream_cx_none_healthy' for arbitrary outbound clusters is" \ "excluded from GET /stats by Istio's default stats_matcher inclusion_list (only" \ "cluster.xds-grpc plus cluster_manager/listener_manager/server/wasm/istio* prefixes are" \ "allowed). The signal that DOES show up is the istio_requests_total custom metric tagged" \ "response_flags=UH:" kubectl -n "$NS" exec client -c istio-proxy -- curl -s localhost:15000/stats \ | grep "istio_requests_total.*destination_service_name.echo\." || true echo "[test] scale echo back to 1, expect recovery to 200" kubectl -n "$NS" scale deploy/echo --replicas=1 kubectl -n "$NS" rollout status deploy/echo --timeout=60s sleep 5 kubectl -n "$NS" exec client -c curl -- curl -s -o /dev/null -w 'after_recovery=%{http_code}\n' \ "http://echo.$NS.svc.homelab.local/" echo "[test] istioctl proxy-config cluster vs raw config_dump cluster set (compare as SETS," \ "since istioctl includes bootstrap static_clusters that a resource=dynamic_active_clusters" \ "filter deliberately excludes)" comm -3 \ <(istioctl proxy-config cluster "client.$NS" -o json | jq -r '.[].name' | sort -u) \ <(kubectl -n "$NS" exec client -c istio-proxy -- curl -s 'localhost:15000/config_dump' \ | jq -r '.configs[] | select(."@type" | endswith("ClustersConfigDump")) | (.static_clusters[]?.cluster.name, .dynamic_active_clusters[]?.cluster.name)' \ | sort -u) \ || echo "no set difference" echo "[test] admin API (15000) is loopback-only: reachable at localhost, not via Pod IP" kubectl -n "$NS" exec client -c istio-proxy -- ss -tln | grep -E "15000|15021|15001|15006|15090" kubectl -n "$NS" exec client -c istio-proxy -- \ curl -s -o /dev/null -w 'localhost_admin=%{http_code}\n' localhost:15000/stats CLIENT_IP=$(kubectl -n "$NS" get pod client -o jsonpath='{.status.podIP}') kubectl -n "$NS" exec deploy/echo -c istio-proxy -- \ curl -s -o /dev/null -w 'crosspod_admin_attempt=%{http_code}\n' --max-time 3 \ "http://$CLIENT_IP:15000/stats" || true echo "[test] 15000 (admin) vs 15021 (health) are distinct port/endpoint sets" kubectl -n "$NS" exec client -c istio-proxy -- \ curl -s -o /dev/null -w 'port_15021=%{http_code}\n' localhost:15021/healthz/ready kubectl -n "$NS" exec client -c istio-proxy -- curl -s localhost:15000/help | grep -c drain_listeners kubectl -n "$NS" exec client -c istio-proxy -- \ curl -s -o /dev/null -w 'admin_op_on_15021=%{http_code}\n' localhost:15021/clusters echo "[done] T62 run complete"