Renew the registry token when a layer download fails mid-import - #279
Open
100milliongold wants to merge 1 commit into
Open
Renew the registry token when a layer download fails mid-import#279100milliongold wants to merge 1 commit into
100milliongold wants to merge 1 commit into
Conversation
The token is fetched once before the layers are downloaded, and its lifetime is set by the registry: 600 s on nvcr.io, 300 s on Docker Hub. A blob request answers 307 and the transfer itself runs against a signed CDN URL, so what matters is when each layer's redirect is requested. On an image with many layers, a request issued near the end of a long download lands after the token has expired and fails with 401. Neither retry layer can recover from that. curl --retry does not retry a 401, and the parallel --retries pass re-reads the same -K file, which nothing has refreshed. Renew the token and resume instead. Layers already in the cache exit early on the next pass, so the download continues where it stopped, and resuming only while the cache keeps growing leaves failures with any other cause to stop as they do today. Signed-off-by: Jea-Eok-Kim <gadian88@gmail.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.
Fixes #278.
The problem
docker::_downloadauthenticates once (src/docker.sh:145) and turns the result into a-Kfilethat every layer request reuses. The token's lifetime belongs to the registry: 600 s on
nvcr.io,300 s on Docker Hub. A blob request answers
307and the transfer runs against a signed CDN URL, sothe token is only needed at the moment each layer's redirect is requested. On an image with many
layers, a request issued near the end of a long download happens after the token has expired and
fails with
401, and the import aborts with no image produced.Neither retry layer recovers from it:
curl --retrydoes not retry a401, and theparallel --retries 2pass re-reads the same unrefreshed-Kfile.The change
Wrap the layer download in a loop that renews the token and resumes when a pass fails. Layers
already in the cache exit early on the next pass (
src/docker.sh:103), so the download continueswhere it stopped rather than starting over.
Resuming is allowed only while the cache keeps growing. That keeps the loop bounded by the number of
layers without adding a retry count, and a download failing for any other reason still stops with an
error, as it does today.
No new configuration, and nothing changes on the success path: a run that does not fail
authenticates once, exactly as before.
Testing
Enroot 4.2.1 from the release
.deb, with thisdocker.shmounted over/usr/lib/enroot/docker.sh,importing
docker://library/python:3.12(7 layers, 900 MB squashfs). Waiting for a real token toexpire is not reproducible, so the token file was overwritten with an invalid one three seconds into
the layer download, which produces the same
401the issue reports.Authentication succeededThe unpatched run fails with the reported symptom:
The patched run recovers:
The
ENROOT_MAX_CONNECTIONS 10row is not a null result. With ten connections and seven layers,every redirect is requested before the token is invalidated, so the run cannot be affected however
long the transfers take afterwards. That is the same property the issue describes: what matters is
when a layer's
307is requested, not how long the image takes to download. It also means the bugneeds more layers than connections, which is the shape of the reported failure (35 layers, 10
connections by default).
The last row is the bound: with the CDN unreachable, the second pass adds nothing to the cache and
the import stops instead of renewing forever.
Raising
ENROOT_MAX_CONNECTIONSis not a workaroundSince the failure needs more layers than connections, the obvious question is whether opening more
connections avoids it. It does not, and the last two rows of the table are that case: connections
above the layer count, every
307requested before the token was invalidated, and the import stilldied with seven
401s on 4.2.1. What carried the dead token was the retry. A transfer that fails forany transient reason is re-run by
parallel, and the re-run starts with a fresh307.Three more reasons it is not a knob to turn:
ENROOT_MAX_CONNECTIONShas taken effect, so "set it above the layer count" is not something acaller can act on.
splits the available bandwidth and runs into whatever concurrency limit the registry enforces.
nvcr.ioreturned600 379 600 378 98 598 600 596 96 595seconds of remaining validity, becausethe server hands out cached tokens. With 96 s left, the first layer's redirect can already be past
expiry, and the connection count has no bearing on that.
The
patched / 10row is worth reading precisely: the injected CDN outage outlives the renewal, sothe second pass adds nothing to the cache and the import stops with the bound message. The renewal
is not a way to survive an unreachable registry, only an expired token.
Interaction with the existing
--retriesparallel --retries 2runs a failing job twice before giving up, locally as well as over--sshlogin; a joblog on this machine confirms two attempts per job. Those attempts happen beforecontrol returns to the caller, so on an expiry each still-pending layer sends one more
401beforethe token is renewed. That is visible in the counts above: three connections and seven layers give
four
401s, or two attempts each for the two layers that had not been requested yet.The cost is bounded by the number of pending layers and it buys a change that does not touch the
worker. Refreshing inside the worker would avoid the extra attempts, at the price described below.
An alternative I did not take
The refresh could live inside
docker::_download_extract, next to the failing request, with a lockon the token file so that
ENROOT_MAX_CONNECTIONSworkers do not each request a token. It needsmore plumbing: that function receives only curl arguments, so the registry, user, and manifest URL
would have to be passed in, and the token path could not be recomputed from
$$because the workersare separate processes. Renewing in the caller needs none of that and reuses the existing resume
behavior. Happy to switch if you prefer the refresh closer to the request.