fix: release dangling node lock - #1271
Conversation
There was a problem hiding this comment.
Summary of Changes
Hello @peachest, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request addresses a critical issue where Kubernetes NodeLocks could become "dangling" if the Pod that set the lock was unexpectedly deleted before releasing it. This situation would block subsequent Pods from being scheduled on the affected node until the lock naturally expired. The changes introduce mechanisms to proactively clear these dangling locks, improving node resource utilization and preventing unnecessary Pod pending states.
Highlights
- Error Handling in Scheduler Binding: Modified the scheduler's Bind function to ensure that if an error occurs during the patching of Pod annotations (e.g., setting the NodeLock), the NodeLock is immediately released instead of leaving it in a potentially stuck state.
- Proactive Dangling NodeLock Release: Enhanced the LockNode function to detect and release NodeLocks that are either expired or associated with a Pod that no longer exists (i.e., a "dangling" lock). This allows new Pods to acquire the lock without waiting for a timeout.
- Improved Readability for Lock Release: Renamed the timeout parameter to skipNodeLockOwnerCheck in the ReleaseNodeLock function for better readability and comprehensibility.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request addresses a critical bug where a NodeLock could become dangling if the owner pod is deleted, preventing other pods from being scheduled on that node. The proposed solution, which involves releasing the lock on error during pod annotation patching and allowing the removal of dangling locks, is well-reasoned and thoroughly explained. The changes are logical and directly address the issue. I've identified one potential issue in the implementation of the dangling lock check that could cause problems with older lock formats and have provided a suggestion for a fix.
1a9c12f to
c471309
Compare
c471309 to
8d23c2e
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request effectively addresses the issue of dangling node locks by ensuring locks are released on error during the pod binding process and by allowing the removal of locks belonging to deleted pods. The changes are logical and well-implemented. I've provided a couple of suggestions to further improve code clarity and efficiency.
c0823f2 to
d10ea7b
Compare
|
CC @Shouren |
cc @s |
|
please fix the UT |
71a6db5 to
c210348
Compare
48498ee to
df75869
Compare
|
@archlitchi This PR seems fine to me. And I think we can create a new issue with 'good first issue' label to see if anyone can implement the optimization of release lock by handling delete event of pod in scheduler. |
agree, you could open that issue and see if anyone is interested |
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
please resolve this conflict, we're ready to go:) |
df75869 to
519c32f
Compare
Signed-off-by: houyuxi <yuxi.hou@transwarp.io>
1. fix setLockNodeWithTimeout 2. add a unit test for dangling nodelock Signed-off-by: houyuxi <yuxi.hou@transwarp.io>
|
We've synchronised to the latest master branch and resolved the conflict. |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: archlitchi, peachest, Shouren The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
/lgtm |
What type of PR is this?
/kind bug
Which issue(s) this PR fixes:
Fixes #714
Fixes #810
Fixes #1244
Background
The background details of the above issues and the history of the fixes are summarised here:
The root of the problem originates from the poor design of the device-plugin framework provided by k8s. In the device-plugin framework, the device-plugin can report the extended resources on the node through the ListAndWatch interface, and handle the initialization of the device through the Allocate interface, as well as mounting the necessary contents for the container to run, including the device file, the control device file, the driver directory, the command line tools, the environment variables, etc. Kubelet will call the Allocate interface of the device-plugin after the Pod is bound to the node. After the Pod is bound to a node, Kubelet calls the Allocate interface of the device-plugin to set up and mount the response for the container based on the returned information.
If the device plugin only needs to manage physical hardware devices, and each device is exclusive to the container (i.e., a device can only be mounted to one container at a time), then all of the above works very well. Because in this case each device is stateless, each device corresponds to a unique extension resource reported by the device plugin via ListAndWatch and has a unique ID; when the Allocate interface is called, kubelet selects the appropriate resource among the unallocated resources and passes the corresponding ID List as a request parameter. The plugin simply selects the corresponding hardware devices based on the ID List for container.
Until the need to manage virtualised device resources arises, at which point each physical device is no longer stateless, using HAMi's nvidia share as an example. Eeach Nvidia device is divided into 100 for sharing. When scheduling Pods, the scheduler not only needs to determine the Node for Pod, but also needs to decide the specific physical device to be allocated to each container based on the remaining available resources of each device on the node, so the actual device that the container needs to mount is no longer determined by the kubelet or the device plugin, but rather by the scheduler's scheduling results. The scheduler can easily record the scheduling result to the corresponding Pod, e.g. by writing it to the Pod annotation during the bind phase, as implemented in HAMi.
On the one hand, as mentioned above, the interface of the device plugin framework is stateless from the very beginning of its design, and device plugins cannot get any information about the Pod or container that needs to be assigned a device through the parameters of the Allocate interface provided by the framework. On the other hand, the k8s scheduling mechanism divides Pod scheduling into a serial Filter phase and a parallel Bind phase, where different Pods may be bound to the same node at the same time, so the device plugin can't get the Pod info which is currently being allocated by kubelet by geting pods bound to the node.
HAMi chooses to use NodeLock to serialise Pod bindings and device Allocating, by adding a NodeLock to Node Annotations so that at most one Pod is bound to the node at a time and processed by the device plugin called by Kubelet. The NodeLock records the Pod, timestamp, and other information, and the device plugin parses it to get the Pod that is currently being processed and gets the scheduler's scheduling result through the Pod annotation to complete the allocation of the device. When the device plugin finishes allocating, it removes the NodeLock. So that next pod can be bound by scheduler and allocated by device plugin.
The initial NodeLock, while recording the Pod that set the NodeLock, did not restrict which pod can release the lock and thus could lead to Pod B accidentally releasing the nodeLock set by Pod A, and further lead to multiple Pods pending. The PR #714 adds an owner check for the lock. Also, it allows skipping the owner check and releasing directly in case the lock expires. So after the PR #714 fix, the only way Pod B can release a lock set by Pod A is if the lock expires.
Subsequently, Issue #810 proposed that PR #714 would cause Pod B to be pending for a long time waiting for Pod A's NodeLock to be expired and released. Therefore, in order to reduce the waiting time, PR #1244 proposes to allow the NodeLock timeout to be set via environment variables, and change the original 5min to user configurable.
Problem Description
The NodeLock will become dangling due to the owner Pod being accidentally deleted between the time the scheduler sets a NodeLock with this Pod as the owner in the bind phase and the time device plugin finish allocating Pod and release nodelock.
All subsequent Pods that are scheduled to the same node are unable to release the dangling NodeLock by any means other than waiting for the NodeLock to expire.
What this PR does
This PR fixes the problem described above.
There are two main fixes:
goto ReleaseNodeLocksto the PatchPodAnnotations step in the Bind function implementation to clear the NodeLock when an error occursNodeLock can now be deleted by non-holders (Pods) under timeout or dangling conditions.
Problem Reproducing
Since PatchPodAnnotation does not release the NodeLock when an error occurs, if a pod is deleted immediately after the NodeLock is set at the time of binding, and a new pod needs to be bound to the same node within 5 minutes, the new pod will be pending, and the event
binding reject: node <nodename> has been locked within 5 minuteswill arise when inspecting pod viakubectl describeThe above problem can be reproduced stably using the following code. This code deletes the owner Pod as soon as a NodeLock is detected, so that the NodeLock will become dangling and persist on the node untile expire, and other Pods have to wait for its timeout.
Usage:
Check the UUID of GPUs on the node:
nvidia-smi -L # or nvidia-smi --query-gpu=uuid --format=csvThen deploy using Depolyment and limit the scheduling scope to the corresponding node with use-uuid:
第一个 Pod 被调度时,会在设置 NodeLock 时被删除,NodeLock 会保持在 Node 上没有被删除。然后 Deployment 自动创建出新的 Pod,这些新的 Pod 必定会处于 pending 状态,并且出现事件
binding reject: node <nodename> has been locked within 5 minutesWhen the first pod is scheduled and nodeLock has been set, the code will delete the pod immediately, and new pods will be pending, and the event
binding reject: node <nodename> has been locked within 5 minuteswill arise when inspecting pod viakubectl describeNotes
Special notes for your reviewer:
This PR only eliminate the problem of NodeLock becoming dangling due to an accidental deletion of a Pod, resulting in other Pods having to wait for the NodeLock to timeout and remain in a pending state.
There are still other scenarios that can cause a Pod to be pending due to NodeLock, such as having a large number of Pods on the same node that need to be bound at the same time, each of which needs to wait for the previous Pod to be processed before it can enter its own binding process.
Does this PR introduce a user-facing change?
No, this PR doesn't change any user-facing nor function signature.
However, we do change the name of a single parameter from
timeouttoskipNodeLockOwnerCheckin functionReleaseNodeLockfor better readability and comprehensibility