Skip to content

Fix multi-volume durability placement - #644

Merged
jmclong (jmclong) merged 4 commits into
Azure:mainfrom
Hrishikeshkashyap:fix/multi-volume-placement
Oct 28, 2026
Merged

jmclong (jmclong) merged 4 commits into
Azure:mainfrom
Hrishikeshkashyap:fix/multi-volume-placement

Conversation

@Hrishikeshkashyap

Copy link
Copy Markdown
Contributor

Pods can reference availability and durability volumes in any order, and multiple durability volumes may resolve to different nodes. Placement must not depend on volume order or permit a node that does not hold every durability volume.

Changes

  • Give durability placement precedence over availability preferences.
  • Intersect node sets across durability volumes.
  • Deny admission when durability placement is missing or has no common node.
  • Document multi-volume placement behavior.

Tests

Existing handler_unit_test.go now covers availability-only aggregation, both mixed-mode orders, matching and conflicting durability nodes in both orders, and missing durability placement.

The focused Linux webhook tests pass.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Early admission returns and required-affinity merging can still bypass durability placement constraints.

2 open findings
What changed in this PR

Updates Pod placement to prioritize durability volumes and require a common storage node across them.

Changes:

  • Intersects durability node sets and rejects missing or conflicting placement.
  • Adds multi-volume placement tests.
  • Documents durability precedence.
File Description
internal/​webhook/​hyperconverged/​handler.go Aggregates placement constraints and adds admission checks.
internal/​webhook/​hyperconverged/​handler_unit_test.go Tests volume ordering, conflicts, and missing placement.
docs/​design/​webhooks.md Explains multi-volume placement rules.
docs/​architecture.md Documents durability precedence and common-node requirements.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/webhook/hyperconverged/handler.go
Comment thread internal/webhook/hyperconverged/handler.go Outdated

Copy link
Copy Markdown
Contributor

Potential upgrade impact: durability PVCs resolved to different nodes

A concrete case is a Pod that mounts two durability-mode PVCs, for example db-data and db-wal. If their PVs resolve to different nodes (db-data to node-a, db-wal to node-b), the new intersection is empty and the webhook denies Pod admission with durability volumes have no common storage node. Before this change, the combined node list could allow scheduling on either node without ensuring both durability volumes were present there.

This can happen when the PVCs were provisioned or bound independently—for example, created at different times or for different consumers, or backed by existing PVs—and then later mounted by the same workload. Delayed binding (WaitForFirstConsumer) can help when the StorageClass/provisioner supports it and the PVCs are provisioned together for the same consumer, but it does not make already-bound PV locations common. The webhook's placement decision is based on each PV's selected-node annotation, falling back to its initial-node volume attribute.

The practical risk is that a rollout, reschedule, or recovery that needs a replacement Pod can stall because the Pod is rejected at admission; this does not by itself evict an already-running Pod. Customers can mitigate by checking all durability PVs referenced by a workload before upgrading or restarting it, ensuring those PVs resolve to a common node, and—if they do not—migrating/reprovisioning the data or otherwise reconciling placement before proceeding. Also worth noting for mixed-mode Pods: when any durability volume is present, this code applies only the durability node set, so availability volumes' preferred-node affinity is not retained.

@Hrishikeshkashyap
Hrishikesh Kashyap (Hrishikeshkashyap) force-pushed the fix/multi-volume-placement branch 2 times, most recently from ab499da to c8d3234 Compare October 16, 2026 08:10
@Hrishikeshkashyap

Copy link
Copy Markdown
Contributor Author

Potential upgrade impact: durability PVCs resolved to different nodes

A concrete case is a Pod that mounts two durability-mode PVCs, for example db-data and db-wal. If their PVs resolve to different nodes (db-data to node-a, db-wal to node-b), the new intersection is empty and the webhook denies Pod admission with durability volumes have no common storage node. Before this change, the combined node list could allow scheduling on either node without ensuring both durability volumes were present there.

This can happen when the PVCs were provisioned or bound independently—for example, created at different times or for different consumers, or backed by existing PVs—and then later mounted by the same workload. Delayed binding (WaitForFirstConsumer) can help when the StorageClass/provisioner supports it and the PVCs are provisioned together for the same consumer, but it does not make already-bound PV locations common. The webhook's placement decision is based on each PV's selected-node annotation, falling back to its initial-node volume attribute.

The practical risk is that a rollout, reschedule, or recovery that needs a replacement Pod can stall because the Pod is rejected at admission; this does not by itself evict an already-running Pod. Customers can mitigate by checking all durability PVs referenced by a workload before upgrading or restarting it, ensuring those PVs resolve to a common node, and—if they do not—migrating/reprovisioning the data or otherwise reconciling placement before proceeding. Also worth noting for mixed-mode Pods: when any durability volume is present, this code applies only the durability node set, so availability volumes' preferred-node affinity is not retained.

