#!/usr/bin/env bash # T13: DestinationRule.trafficPolicy field -> Envoy Cluster JSON location mapping # # NOTE: harness-notes.md says the cluster DNS domain is homelab.local (NOT cluster.local). # That is true for actual DNS resolution (kubeadm clusterDomain / CoreDNS Corefile = homelab.local). # However istiod's own --domain flag in this cluster is hardcoded to "cluster.local" # (kubectl -n istio-system get deploy istiod -o jsonpath='{.spec.template.spec.containers[0].args}'), # independent of the k8s cluster's actual clusterDomain. Istio's internal service registry / xDS # naming for in-mesh k8s Services therefore uses *.svc.cluster.local regardless of the real DNS # domain. The DestinationRule host below must match Istio's registry domain (cluster.local), not # the k8s DNS domain, or istioctl analyze raises IST0174 "does not match any services in the mesh" # and the trafficPolicy never applies. set -euo pipefail NS=istio-vt-t13 SCRIPT_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} with istio-injection=enabled" kubectl create namespace "${NS}" kubectl label namespace "${NS}" istio-injection=enabled --overwrite echo "[apply] applying manifest.yaml" kubectl apply -f "${SCRIPT_DIR}/manifest.yaml" echo "[wait] waiting for client pod Ready" kubectl -n "${NS}" wait --for=condition=Ready pod/client --timeout=90s echo "[wait] waiting for echo deployment rollout" kubectl -n "${NS}" rollout status deployment/echo --timeout=90s sleep 5 echo "=== circuitBreakers.thresholds (expect maxConnections=77, maxPendingRequests=42, maxRequests=88) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].circuitBreakers.thresholds[0] | {maxConnections, maxPendingRequests, maxRequests}' echo "=== upstreamConnectionOptions.tcpKeepalive (expect time=60/interval=5/probes=3) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].upstreamConnectionOptions.tcpKeepalive' echo "=== maxRequestsPerConnection at legacy top-level path (expect null - moved location) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].maxRequestsPerConnection' echo "=== maxRequestsPerConnection actual location: typedExtensionProtocolOptions (expect 5) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].typedExtensionProtocolOptions."envoy.extensions.upstreams.http.v3.HttpProtocolOptions".commonHttpProtocolOptions.maxRequestsPerConnection' echo "=== lbPolicy (expect LEAST_REQUEST) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].lbPolicy' echo "=== outlierDetection (expect consecutive5xx=4, interval=9s, baseEjectionTime=33s) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '.[0].outlierDetection' echo "=== cluster count matching fqdn (expect 2: one per Service port 80/443, NOT DR field-splitting) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json | jq 'length' echo "=== per-cluster field co-location check (both port-80 and port-443 clusters should carry ALL fields together) ===" istioctl proxy-config cluster client."${NS}" --fqdn echo."${NS}".svc.cluster.local -o json \ | jq '[.[] | {name, lbPolicy, circuitBreakers: .circuitBreakers.thresholds[0], tcpKeepalive: .upstreamConnectionOptions.tcpKeepalive, outlierDetection: (.outlierDetection != null)}]'