Commit Graph
23 Commits
Author SHA1 Message Date
Maksym Pavlenko a35da471f3 unpack: don't drop topHalf errors in parallel mode
When a layer fails to prepare during parallel unpack we break out of the
launch loop but never return the error, so unpack() can report success and
label the image with a chainID that was never created. Keep the error and
return it once the already queued layers have been drained, so those still
commit as they would in sequential mode. Also end the layer's tracing span,
which leaked on this path.

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
2026-08-05 09:43:56 -07:00
Maksym Pavlenko 257a5900b0 core/unpack: detect staged layers via read-only mounts
Replace the snapshots.ErrAlreadyStaged sentinel error with a
read-only-mounts signal: a snapshotter (e.g. erofs serving a layer
content cache hit) now returns Prepare mounts normally, with no
error, when the layer content is already staged into the active
snapshot. The unpacker's isStaged helper checks the last mount's
ReadOnly() to decide whether to skip fetch+apply and just commit.

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
2026-07-31 09:18:16 -07:00
Maksym Pavlenko 1e001e6dfe erofs: make the layer content cache work with parallel unpack
The cache used to serve a hit by committing the layer during Prepare and
returning ErrAlreadyExists, which is incompatible with parallel unpacking:
the "rebase" capability defers the parent to Commit time, so a
commit-at-Prepare layer ends up parentless. The erofs snapshotter therefore
had to disable rebase whenever the cache was enabled, making every cache
*miss* fall back to a fully sequential (slower-than-cold) pull.

Introduce snapshots.ErrAlreadyStaged, returned from Prepare to mean "the
active snapshot's content is staged; skip the layer download and apply, but
still Commit it" (where the parent is applied). Unlike ErrAlreadyExists it
does not end the layer's lifecycle. The unpacker handles it by emitting a
status whose bottom half runs the normal serialized commit (rebasing the
parent in), and the metadata snapshotter threads it through Prepare like a
normal active snapshot. The erofs cache hit now stages the blob and returns
ErrAlreadyStaged instead of committing, so it can advertise "rebase"
unconditionally: hits skip download+conversion and misses stay parallel.

Also promote the "rebase" capability string to snapshots.RebaseCap (shared by
the unpacker, erofs, and overlay).

Follow up:  fetch is range-based, so only a fully cached contiguous
prefix skips downloads; a miss in a lower layer still pulls everything above
it. Per-layer fetch-skip is left as a follow-up.

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
2026-07-31 09:18:15 -07:00
Maksym Pavlenko 728093bdca snapshots/erofs: source pre-converted layers from a content cache
Add a layer_content_cache option to the erofs snapshotter: a directory of
pre-converted, diffID-keyed erofs layer blobs (in production a read-only
mountpoint-s3/FUSE mount) that the snapshotter sources layers from on pull
instead of downloading and converting them per node.

On an image-layer extraction Prepare, if the layer's blob is present in the
cache the snapshotter stages it as a symlink, commits it as the target
chainID in the same transaction, and returns ErrAlreadyExists. This uses the
existing remote-snapshot protocol, so containerd skips both the layer
download and the tar->erofs conversion; no core changes are needed. Any miss
(cache disabled, no snapshot.ref/diff-id labels, blob absent, unreadable
cache) falls through to the normal path, so pulls keep working.

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
2026-07-21 09:43:41 -07:00
Henry Wang 3382fb7162 Tweak mount info for overlayfs in case of parallel unpack
Signed-off-by: Henry Wang <henwang@amazon.com>
2026-03-24 18:43:07 +00:00
Hasan Siddiqui ca7461cbe1 Propagate diff ID and parent chain ID via labels in Prepare RPC
Signed-off-by: Hasan Siddiqui <hasiddiqui@google.com>
2026-03-23 15:46:10 +00:00
Sebastiaan van Stijn 3723a67092 core: modernize: rangeint
go install golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest
    modernize -rangeint -fix ./...

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-15 14:56:35 +01:00
Cindia-blue fb295b9d4f Tracing: add spans in task/metadata and sandbox paths
This PR adds opt-in tracing spans/attributes in CRI image pull and selected sandbox-related paths to improve debugging and correlation (e.g., sandbox.id/pod metadata). If maintainers prefer a smaller diff, I’m happy to split this into a pull-only PR plus follow-ups.
• follow-up after pull-only PR
• focuses on task/metadata/sandbox/cni setup spans

Signed-off-by: Cindy Li <cindyli@pinterest.com>
2026-01-09 00:05:37 +00:00
ningmingxiao 508f8cac6d add some log if blob is skipped to download
Signed-off-by: ningmingxiao <ning.mingxiao@zte.com.cn>
2025-12-08 11:11:52 +08:00
Henry Wang 0198b87fcf Implement parallel unpack
Signed-off-by: Henry Wang <henwang@amazon.com>
2025-10-24 17:54:26 +00:00
Derek McGowan d9bb00578e Add more error details when unpack fails to extract
Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-06-02 22:49:59 -07:00
Derek McGowan 00edba6aa8 Remove internal interface from unpacker interface
Avoid using internal interfaces for exported types and always prefer
using interfaces over structs to limit unused imports.

Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-05-28 14:23:24 -07:00
Danny Canter 33ee060a35 Use Go 1.19 atomic wrappers everywhere
We've long been able to use these and they have a couple
great benefits:

1. Forces you to always access them atomically. With the pointer
variants it's completely valid to access the regular ol' int64/uint32
etc. without using the atomic.* methods. These wrappers don't provide
access to the underlying value so it forces correct usage always.

