Skip to content

Forceful Fallback — Scenario O

What this validates

Window-bounded forceful fallback (#156), earliest-deadline ordering (#157), and do-not-disrupt exclusion (#170) running together on a shared deadline — all on real EKS Auto Mode.

Real-EKS validation of the three features above exercised simultaneously. See §7.2 for the assumptions this flips to validated, and the runbook for what noderotation_forceful_fallback_total and ForcefulFallback mean operationally.

Scenario O · EKS Auto Mode · 2026-07-12 UTC

12-node rotation on a shared deadline — graceful → forceful fallback split

All 12 nodes are created at once and share the same creationTimestamp (deadline). They are processed one at a time, serially (maxUnavailable=1), and graceful vs. forceful is decided purely by whether, at the moment a node's turn comes up, t_rot (12m) or more remains until the deadline. expireAfter stays fixed at 2h throughout (trick-free).

forceful_fallback_total

0 3

completed{success}

12 9 graceful + 3 forceful

expireAfter backstop

0 expired

controller restarts

0 counter never reset

pre-thresholdgraceful windowforceful window09:26:20Zage 48m · eligible10:26:20Zage 1h48m · forceful boundary10:38:20Zage 2h · backstop (not reached)08:3809:0809:3810:0810:38batch123456789101112
graceful surge (with placeholder) × 9 forceful fallback (surge-less) × 3 expireAfter backstop (0 this run)
9

Case A graceful

remaining ≥ t_rot (12m)

There is enough time before the deadline to complete the surge. placeholder Pod → new node Ready → voluntary drain of the old node (PDB applies). mode is empty. Nodes 1–9.

3

Case B forceful

remaining < t_rot (12m)

The graceful surge would not finish in time. Since it is in-window and opt-in, the NodeClaim is deleted without a placeholder (voluntary path, PDB applies). mode=forceful-fallback, a Warning event, counter +1. Nodes 10–12.

0

Case C backstop

age reaches 2h

Even forceful fallback would not finish in time and the deadline is reached → Karpenter force-expires the node (outcome=expired). The "fallback of the fallback." This run, forceful fallback handled all remaining nodes by 10:33, so this case is 0.

Time UTCagephaseNodeClaimbehavior / evidence
08:38:200▸ 12-node synchronized batch creationshared deadline 10:38:20Z. schedule intentionally warns (ThroughputBurstShortfall N=12>K·C=6 / ThroughputBelowArrival)
09:26:2048m▸ eligible boundary (ageThreshold A=48m)from here, candidates become rotation targets
09:26:2448mgraceful152t8hplaceholder surge → make-before-break (total 1m2s)
09:33:4155mgraceful25rp47placeholder Pending→Running, mode empty
09:41:291h03mgraceful394f5nsame as above (cooldown 6m → interval ~7m)
09:48:451h10mgraceful4fvd7psame as above
09:56:341h18mgraceful5gjgg8same as above
10:04:501h26mgraceful6n8zqtsame as above
10:12:401h34mgraceful7p2vdksame as above
10:19:541h41mgraceful8phmmssame as above
10:24:211h46mgraceful9rnwb7last graceful (age 1h46m, just inside the 1h48m boundary)
10:26:201h48m▸ forceful boundary crossed (deadline − t_rot)cooldown dropped 6m→1m just before, at 10:24:00. Every pick from here on is Case B
10:26:551h48mforceful10sgh57first forceful (age 1h48m35s), mode=forceful-fallback, placeholder NotFound, counter 0→1
10:30:591h52mforceful11t7sb5surge-less, Warning event, counter →2
10:33:161h54mforceful12tkkf7counter →3 — complete, all 12 rotations done (~5m before the 10:38 deadline)
10:38:202h▸ expireAfter backstop — not reached (Case C: 0)

#157 (earliest-deadline order): since all 12 share the same creationTimestamp, ordering degenerates to the Name tiebreak, and they are consumed in exact ascending order — 52t8h < 5rp47 < 94f5n < fvd7p < gjgg8 < n8zqt < p2vdk < phmms < rnwb7 < sgh57 < t7sb5 < tkkf7.

The graceful/forceful mix was shaped via cooldown tuning: a real graceful rotation finishes in ~1 minute (46s–1m51s), so left alone the serial surge would handle all 12 and no forceful fallback would occur. cooldownAfter=6m in the first half held the surplus back until the deadline, then dropping it to 1m at 10:24 (just before the boundary) let forceful fallback clear the rest before the backstop. expireAfter never changes, so this is not a trick.

t_rot = readyTimeout 5m + tGP 5m + buffer 2m = 12m (buffer shrank 15m→2m in #215). The split is decided purely by whether the pick time falls before or after 10:26:20Z; because the deadline is shared, the instant that boundary was crossed, every remaining node flipped to forceful at once.

Scenario coverage

The matrix below tracks every EKS Auto Mode PoC scenario against the specification's roadmap and open-questions. A scenario stays "planned" until re-run and observed on a real cluster — code coverage (unit/envtest/KWOK) is necessary but not sufficient for a row to flip to validated.

EKS Auto Mode PoC · SCENARIOS.md × VALIDATION.md

Scenario coverage matrix

Rows = the capability (behavior layer) under test, columns = scenarios 0–O. = that scenario's primary focus, = passes through as a secondary effect. A row where two scenarios both have is overlapping; rows marked are guarantees from Karpenter/AWS itself (not this controller's logic).

Scenarios

17 0, A–O

Capabilities under test

17 layers

Overlapping capability rows

2 backstop · rollback

Karpenter/AWS-leaning

2 N · A (partial)

primary focus passes through (secondary) not covered validated (observed on real EKS) planned (code coverage only)⚠ overlap the same capability is the primary focus of 2 scenarios☁ external a Karpenter/AWS guarantee
Capability / scenario →Status0ABCDEFGHIJKLM-AM-BNO
Surge & placement
Surge — new provisioningmake-before-break
Surge — capacity-absorbbin-packs onto existing spare (no new node)
Placement — NodePool confinementdoesn't leak onto another pool's spare
Gates (don't start / boundary)
Gate — NodePool limitsno surge when there is no headroom
Gate — maintenance window / boundaryin-flight finishes; nothing new starts past the boundary
Backstop, failure & outcome
expireAfter backstop (R6)⚠ D⊂Ilead time wins out
readyTimeout rollback + cleanup⚠ C≈M-Bstate=failed / retry++ / uncordon
Forced-expiry expired outcomecaught while pending
PDB-respecting voluntary drainminAvailable actually blocks
do-not-disrupt (same key, 3 facets)
① the controller writessurge-pair protection + owned marker
excluded from candidate selection (reads)#170
③ Karpenter honors it (Drift)☁ external
Robustness & other
Resume across leader changecontinues from annotations alone
placeholder preemptionvictim / preemptionPolicy=Never
zonal-EBS (PV) reattach☁ partialstateful workloads · CSI/AWS
Forceful fallback (v0.4+ / #156, #157, #170)
surge-less forceful fallback#156
earliest-deadline order#157

Column legend0 core surge · A zonal-EBS · B limits · C readyTimeout · D backstop · E expired · F confinement · G PDB · H dnd-write · I R6 soak · J absorb · K leader · L window · M-A preemption · M-B rollback · N dnd-honor · O forceful-fallback(+#157/#170)

D ⊂ I backstop

I subsumes D. D is a static snapshot of the backstop metric, while I demonstrates the same property dynamically across a continuous rotation. Proving R6 via I alone is sufficient.

For re-validation, keep I and fold D away (note the static value alongside I's row).

C ≈ M-B rollback

Both verify the same rollback cleanup (state=failed, retry++, uncordon, surge teardown). The only difference is the trigger (C = node not Ready / M-B = placeholder stuck Pending).

Either one alone is enough for the rollback assertions (M-A's preemption is distinct, so keep it).

N · A (partial) external guarantees

N (Karpenter honoring do-not-disrupt) and A's EBS reattach are more a check of Karpenter/AWS's own guarantees (de-risking) than of this controller's logic.

Can be deprioritized under cost pressure (on the assumption that the docs can be trusted).

do-not-disrupt is not a "duplicate" but 3 facets: ① writes it (H) / ② reads and excludes it (#170, O) / ③ Karpenter honors it (N) are separate layers, so this is not redundancy. But since they all revolve around the same key, understanding them together makes the blast radius of a change easier to read.

Rough minimal-cover set: unique = 0, A, B, E, F, G, J, K, L, M-A, O. To fold overlaps: I (subsumes D) plus either C or M-B, with N optional. O is multi-capability (the #156 core plus #157 and #170; it also passes through backstop/expired/PDB/surge secondarily).

Source: test/e2e/eks-automode/SCENARIOS.md (procedure) and VALIDATION.md (observed evidence per scenario), spec §7.2. ●/◯ reflect a judgment call about primary vs. secondary focus, not an exhaustive enumeration of every secondary pass-through.