Conversation
AKS agent pool upgradeSettings previously declared only maxSurge, so Pulumi nulled drainTimeoutInMinutes and undrainableNodeBehavior on every apply. With the legacy null behaviour, a node whose pods cannot be evicted -- such as one hosting Workbench sessions covered by a PodDisruptionBudget that structurally permits no disruptions -- fails the entire node-image upgrade after the 30 minute default drain timeout and leaves the pool in provisioningState: Failed. AKS then retries daily, surging a replacement node and tearing it down at the VMSS layer, which bypasses the Eviction API and kills whatever was scheduled onto it. Set undrainableNodeBehavior to Cordon so AKS cordons and skips such nodes rather than failing the operation, and set the per-node drain timeout to 10 minutes, matching the cluster autoscaler's default maxGracefulTerminationSec. Applied to the user AgentPool resources, and to the system node pool profile where it takes effect on cluster creation.
|
Claude finished @Lytol's task in 2m 7s —— View job Review
The fix is minimal and correct. Two observations worth noting: User pools (load-bearing case): The System pool comment accuracy: The comment at
No issues with the approach, the field names, or the 10-minute drain timeout rationale. The caveats section of the PR description (cordoned nodes still count toward |
Description
The AKS step declares
upgradeSettingswith onlymaxSurge, andupgradeSettingsis not in the step'signoreChangeslist (lib/steps/aks.go:167-187). Pulumi therefore owns the whole object and resetsdrainTimeoutInMinutesandundrainableNodeBehaviorto null on every apply.With those fields null, an agent pool becomes permanently wedged if it hosts a pod that cannot be evicted. Workbench components get a PodDisruptionBudget with
maxUnavailable: 0per component, which makesdisruptionsAllowedstructurally zero — the API server then rejects every eviction for the selected pods (HTTP 429,"Cannot evict pod as it would violate the pod's disruption budget"). A node running a Workbench session is consequently undrainable indefinitely.When an automatic node-image upgrade hits such a node it fails the whole operation after the 30 minute default drain timeout, leaving the pool in
provisioningState: Failed. AKS then retries on a daily cadence: it surges a new node, cordons the old one — so newly launched pods land on the surge node — times out draining, and deletes the surge instance at the VMSS layer, bypassing the Eviction API, terminating whatever was running on it. The pool staysFailedindefinitely with its nodes pinned to a stale image, and long-running Workbench background jobs are killed off by the repeating surge/teardown cycle.Code Flow
undrainableNodeBehavior: Cordonmakes AKS cordon and skip an undrainable node rather than failing the operation, which stops both the wedge and the repeating surge/teardown loop.drainTimeoutInMinutes: 10aligns with the cluster autoscaler's defaultmaxGracefulTerminationSecof 600s, so both subsystems allow the same graceful-shutdown budget instead of the 30 minute default stalling every upgrade.Applied at both declaration sites. The per-pool
AgentPoolresources are the load-bearing case. The system node pool profile on theManagedClusteris also set, but"agentPoolProfiles"is unconditionally added toignoreChanges(:189-193) —ResolveUserNodePoolserrors whenuser_node_poolsis empty, so that branch always fires — meaning the system-pool values apply only at cluster creation. Existing clusters' system pools are unaffected; this is recorded in a code comment so the next reader isn't misled.maxSurgeunchanged at10%.Field names and types verified against
pulumi-azure-native-sdk/containerservice/v3@v3.16.0(pulumiTypes.go:2099):UndrainableNodeBehaviorispulumi.StringPtrInput,DrainTimeoutInMinutesispulumi.IntPtrInput; REST API version 2025-09-01 supports both. Uses the typed constantcontainerservice.UndrainableNodeBehaviorCordon, consistent withOSSKUUbuntu/ScaleDownModeDeleteelsewhere in the file.upgradeSettingsupdates in place and does not force pool replacement.Hardcoded, not configurable
Consistent with
maxSurge,ScaleDownMode,OsSKUand the other upgrade-path settings here, all hardcoded. The legacy null behaviour has no scenario in which it is preferable, so a per-workload knob would only create a way to stay broken. These settings also have to be correct for automatic node-image upgrades, which run with nobody invokingptd ensure— an opt-in would protect only workloads someone remembered to configure. Addingdrain_timeout_minutestoAzureUserNodePoolConfiglater is a small additive change if a real need appears.Caveat: cordoned nodes are released by session lifetime, not by the upgrade
Cordonleaves the undrainable node running and cordoned rather than replacing it. It is released only once its blocking pods clear — for Workbench, the session's own kill/suspend timeout.NodeOSUpgradeChannel: NodeImage,UpgradeChannel: Patch,:243-246), not just operator-runptd ensure.MaxCountfor the cluster autoscaler, so accumulation reduces schedulable capacity and can stop scale-out with no alert and nothing visible in a Pulumi preview.Failed); now it is quiet. Because AKS reports the operationSucceededand bumps the pool-levelorchestratorVersion, skipped nodes are invisible to subsequent previews and can drift toward the kubelet version-skew boundary while the pool looks healthy. Monitoring on cordoned/quarantined node count is a sensible follow-up.MaxCount, but a workload with no session kill-timeout could hold a node cordoned indefinitely.Still strictly better than the status quo, which doesn't merely strand a node but wedges the pool and runs a repeating loop destroying surge instances out from under running pods.
10 minutes is the conservative choice against the 30 minute default; 5 is the more aggressive option if upgrades stall on blocked nodes.
Category of change
Checklist