2. Conveys intent much better. Seeing the type be atomic.Int32 immediately
lets the reader know that this var will be used in a concurrent context,
and we no longer need comments like "this MUST be accessed atomically"
or similar.

Signed-off-by: Danny Canter <danny@dcantah.dev>
2025-05-03 03:46:20 -07:00
Derek McGowan cdd7ec40db Support configuring custom media types for unpack
Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-04-23 21:19:07 -07:00
Derek McGowan d52386ab98 Add check for rootfs type and only unmarshal relevant parts
Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-04-22 23:24:37 -07:00
Kazuyoshi Kato f38df49348 Merge pull request #11227 from dmcgowan/unpack-snapshotter-exports
Add snapshotter exports to unpack platform
2025-01-09 00:23:46 +00:00
Derek McGowan 63f6047282 Add snapshotter exports to unpack platform
Allows the unpack operation to be optimized based on the specific
snapshotter configuration.

Signed-off-by: Derek McGowan <derek@mcg.dev>
2025-01-07 16:04:44 -08:00
Jin Dong d156d3df96 Benchamrk chainID calculation in unpack
Signed-off-by: Jin Dong <djdongjin95@gmail.com>
2025-01-05 15:18:04 +00:00
Jin Dong 95f45541e4 Avoid duplicated chain ID calculation in unpack
This PR optimizes the chain ID calculation in unpack so we
only (pre-)calculate the chain ID for layer_i exactly once,
by calling `identity.ChainIDs(diffIDs)`.

Currently in `unpack` for every layer_i, we calculate the chain id
of layer_i-1 and layer_i *repeatedly*. Because each
`identity.ChainID(diffIDs)` call involves:

1. Copy `diffIDs` to a new slice
2. Calculate chain ID for every index (by calling `ChainIDs(diffIDs)`)
3. Return the final one as result of `identity.ChainID(diffIDs)`.

This means, given an image with N layers, for every layer_i:

1. we create 2 new slices and copy the diffIDs sofar;
2. we're recalculating all chain IDs from layer_0 to layer_i-1 twice;
3. in total, the chain ID for layer_i is calculated 2 * (N - layer_i) times;

Signed-off-by: Jin Dong <djdongjin95@gmail.com>
2025-01-03 03:47:13 +00:00
Austin Vazquez bee64b2b93 Remove loop variable copies
Signed-off-by: Austin Vazquez <macedonv@amazon.com>
2024-12-23 23:14:49 -07:00
Saket Jajoo d7f83034c2 Fix the race condition during GC of snapshots when client retries
When an upstream client (e.g. kubelet) stops or restarts, the CRI
connection to the containerd gets interrupted which is treated as a
cancellation of context which subsequently cancels an ongoing operation,
including an image pull. This generally gets followed by containerd's
GC routine that tries to delete the prepared snapshots for the image
layer(s) corresponding to the image in the pull operation that got
cancelled. However, if the upstream client immediately retries (or
starts a new) image pull operation, containerd initiates a new image
pull and starts unpacking the image layers into snapshots. This may
create a race condition: the GC routine (corresponding to the failed
image pull operation) trying to clean up the same snapshot that the new
image pull operation is preparing, thus leading to the "parent snapshot
does not exist: not found" error.

Race Condition Scenario:
Assume an image consisting of 2 layers (L1 and L2, L1 being the bottom
layer) that are supposed to get unpacked into snapshots S1 and S2
respectively.

During an image pull operation, containerd unpacks(L1) which involves
Stat()'ing the chainID. This Stat() fails as the chainID does not
exist and Prepare(L1) gets called. Once S1 gets prepared, containerd
processes L2 - unpack(L2) which again involves Stat()'ing the chainID
which fails as the chainID for S2 does not exist which results in the
call to Prepare(L2). However, if the image pull operation gets
cancelled before Prepare(L2) is called, then the GC routine tries to
clean up S1.

When the image pull operation is retried by the upstream client,
containerd follows the same series of operations. unpack(L1) gets
called which then calls Stat(chainID) for L1. However, this time,
Stat(L1) succedes as S1 already exists (from the previous image pull
operation) and thus containerd goes to the next iteration to
unpack(L2). Now, GC cleans up S1 and when Prepare(L2) gets called, it
returns back the "parent snapshot does not exist: not found" error.

Fix:
Removing the "Stat() + early return" fixes the race condition. Now
during the image pull operation corresponding to the client retry,
although the chainID (for L1) already exists, containerd does not
return early and goes on to Prepare(L1). Since L1 is already prepared,
it adds a new lease to S1 and then returns `ErrAlreadyExists`. This
new lease prevents GC from cleaning up S1 when containerd processes
L2 (unpack(L2) -> Prepare(L2)).

Fixes: https://github.com/containerd/containerd/issues/3787

Signed-off-by: Saket Jajoo <saketjajoo@google.com>
2024-10-02 22:10:15 +00:00
Derek McGowan 681a083fab Update unpacker to always fetch all
When a set of layers are provided to the unpacker, then the unpacker
should still fetch them regardless of whether they will be used for
unpack. The image handler filters are responsible for removing content
which is not intended to be fetched. Currently there is no way to use an
unpacker and also fetch all platforms.

Signed-off-by: Derek McGowan <derek@mcg.dev>
2024-05-09 21:59:42 -07:00
Derek McGowan f46aea6187 Move transfer and unpack packages
Packages related to transfer and unpacking provide core interfaces which
use other core interfaces and part of common functionality.

Signed-off-by: Derek McGowan <derek@mcg.dev>
2024-02-07 22:40:15 -08:00