Zone-Aware Argo Rollouts
Verification baseline: Istio 1.31.0, Argo Rollouts 1.10.0, Kubernetes 1.32–1.36 Last reviewed: September 11, 2026 Difficulty: Advanced
This guide separates independent zonal canaries from cross-AZ failover. The main example is a zonal routing/isolation blueprint: a client cohort selects one zone's stable/canary Services. It does not implement automatic failover to another zone when those endpoints disappear.
Table of Contents
- Problem Definition
- Architecture Overview
- Key Design Decisions
- Implementation Guide
- Traffic Flow
- Troubleshooting
- Best Practices
Problem Definition
Spot Interruptions and PDBs
Spot capacity can be reclaimed and correlated capacity loss must be planned for. Interruption notices are best effort; stop/terminate notices normally give two minutes, while hibernation starts immediately. EKS managed node groups attempt replacement/rebalancing, but do not guarantee a replacement is Ready before draining an interrupted node.
A PodDisruptionBudget constrains voluntary Eviction API operations. It does not prevent EC2 interruption, node failure, direct Pod deletion or every controller-driven update. Rollouts does not create/manage the PDBs below; the application team defines them and Kubernetes computes their status.
For nine healthy matching Pods with integer minAvailable: 6, the illustrative voluntary-disruption allowance is three. If only six remain healthy, that allowance is zero. This is not a “33% minimum requiring six Pods”: with nine expected Pods, minAvailable: "33%" rounds up to three. Percentage maxUnavailable has different semantics and controller-scale requirements.
Splitting that global budget into minAvailable: 1 per zone changes protection. With one zone gone, the remaining two budgets may allow voluntary evictions down to one healthy Pod each, rather than preserving six globally. This can be useful for independent operations, but it is a different availability/capacity policy. A budget selecting both stable and canary Pods also does not guarantee capacity in each weighted destination separately.
Goals and Constraints
| Design | Independent zonal versions | Cross-AZ failover | Main constraint |
|---|---|---|---|
| One shared revision/subset with endpoints across AZs | No independent per-zone revision control | Possible within the selected endpoint pool | Healthy capacity and compatible release/data state in the other AZ |
| Per-zone Rollouts and zone-filtered Services, shown here | Yes | Not supplied by locality settings | A selected zone's empty pool remains empty |
| Separate health-aware entry routing before zonal gateways/pipelines | Can be designed | Requires its own policy/controller and tests | Extra routing, capacity, identity and recovery coordination |
Do not claim all three properties—strict zonal filtering, independent per-zone hashes and transparent failover—by adding a DestinationRule alone. A separate entry-routing design is outside this deployment blueprint and has not been production-tested here.
Architecture Overview
A common test Service supplies the DNS name. For injected clients, a VirtualService selects a zonal route using an explicit client Pod label. Each route points to that zone's stable/canary Services. A separate Rollout controls that pair's Service hashes and its own named route's weights.
Istiod compiles the configuration into Envoy. VirtualService/DestinationRule objects are not network hops. A non-meshed client can use the common Service's ordinary Kubernetes endpoint selection, bypassing these route rules; routing labels are not a security boundary.
Each zone's Pods are scheduled only in its configured AZ. Consequently, zone A's Services contain no zone B/C fallback endpoints. Independent deployment state also does not eliminate shared control-plane, API-server, network or database dependencies.
Key Design Decisions
1. Route Ownership
Rollouts 1.10 reconciles configured route weights and supported managed destinations; it does not blindly replace every destination array. Different route names avoid competing desired weights. However, three controllers updating one VirtualService can still encounter Kubernetes resourceVersion conflicts and reconciliation delays. Separate objects/entry routing are an option when stronger control-plane isolation is required.
This blueprint uses distinct routes (zone-a-route, zone-b-route, zone-c-route) and host-level splitting. It does not mix Service-hash and DestinationRule-subset ownership. The integration guide explains both alternatives.
2. Client Labels and Actual AZs
sourceLabels selects source workloads when Istiod builds their mesh configuration; it is not a runtime request-header match. Node labels such as topology.kubernetes.io/zone are not automatically copied to Pods. A custom routing.example.com/zone: a Pod label must be paired with verified placement in the intended AZ.
These selectors apply to mesh clients, not generic external requests arriving at an ingress gateway. Preserve the mesh gateway scope. Unknown client cohorts get an explicit 503 response in this example instead of silently selecting another zone.
3. Locality Cannot Escape the Selected Pool
A subset selected by zone: a and a Rollout A hash cannot fail over to a sibling zone B subset. The same applies to a zone-filtered Service. Outlier detection only changes endpoint eligibility inside the selected upstream pool; it does not jump to a different VirtualService route.
Also, localityLbSetting.distribute, failover and failoverPriority are alternatives, not fields to combine freely. The failover.from/to values are regions, not region/zone strings. An A→B→C→A AZ cycle is not established by those entries.
Implementation Guide
Prerequisites
Use the controller, CLI, injection, Prometheus and workload prerequisites from Argo Rollouts Integration. This is an isolated sidecar HTTP demo with default/legacy injection; use the installed revision/tag instead on a revisioned mesh. The lab requires compatible EC2-backed Linux worker nodes, quotas, networking, image access and observability. Fargate is outside this blueprint; choose a currently supported EKS version within the Istio compatibility range.
The example AZ names us-east-1a/b/c must be replaced with actual node labels. AZ name mappings can differ across accounts; verify AZ IDs when coordinating physical zones across accounts. The pinned demo image is Linux amd64 only, so each selected AZ needs compatible nodes or a separately verified replacement image.
kubectl get nodes -L topology.kubernetes.io/region,topology.kubernetes.io/zone,kubernetes.io/arch1. Namespace and Common/Zone Services
apiVersion: v1
kind: Namespace
metadata:
name: zone-rollouts-demo
labels:
istio-injection: enabled
---
apiVersion: v1
kind: Service
metadata:
name: test
namespace: zone-rollouts-demo
spec:
selector:
app: test
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-stable-a
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: a
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-canary-a
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: a
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-stable-b
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: b
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-canary-b
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: b
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-stable-c
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: c
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: test-canary-c
namespace: zone-rollouts-demo
spec:
selector:
app: test
zone: c
ports:
- name: http
port: 8080
targetPort: httpThe common Service is a DNS entry for meshed callers, not an authorization mechanism. The six zonal Services have an actual Pod zone selector; Rollouts adds each stable/canary hash.
2. Client Template Contract
Merge this into an existing Deployment named zone-client-a, retaining its selector, containers, verified image, resources and other placement constraints. Add the a route label to the actual Pod template, not only the Deployment metadata:
metadata:
name: zone-client-a
namespace: zone-rollouts-demo
spec:
template:
metadata:
labels:
routing.example.com/zone: a
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1aPrepare equivalent B/C clients with matching labels and actual AZ constraints when testing them. When combining affinity with existing rules, preserve the intended AND/OR restrictions; do not broaden node eligibility accidentally. Verify .spec.nodeName against the selected Node's zone. The fragment does not deploy a client or copy node metadata automatically.
3. Shared VirtualService with Independent Routes
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: test
namespace: zone-rollouts-demo
spec:
hosts:
- test
- test.zone-rollouts-demo.svc.cluster.local
gateways:
- mesh
http:
- name: zone-a-route
match:
- sourceLabels:
routing.example.com/zone: a
route:
- destination:
host: test-stable-a
port:
number: 8080
weight: 100
- destination:
host: test-canary-a
port:
number: 8080
weight: 0
retries:
attempts: 0
- name: zone-b-route
match:
- sourceLabels:
routing.example.com/zone: b
route:
- destination:
host: test-stable-b
port:
number: 8080
weight: 100
- destination:
host: test-canary-b
port:
number: 8080
weight: 0
retries:
attempts: 0
- name: zone-c-route
match:
- sourceLabels:
routing.example.com/zone: c
route:
- destination:
host: test-stable-c
port:
number: 8080
weight: 100
- destination:
host: test-canary-c
port:
number: 8080
weight: 0
retries:
attempts: 0
- name: unclassified-client
directResponse:
status: 503
body:
string: No reviewed client-zone routeBefore test traffic, verify that each controller has selected the intended hashes and healthy endpoints. Initial weights are 100/0, not an unexplained live 90/10 split. Each named route explicitly disables mesh retries; application retry behavior is separate.
4. Per-zone Rollouts
The workload, replica count, requests/limits and pauses are demo inputs. Three replicas do not automatically spread across three nodes. Review node-level spread/anti-affinity, capacity, PDBs and stable/canary headroom for a real deployment.
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-a
namespace: zone-rollouts-demo
spec:
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: test
zone: a
template:
metadata:
labels:
app: test
zone: a
spec:
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
terminationGracePeriodSeconds: 45
containers:
- name: app
image: argoproj/rollouts-demo@sha256:3225193a6415b14b3fcdd160c40248b2bfd62f8c77326480559b91a41ced6e20
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
strategy:
canary:
stableService: test-stable-a
canaryService: test-canary-a
maxSurge: 1
maxUnavailable: 0
trafficRouting:
istio:
virtualService:
name: test
routes:
- zone-a-route
steps:
- setWeight: 5
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-a
- name: namespace
value: zone-rollouts-demo
- pause: {}
- setWeight: 25
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-a
- name: namespace
value: zone-rollouts-demo
- setWeight: 50
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-a
- name: namespace
value: zone-rollouts-demo
- setWeight: 75
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-a
- name: namespace
value: zone-rollouts-demo
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-b
namespace: zone-rollouts-demo
spec:
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: test
zone: b
template:
metadata:
labels:
app: test
zone: b
spec:
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
terminationGracePeriodSeconds: 45
containers:
- name: app
image: argoproj/rollouts-demo@sha256:3225193a6415b14b3fcdd160c40248b2bfd62f8c77326480559b91a41ced6e20
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1b
strategy:
canary:
stableService: test-stable-b
canaryService: test-canary-b
maxSurge: 1
maxUnavailable: 0
trafficRouting:
istio:
virtualService:
name: test
routes:
- zone-b-route
steps:
- setWeight: 5
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-b
- name: namespace
value: zone-rollouts-demo
- pause: {}
- setWeight: 25
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-b
- name: namespace
value: zone-rollouts-demo
- setWeight: 50
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-b
- name: namespace
value: zone-rollouts-demo
- setWeight: 75
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-b
- name: namespace
value: zone-rollouts-demo
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: test-c
namespace: zone-rollouts-demo
spec:
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: test
zone: c
template:
metadata:
labels:
app: test
zone: c
spec:
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64
terminationGracePeriodSeconds: 45
containers:
- name: app
image: argoproj/rollouts-demo@sha256:3225193a6415b14b3fcdd160c40248b2bfd62f8c77326480559b91a41ced6e20
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1c
strategy:
canary:
stableService: test-stable-c
canaryService: test-canary-c
maxSurge: 1
maxUnavailable: 0
trafficRouting:
istio:
virtualService:
name: test
routes:
- zone-c-route
steps:
- setWeight: 5
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-c
- name: namespace
value: zone-rollouts-demo
- pause: {}
- setWeight: 25
- pause:
duration: 5m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-c
- name: namespace
value: zone-rollouts-demo
- setWeight: 50
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-c
- name: namespace
value: zone-rollouts-demo
- setWeight: 75
- pause:
duration: 10m
- analysis:
templates:
- templateName: zone-canary-check
args:
- name: service-name
value: test-canary-c
- name: namespace
value: zone-rollouts-demoEach Rollout has its own selector, image/revision state, Services and analysis arguments. After the first 5% gate, an indefinite pause requires an intentional promotion. A hard zone affinity leaves the affected Pods Pending if that AZ has no capacity; it does not relocate them to healthy AZs.
5. Explicit PDBs
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-a-pdb
namespace: zone-rollouts-demo
spec:
minAvailable: 1
selector:
matchLabels:
app: test
zone: a
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-b-pdb
namespace: zone-rollouts-demo
spec:
minAvailable: 1
selector:
matchLabels:
app: test
zone: b
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test-c-pdb
namespace: zone-rollouts-demo
spec:
minAvailable: 1
selector:
matchLabels:
app: test
zone: cThese preserve the original illustrative minimum of one Pod per zone. They are not equivalent to the earlier global minimum of six and do not protect each traffic-weighted revision independently. Inspect currentHealthy, desiredHealthy and disruptionsAllowed before voluntary maintenance.
6. Per-zone Analysis
Create this template before the Rollouts. It uses the actual zonal canary Service name and standard source-reporter metrics rather than invented Pod-zone labels:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: zone-canary-check
namespace: zone-rollouts-demo
spec:
args:
- name: service-name
- name: namespace
metrics:
- name: request-volume
interval: 1m
successCondition: len(result) == 1 && !isNaN(result[0]) && !isInf(result[0]) && result[0] >= 20
failureLimit: 0
provider:
prometheus:
address: http://prometheus.istio-system.svc.cluster.local:9090
query: sum(increase(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}"}[2m]))
count: 5
- name: http-2xx-rate
interval: 1m
successCondition: len(result) == 1 && !isNaN(result[0]) && !isInf(result[0]) && result[0] >= 0.95
failureLimit: 0
provider:
prometheus:
address: http://prometheus.istio-system.svc.cluster.local:9090
query: |-
(sum(rate(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}",response_code=~"2.."}[2m])) or vector(0))
/
sum(rate(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}"}[2m]))
count: 5The numerator is specifically HTTP 2xx, preserving this guide's success definition. An all 5xx window produces 0, not an empty numerator; no traffic or missing data cannot pass the finite-value/volume gates. Source proxies and the provider must reach the configured Prometheus, and its actual labels must match these selectors.
Sustain representative meshed traffic through http://test.zone-rollouts-demo.svc.cluster.local:8080/color. The caller label chooses its zone, and the controller chooses that zone's stable/canary weight. Five-minute warm-up exceeds the two-minute metric lookback; the twenty-request estimate and measurement counts are illustrative, not statistical confidence.
Traffic Flow
Normal Zonal Path
A correctly labelled client A, placed in the reviewed AZ, gets zone-a-route, which targets A's stable/canary Services. The observed ratio depends on sample size, sessions and readiness, not an exact per-ten-request guarantee.
Zone Loss
If every eligible A endpoint disappears, A's route has no healthy upstream. Lowering an outlier threshold or changing the PDB cannot manufacture B endpoints in A's Service. The example returns failures until capacity or an explicitly designed higher-level routing policy changes.
For continuity across AZs, choose an architecture with healthy remote endpoints in the same selected release pool, or implement and test a separate entry-layer failover policy. Account for remote capacity, data consistency, authentication, in-flight requests and failback. No transparent/cyclic failover is claimed by the zonal code above.
Separate Shared-endpoint Locality Alternative
The following belongs to a different setup: an existing shared-app Service or selected subset whose eligible endpoints span AZs and have compatible release state. It does not enable failover on the zonal Services above. If a DestinationRule already owns that shared host/subsets, merge this trafficPolicy into it instead of creating a competing rule:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: shared-endpoint-locality
namespace: zone-rollouts-demo
spec:
host: shared-app.zone-rollouts-demo.svc.cluster.local
trafficPolicy:
loadBalancer:
simple: LEAST_REQUEST
localityLbSetting:
enabled: true
failoverPriority:
- topology.kubernetes.io/region
- topology.kubernetes.io/zone
outlierDetection:
consecutive5xxErrors: 3
interval: 10s
baseEjectionTime: 30s
maxEjectionPercent: 100Locality priorities prefer matching region/zone and can move subsequent traffic to eligible healthy endpoints. They do not specify a fixed cyclic AZ order. consecutive5xxErrors is the current field; consecutive detection is traffic-driven, while interval applies to periodic detection work. Ejecting all endpoints can cause errors, and an already failed request is not automatically replayed. See Zone-Aware Routing.
Troubleshooting
kubectl argo rollouts get rollout test-a -n zone-rollouts-demo
kubectl get virtualservice test -n zone-rollouts-demo -o yaml
kubectl get services test-stable-a test-canary-a -n zone-rollouts-demo -o yaml
kubectl get pods -n zone-rollouts-demo -l app=test -o wide --show-labels
kubectl get endpointslices -n zone-rollouts-demo -l kubernetes.io/service-name=test-canary-a
kubectl get pdb -n zone-rollouts-demo -o wide
istioctl proxy-config routes <client-a-pod> -n zone-rollouts-demo
istioctl proxy-config endpoints <client-a-pod> -n zone-rollouts-demo --cluster 'outbound|8080||test-canary-a.zone-rollouts-demo.svc.cluster.local'
kubectl get analysisruns -n zone-rollouts-demo
kubectl logs -n argo-rollouts deployment/argo-rollouts- Update conflicts: Distinguish ownership of the same route from temporary object-version conflicts on a shared VirtualService. Different subset names alone do not solve either cause.
- Wrong zone selected: Compare actual caller Pod labels, Node placement and compiled routes. Do not assume node zone labels were copied to Pods.
- No fallback: Inspect the selected Service/subset's eligible endpoints first. If none exist outside A, faster outlier detection cannot provide cross-AZ failover.
- No canary traffic: Confirm mesh traversal, ready EndpointSlices, hash selectors, actual weights and sufficient samples.
- Stuck/failed analysis: Inspect its raw result and provider error, not only the aggregate rollout phase. Missing custom zone labels are not built-in telemetry.
Best Practices
1. Coordinate Version Changes Explicitly
Promote resumes an existing pause; it does not deploy a new image or prove another AZ is healthy. Update one zone's desired image in Git (or directly in the isolated lab), observe its analysis and workload state, then decide whether to advance another zone. A fixed five-minute wait is not sufficient evidence by itself.
# Isolated lab alternative to changing Git; affects zone A only.
kubectl argo rollouts set image test-a app=argoproj/rollouts-demo@sha256:e32df3d15f759d36c323b3dccb7003d38df1a4274d37217715151f085c24c58f -n zone-rollouts-demo
kubectl argo rollouts get rollout test-a -n zone-rollouts-demo --watch
# After the intended manual pause and review:
kubectl argo rollouts promote test-a -n zone-rollouts-demoAbort does not revert the desired image in Git. Coordinate GitOps so it does not overwrite each zone's managed weights or Service hash selectors; see the preceding integration guide.
2. Continuous Analysis Alternative
Replace the inline-step schedule deliberately if continuous monitoring is needed. count is omitted in the background template, and startingStep: 2 means the third step:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: zone-canary-continuous
namespace: zone-rollouts-demo
spec:
args:
- name: service-name
- name: namespace
metrics:
- name: request-volume
interval: 1m
successCondition: len(result) == 1 && !isNaN(result[0]) && !isInf(result[0]) && result[0] >= 20
failureLimit: 0
provider:
prometheus:
address: http://prometheus.istio-system.svc.cluster.local:9090
query: sum(increase(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}"}[2m]))
- name: http-2xx-rate
interval: 1m
successCondition: len(result) == 1 && !isNaN(result[0]) && !isInf(result[0]) && result[0] >= 0.95
failureLimit: 0
provider:
prometheus:
address: http://prometheus.istio-system.svc.cluster.local:9090
query: |-
(sum(rate(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}",response_code=~"2.."}[2m])) or vector(0))
/
sum(rate(istio_requests_total{reporter="source",destination_service_name="{{args.service-name}}",destination_service_namespace="{{args.namespace}}"}[2m]))
---
spec:
strategy:
canary:
analysis:
templates:
- templateName: zone-canary-continuous
startingStep: 2
args:
- name: service-name
value: test-canary-a
- name: namespace
value: zone-rollouts-demo
steps:
- setWeight: 5
- pause:
duration: 5m
- setWeight: 25
- pause:
duration: 5m
- setWeight: 50
- pause: {}Keep the correct zone's route, Services and arguments when adapting this A fragment to B/C. Independent analysis can still depend on shared Prometheus/network/control-plane availability.
3. Monitoring Rules
This PrometheusRule requires Prometheus Operator and matching rule namespace/label selectors. A standalone Istio addon Prometheus does not load this CRD automatically.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: zone-rollout-alerts
namespace: zone-rollouts-demo
spec:
groups:
- name: zone-rollout
rules:
- alert: HighErrorRateZoneACanary
expr: |-
((sum(rate(istio_requests_total{reporter="source",destination_service_namespace="zone-rollouts-demo",destination_service_name="test-canary-a",response_code=~"5..|0"}[2m])) or vector(0)) / sum(rate(istio_requests_total{reporter="source",destination_service_namespace="zone-rollouts-demo",destination_service_name="test-canary-a"}[2m]))) > 0.05
and
(sum(increase(istio_requests_total{reporter="source",destination_service_namespace="zone-rollouts-demo",destination_service_name="test-canary-a"}[2m]))) >= 20
for: 2m
annotations:
summary: Zone A canary has elevated 5xx/zero-status rate with observed traffic
- alert: UnexpectedDemoZoneRoute
expr: |-
sum(rate(istio_requests_total{
reporter="source",source_workload="zone-client-a",source_workload_namespace="zone-rollouts-demo",
destination_service_namespace="zone-rollouts-demo",destination_service_name=~"test-(stable|canary)-(b|c)"
}[5m])) > 0
for: 5m
annotations:
summary: Demo client A is using a B/C destination Service; inspect route/placement assumptionsThe first rule checks 5xx/zero-status failures; 4xx also lowers the Analysis 2xx rate but does not trigger this particular error rule. Low/missing traffic requires a separately designed expected-traffic/telemetry-health signal; absence is not proof of a healthy canary.
The second is a demo routing-invariant alert, not a universal physical cross-AZ detector. It assumes the real source Deployment is zone-client-a, its placement is A, and B/C Services retain their zonal selectors. For physical-zone measurements, use verified node/endpoint locality or explicitly enriched telemetry as described in the routing/observability guides.
4. Recovery and Capacity
Diversify Spot capacity and retain adequate fault-tolerant capacity according to the actual EKS/node provisioning model. Zonal affinity, interruption handling and PDB policy cannot guarantee replacement capacity. Check the node group's lifecycle behavior and the application's termination/restore process.
There are no measured resource or latency results for this blueprint. Size Istiod, proxies and the Rollouts controller from measured reconciliation pressure, endpoint counts, resource use and normal/failure-path latency for the actual workload.
References and Next Steps
- Argo Rollouts Integration
- Zone-Aware Routing
- Outlier Detection
- DestinationRule
- Istio VirtualService source selectors
- Istio locality failover
- Istio DestinationRule API
- Argo Rollouts Istio integration
- Kubernetes disruptions
- PDB configuration and rounding
- Node affinity
- EC2 interruption notices
- EKS managed-node Spot behavior
- AWS AZ IDs and account mapping
- AWS Regions and Availability Zones
Use the maintained integration/routing guides above as the starting point for lab validation. Multi-cluster introduces additional trust, connectivity and failure-domain design; it is not a single-setting extension of this blueprint.