Revision-Based Canary Upgrade Breaks One Namespace
Your platform team operates Istio 1.17.3 installed with istioctl using the default profile: three istiod replicas, an istio-ingressgateway with an HPA (min 3, max 12), 240 workloads across 12 namespaces, all injected through the namespace label istio-injection=enabled. A single shared EnvoyFilter lives in istio-system and adds a custom header to every inbound listener at the HTTP_FILTER stage. You must reach 1.20.1 with zero failed requests, and security wants 1.17 fully removed within seven days. You choose a revision-based canary install, run istioctl install --revision=1-20-1 --set revision=1-20-1, and relabel only the payments namespace with istio.io/rev=1-20-1. New payments pods start with 1.20 sidecars. Within minutes, 6% of calls into payments return 503 with response flag UF, while payments pods still running the 1.17 sidecar serve fine. istioctl proxy-status shows the new payments proxies as STALE.
1. Was `istioctl install --revision=1-20-1` enough to stand up a usable canary control plane? What must be verified or done before relabeling any namespace, and what does relabeling one namespace actually change?
Read the worked answer
The install creates a second control plane, not a replacement. Concretely it produces istiod-1-20-1, a new istio-sidecar-injector-1-20-1 webhook, and revision-suffixed gateway Deployments; the original istiod and istio-ingressgateway are untouched. Before relabeling, verify:
- Both revisions are healthy:
kubectl get pods -n istio-systemandistioctl proxy-statusshould list both istiods, with every proxy SYNCED. - CRDs are shared: they are cluster-scoped, and the new install upgraded them for both control planes. Confirm the old revision still reconciles cleanly (watch istiod logs for schema errors) before sending it new work.
- Trust domain / CA: both revisions must mount the same
cacertsso 1.17 and 1.20 proxies can do mTLS with each other. Cross-revision mTLS failures look like 503s, so check this early. - Ingress path: traffic still enters through the 1.17 gateway. Decide deliberately whether to cut the ingress gateway to the new revision early (good: small blast radius validates 1.20 end to end) or keep it on 1.17 until every namespace migrates.
- Default tag:
istioctl tag liststill pointsdefaultat 1.17, soistio-injection=enablednamespaces keep getting 1.17 sidecars. Onlyistio.io/rev=1-20-1selects the new revision.
Relabeling payments changes only which injector webhook handles new pods there. Existing pods keep their old sidecar until restarted, so you get a mixed data plane in one namespace - which is precisely why a shared cluster-scoped config object can break only part of it.
2. Diagnose the 503 UF and the STALE status. What is the most likely mechanism given what is shared between the two revisions, what commands confirm it, and how do you restore payments immediately?
Read the worked answer
STALE means the proxy is not acknowledging the newest config version; a 503 with UF on inbound traffic means the listener/route it acknowledged cannot serve the request. The one thing that changed for payments is the Envoy version behind a cluster-wide EnvoyFilter with no workloadSelector.
Likely mechanism: the filter patches HTTP_FILTER on every inbound listener using a schema or filter name that 1.17's Envoy accepted but 1.20's Envoy rejects. The new listeners are rejected (NACK), the proxy keeps the previous good snapshot for some listeners, and inbound requests to payments hit an incomplete listener chain → 503 UF. Because the filter is cluster-scoped, only the new-version proxies are affected.
Confirm in this order:
istioctl proxy-status <payments-pod>.payments- read the version detail, not just the word STALE.kubectl logs <pod> -c istio-proxy | grep -i -E "nack|reject|config"- Envoy logs the rejected resource name.kubectl logs deploy/istiod-1-20-1 -n istio-system | grep -i nack- istiod logs the NACK with the proxy ID.istioctl proxy-config listener <pod> -n payments- the expected filter chain is missing.
Immediate mitigation: move payments back off the new revision. Set istio.io/rev to the old revision (or remove it and restore istio-injection=enabled) and restart the Deployment; injection is per-pod, so a rollback is a rollout. Then fix the EnvoyFilter: correct the typed config for 1.20 and add a workloadSelector so a bad patch can never take the whole mesh again.
3. Walk through the final seven-day removal of 1.17. What evidence do you require before running the uninstall, and what must not be deleted?
Read the worked answer
Removal is a sequence of proofs, not a single command.
- No data plane on the old revision.
istioctl proxy-statusmust contain zero entries whose istiod column is the old revision. Because STALE/leftover entries happen, cross-check withkubectl get pods -A -o jsonand grep theistio-proxyimage tag for 1.17.3. Anything found gets restarted with the new revision label first. - Ingress and egress cut over. The 1.17 gateway Deployment must be replaced by the 1.20 one, and any
Gatewayresources must still resolve. Send real traffic through the new gateway and watch 5xx before deleting the old one. - Tag and label cleanup.
istioctl tag set default --revision 1-20-1 --overwritesoistio-injection=enablednamespaces land on 1.20, then remove staleistio.io/rev=1-17-3labels. Namespaces carryingistio.io/revignoreistio-injection, which is a common cause of "injection silently stopped working" after a migration. - Uninstall the old revision only.
istioctl uninstall --revision 1-17-3removes that revision's Deployments, Services and webhook. Verify withkubectl get deploy,mutatingwebhookconfiguration -n istio-systemthat only 1.20 objects remain.
Do not delete: the CRDs (they are shared and still in use), the istio-system namespace, and the cacerts secret if you plan future revisions. Do not delete the old revision while its proxies are still connected - they lose xDS and behave unpredictably rather than failing cleanly.