fix: Order container engines after nvidia-cdi-refresh.service - #2019
Open
ehfd wants to merge 1 commit into
Open
Conversation
Signed-off-by: Seungmin Kim <8457324+ehfd@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1979.
Reviewers: @cdesiniotis @henry118
Follow-up to #1979, split out at @cdesiniotis' request so that #1979 stayed scoped to device node creation.
Summary
nvidia-cdi-refresh.serviceis not ordered against any container engine, so a container started early at boot can reach the runtime before the service has created the control device nodes and generated the CDI specification. This adds a drop-in that ordersdocker,containerdandcri-oafter the service, and bounds the service's start time now that engines wait on it.Why This Exists
The service is
Type=oneshot,WantedBy=multi-user.target, with no ordering against any engine. A GPU container withrestart=always, or an earlydocker run, can therefore reach the runtime before the service'sExecStarthas run: the first container of the boot gets a specification generated before the device nodes exist, and subsequent ones do not. @henry118 identified this ordering as the packaging-level fix for that race in #1979 (comment).Why a drop-in rather than the unit
#1735 was about exactly this class of change: an ordering constraint shipped in the unit could not be removed by users, only worked around by overriding the whole unit. That is still true — verified on systemd 259, assigning an empty
Before=in a drop-in does not reset an in-unitBefore=list.Shipping the ordering as a vendor drop-in gives it an off switch. A same-named empty file in
/etc/systemd/system/nvidia-cdi-refresh.service.d/takes precedence over the vendor copy and cancels it, with no need to override the unit.The two cycle scenarios raised in review
@henry118 noted in #1979 that a
Before=can only produce a cycle if either docker is ordered before us, or a third unit sits between us, and judged both unlikely. I built both on Ubuntu 26.04 / systemd 259 / Docker 29.7.2 to see what actually happens if they do occur.A —
docker.servicehasBefore=nvidia-cdi-refresh.service:B — a third unit has
After=docker.serviceandBefore=nvidia-cdi-refresh.service:In both cases systemd resolves the cycle by deleting our job: docker and the third unit start normally, and
nvidia-cdi-refresh.servicedoes not run, leaving the CDI specification unrefreshed for that boot. Nothing fails loudly — the only signal is the journal line above.That is milder than #1735, where the job that got deleted belonged to the user's own unit, but it is the same class of problem, and it is the reason the ordering is shipped as a drop-in. A host that hits either scenario can drop an empty same-named file into
/etc/systemd/system/nvidia-cdi-refresh.service.d/and get the previous behaviour back. Verified against scenario B: with the vendor drop-in cancelled, no cycle is reported, every unit starts, and the specification is regenerated.Why TimeoutStartSec
Type=oneshotdefaults toTimeoutStartSec=infinity, and the unit sets no override. Once engines are ordered after it, anvidia-smi -Lthat hangs — a wedged GPU, a driver still initialising, a fabric manager not yet up — would delay them without bound. The drop-in therefore also setsTimeoutStartSec=90s, matching systemd'sDefaultTimeoutStartSec. On expiry the engines start and the specification is refreshed by the next trigger; the ordering isBefore=, notRequires=, so nothing depends on the service succeeding.Both settings live in the same drop-in, so removing the drop-in restores the previous behaviour exactly.
Behavior Changes
docker.service,containerd.serviceandcrio.servicegain an implicitAfter=nvidia-cdi-refresh.servicewhen they are part of the same transaction, so the first container at boot no longer races node creation and CDI generation./etc/systemd/system/.ExecConditionfails (no NVIDIA kernel module) are unaffected: the job completes immediately and adds no measurable boot delay.Implementation Summary
deployments/systemd/10-container-engines.conf, installed to<unitdir>/nvidia-cdi-refresh.service.d/.nvidia-cdi-refreshe2e suite to assert the drop-in is installed and that the resultingBefore=includesdocker.service.Verification
Ubuntu 26.04, systemd 259, Docker 29.7.2, 2x Tesla P100. The file as shipped in this PR, installed to
/usr/lib/systemd/system/nvidia-cdi-refresh.service.d/:No ordering cycles are reported by
systemd-analyze verify, and the unit still loads.Cancelling it as a user would, with a same-named empty drop-in under
/etc:For scale, the service took 10.758s wall clock at boot on this two-GPU host, almost all of it the cold
nvidia-smi -L; that is the delay the ordering introduces before the engines start.