The common-node requirement is intentional: the previous behavior could allow scheduling without ensuring all durability volumes were present on the selected node.

I’ve added a concise upgrade warning advising users to verify that their durability PVs resolve to a common storage node before upgrading or restarting affected workloads. It also clarifies that replacement Pod creation can be denied, running Pods are not evicted, and WaitForFirstConsumer does not relocate already-bound PVs.

Availability-volume preferences remain omitted for mixed-mode Pods in this PR; the durability restriction takes precedence. Preserving those preferences alongside the required constraint could be handled separately.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The acknowledged prerequisite fix in #643 for composing existing required affinity remains outstanding.

0 open findings

2 resolved since last review

🧠 Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Empty-node validation remains incomplete, and safe affinity composition still depends on integrating and verifying #643.

1 open issue

Review effort: Balanced

Comment thread internal/webhook/hyperconverged/handler.go
}
slices.Sort(durabilityNodes)
durabilityNodes = slices.Compact(durabilityNodes)
return h.patchPodWithNodeAffinity(pod, durabilityNodes, FailoverModeDurability)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if durabilityNodes has two different node names? Lana Andreasyan (@landreasyan) what is your suggestion in that case? Should we better fail?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata-derived placements are intersected, so different owner nodes result in admission denial. With the topology fallback, multiple allowed nodes are valid; we preserve and AND the selectors across volumes. If no node satisfies them, Kubernetes leaves the Pod Pending rather than the webhook choosing a node.

Comment thread docs/design/webhooks.md
take precedence over availability preferences. Multiple durability volumes
must have a common storage node.

Before upgrading, verify that each workload's durability PVs resolve to a common

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to implement this? Can we check if any pod in a state that can go un-scheduled post upgrade ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A pre-upgrade check could be a kubectl/jq script, or a dry-run mode in the webhook that logs would-be denials. I'd think we should be tracking that as a follow-up issue rather than expanding this PR.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Valid topology-pinned durability volumes without selected-node metadata can now prevent replacement Pod creation.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread internal/webhook/hyperconverged/handler.go
@Hrishikeshkashyap
Hrishikesh Kashyap (Hrishikeshkashyap) force-pushed the fix/multi-volume-placement branch 2 times, most recently from da0f0a8 to c400e4a Compare October 23, 2026 06:50
for _, right := range selector.NodeSelectorTerms {
if len(right.MatchExpressions) == 0 && len(right.MatchFields) == 0 {
continue
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Avoid materializing the Cartesian product of retained PV topology selectors.

This fallback is reachable for durability PVs with retained spec.nodeAffinity but no selected-node metadata. Each two-term selector doubles the Pod selector terms. Using the production admission helper and applying its actual JSON patch, 12 PVs with identical two-term selectors produced 4,096 terms and a 3,485,834-byte patch. Each output term also copies the accumulated requirements, so webhook CPU/memory and the mutated Pod size grow along with the term count. Identical selectors do not need these combinations, and merely deduplicating identical inputs would not bound the general case.

A smaller fix is to leave retained PV topology on the PV rather than copy it into Pod affinity: the scheduler already checks the node affinity of every bound PV (checkBoundClaims in the VolumeBinding plugin). Keep intersecting metadata-derived owner nodes and compose that restriction with every existing nonempty Pod selector term using #643. Preserve denial for genuinely missing placement and conflicting metadata-derived owners. Topology conflicts can remain Pending, as intended here, without enumerating every combination or introducing a Node LIST.

Please add a regression using multiple topology-only PVs with two OR terms each, asserting bounded Pod patch size while retaining the metadata-owner and application-affinity constraints.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest update. Retained PV topology stays on the PV and is enforced by Kubernetes’ scheduler; the webhook no longer expands it into Pod affinity terms. Metadata-derived placement is still intersected, and the Pod’s required affinity is composed with that constraint. Added a 12-PV regression that verifies bounded patch size.

Give durability volumes precedence over availability volumes and intersect their resolved node sets. Deny admission when durability placement is missing or has no common node.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verify mixed-mode order independence, durability-node intersection, conflicting nodes, and unresolved durability placement.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document durability precedence and the common-node requirement for multiple durability volumes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Continue aggregation when an availability PV has no placement metadata so durability constraints cannot be bypassed. Add regression tests for resolved, unresolved, and conflicting durability placement in both volume orders. Keep a concise upgrade warning for affected workloads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@jmclong jmclong (jmclong) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the topology-selector growth fix and the bounded-patch regression, including preservation of application affinity and metadata-derived ownership constraints. Focused webhook unit tests pass; CI checks are green.

@jmclong
jmclong (jmclong) merged commit 9fb30ca into Azure:main Oct 28, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants