Skip to content

Cache Node metadata and self-heal missed bindings in NodeLabelDelivery - #5501

Open
ryanaoleary wants to merge 3 commits into
kuberay-project:masterfrom
ryanaoleary:fix/node-label-delivery-scale
Open

ryanaoleary wants to merge 3 commits into
kuberay-project:masterfrom
ryanaoleary:fix/node-label-delivery-scale

Conversation

@ryanaoleary

@ryanaoleary ryanaoleary commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Why are these changes needed?

Following #5300, the pod binding webhook still reads each bound Node directly from the API server for worker groups with labelRefs. In addition, worker pods that bind without the ray.io/node-labels annotation (for example, during a webhook rollout or before a node labeler finishes) fail to start and remain stuck in CrashLoopBackOff because Kubernetes only runs pods/binding once per pod.

This PR makes two changes:

  1. Read node metadata from the informer cache

    • Read node PartialObjectMetadata from the manager cache in the binding webhook and fall back to a live API read only on NotFound.
    • Pre-register the node metadata informer when NodeLabelDelivery is enabled, strip managedFields from cached node objects, and add list and watch permissions on nodes to the cluster role.
  2. Recreate worker pods after transient missed bindings

    • When a worker pod prepared for node label delivery terminates without ray.io/node-labels, check the bound node in the metadata cache during reconciliation.
    • Keep the pod as-is if the node still lacks a requested label or the label is not allowlisted, and delete the pod so it can be recreated once all requested labels are present on the node.

Related issue number

Closes #5499

Labels

  • If this PR has user-facing changes that require documentation updates at release time, I have added the doc-updates-required label.
  • If this PR contains breaking changes, I have added the breaking-change label.

Checks

  • I've made sure the tests are passing.
  • Testing Strategy
    • Unit tests
    • Manual tests
    • This PR is not tested :(

Signed-off-by: Ryan O'Leary <ryanaoleary@google.com>
Signed-off-by: Ryan O'Leary <ryanaoleary@google.com>
Signed-off-by: Ryan O'Leary <ryanaoleary@google.com>
@ryanaoleary ryanaoleary changed the title [ray-operator] Cache Node metadata and self-heal missed bindings in NodeLabelDelivery Cache Node metadata and self-heal missed bindings in NodeLabelDelivery Nov 4, 2026
@win5923

win5923 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor

cursor Bot commented Nov 4, 2026

Copy link
Copy Markdown

Skipping Bugbot: Unable to authenticate your request. Please make sure Bugbot is properly installed and configured for this repository.

Comment on lines 17 to 19
// K8sControllerRuntimeCacheSelectors returns a map[client.Object]cache.ByObject that scopes the manager's
// informer cache to only watch KubeRay-managed Jobs (filtered by app.kubernetes.io/created-by=kuberay-operator)
// and Ray node Pods (filtered by ray.io/node-type in head|worker|redis-cleanup).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should also mention the Node cache.

"The worker Pod %s/%s terminated without the %s annotation, and bound Node %s could not be read: %v.",
pod.Namespace, pod.Name, utils.RayNodeLabelsAnnotationKey, pod.Spec.NodeName, err)
}
for _, ref := range worker.LabelRefs {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe extract buildNodeLabels into a shared utility function (e.g. utils.ResolveNodeLabels) and reuse it here so we don't have to maintain the same validation logic in two places.

// plugin applied the node label after binding or the binding webhook timed out under failurePolicy: Ignore).
// Because pods/binding only executes once per Pod lifetime, the container cannot recover via in-place restart;
// once the bound Node carries all requested and allowed labelRefs, the Pod is deleted so it can be recreated.
func (r *RayClusterReconciler) shouldDeleteWorkerPod(ctx context.Context, pod corev1.Pod, worker *rayv1.WorkerGroupSpec) (bool, string) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we should reconsider this behavior, as it changes the existing deletion logic and may cause Pods that would normally be deleted to be retained.

This affects:

  • restartPolicy: Never workers (used by autoscaler v2 with Ray < 2.56) that are Failed, or still Running because a sidecar is alive. In the latter case, they continue holding GPU/TPU resources.
  • Multi-host groups, where the entire replica can be retained.

These Pods still count toward the group's replicas, so KubeRay won't replace them. The retain path also provides no event or log to indicate the issue.

Maybe only adding deletion conditions, without changing existing deletion behavior. This fully addresses the Always Pod CrashLoopBackOff issue described in the PR:

shouldDelete, reason := shouldDeletePod(pod, rayv1.WorkerNode)
if shouldDelete || !missedNodeLabelDelivery(pod, worker) {
    return shouldDelete, reason
}

WDYT?

return map[client.Object]cache.ByObject{
&batchv1.Job{}: {Label: jobSelector},
&corev1.Pod{}: {Label: podSelector},
&corev1.Node{}: {Transform: cache.TransformStripManagedFields()},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just curious, for large clusters, how much memory overhead could caching all Node labels and annotations introduce?

This branch has not been deployed

No deployments
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.

[Bug] NodeLabelDelivery uncached Node GETs and unrecoverable missed bindings

2 participants