Skip to content

mount layers the destination registry already holds - #990

Merged
mzihlmann merged 3 commits into
mainfrom
mz989-cross-repo-mount
Aug 12, 2026
Merged

mount layers the destination registry already holds#990
mzihlmann merged 3 commits into
mainfrom
mz989-cross-repo-mount

Conversation

@mzihlmann

@mzihlmann mzihlmann commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #989

A registry will copy a blob between its own repositories for free, but only if it is told which repository already holds it, and kaniko loses that the moment it copies a layer locally. With --cache that costs an upload per layer on every build: the layer goes to the cache repo as it is built, and the same bytes go up again inside the image a moment later. A cache hit pays it too, uploading a layer that was just read out of the cache repo. Both repositories usually sit on the registry being pushed to, so the bytes are already there.

pkg/mounts keeps a note of which repositories hold which layer and re-tags them on the way into remote.Write. Keyed by digest rather than carried on the layer, because the relabel in convertLayerMediaType replaces the layer object while keeping its bytes, and a rewrite that does change the bytes changes the digest and simply misses. Only repositories this build read from or wrote to are recorded, since remote.Write fails a push outright if the token request for a mount source is refused.

Measured against a local registry with the cache repo and destination on it, the built layer is mounted instead of uploaded on both a cache miss and a cache hit, leaving only the config blob to upload. Behind FF_KANIKO_CROSS_REPO_MOUNT, default off.

The first commit is separate and changes no behaviour: the dryrun plan now accounts for every layer each push sends, so the effect of this and the remaining producers in #989 shows up as a plan diff rather than only in registry logs.

Summary by CodeRabbit

  • New Features

    • Added optional cross-repository layer mounting to reduce redundant image uploads.
    • Added the FF_KANIKO_CROSS_REPO_MOUNT feature flag, disabled by default.
    • Build plans now show clearer, indented fetch, stream, unpack, upload, mount, and cache-status steps.
  • Bug Fixes

    • Improved ordering of build commands and associated cache results in rendered plans.
  • Documentation

    • Documented the feature flag, default behavior, and planned activation timeline.

@mzihlmann mzihlmann added the performance Build speed or resource usage label Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds FF_KANIKO_CROSS_REPO_MOUNT, tracks layer origins by digest, mounts eligible layers during pushes, updates build-plan rendering, pins golden-test images, and adds cross-repository mount coverage.

Changes

Cross-repository mounting

Layer / File(s) Summary
Feature flag and provenance tracking
README.md, pkg/config/featureflags.go, pkg/mounts/mounts.go, pkg/cache/cache.go, integration/images.go
Adds the feature flag and records image repositories by layer digest.
Push integration and plan generation
pkg/executor/push.go, pkg/executor/build.go
Wraps eligible layers for same-registry mounting and emits MOUNT or UPLOAD plan steps.
Golden coverage
golden/golden_test.go, golden/testdata/test_issue_mz989/*
Adds scenarios for plain pushes, stored images, cache misses, cache hits, rekeyed layers, and expected mounts or uploads.

Build-plan output updates

Layer / File(s) Summary
Stage and cache event rendering
golden/testdata/test_issue_mz195/*, golden/testdata/test_issue_mz334/*, golden/testdata/test_issue_mz480/*, golden/testdata/test_issue_mz936/*
Updates digest references, explicit stage operations, cache events, and push uploads.
Command and cache ordering
golden/testdata/test_issue_mz333/*, golden/testdata/test_issue_mz338/*, golden/testdata/test_issue_mz487/*, golden/testdata/test_issue_mz703/*, golden/testdata/test_issue_mz791/*, golden/testdata/test_issue_mz813/*, golden/testdata/test_issue_mz822/*, golden/testdata/test_unittests/*
Updates stream, unpack, command, and cache-entry ordering and indentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels: enhancement, caching, tests

Suggested reviewers: 0hlov3, babs, bobdu, nejch

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The implementation covers registry-cache provenance and push mounting, but it does not address the linked issue's cache-dir and OCI-warmer provenance requirements. Implement provenance recording and loading for cache-dir images and OCI-warmed images, or link the remaining work to a separate issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: mounting layers already present in the destination registry.
Description check ✅ Passed The description explains the problem, solution, feature flag, issue link, and plan changes, although template checklist sections are omitted.
Out of Scope Changes check ✅ Passed The README, integration setting, golden fixtures, feature flag, mounting logic, and plan updates support the stated cross-repository mounting objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mz989-cross-repo-mount

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
golden/testdata/test_issue_mz989/test.go (1)

5-53: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover the flag-on mount contract with same-registry and flag-off cases.

golden/golden_test.go sets FF_KANIKO_CROSS_REPO_MOUNT=1 globally, so the plain comment on line 15 is already misleading. Split the current upload cases into explicit FF_KANIKO_CROSS_REPO_MOUNT=false baselines, then add FF_KANIKO_CROSS_REPO_MOUNT=true scenarios with the base repo present on registry example.com; those should record mounts for plain/cache_hit and preserve uploads for rekeyed. Cross-registry source repos should only verify uploads, not mounts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@golden/testdata/test_issue_mz989/test.go` around lines 5 - 53, Update the
golden test cases in Tests to explicitly set FF_KANIKO_CROSS_REPO_MOUNT=false
for baseline upload scenarios, then add flag-enabled cases with the base
repository available on example.com that expect mounts for plain and cache_hit
while keeping rekeyed uploads. Include a cross-registry source-repository case
that verifies uploads only, and revise misleading comments and plan fixtures
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/executor/push.go`:
- Around line 321-322: Gate the mounts.RecordImage call in
pkg/executor/push.go:321-322 so pushed images are recorded only when
config.FF.CrossRepoMount is enabled. Apply the same conditional guard to the
cache image recording call in pkg/cache/cache.go:85; both sites should avoid
updating provenance when the feature is disabled.

---

Nitpick comments:
In `@golden/testdata/test_issue_mz989/test.go`:
- Around line 5-53: Update the golden test cases in Tests to explicitly set
FF_KANIKO_CROSS_REPO_MOUNT=false for baseline upload scenarios, then add
flag-enabled cases with the base repository available on example.com that expect
mounts for plain and cache_hit while keeping rekeyed uploads. Include a
cross-registry source-repository case that verifies uploads only, and revise
misleading comments and plan fixtures accordingly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a4733ec-08b6-43f6-a796-45baa0694d32

📥 Commits

Reviewing files that changed from the base of the PR and between f0123f8 and b4c95a3.

📒 Files selected for processing (61)
  • README.md
  • golden/golden_test.go
  • golden/testdata/test_issue_mz195/Dockerfile
  • golden/testdata/test_issue_mz195/plans/fourth
  • golden/testdata/test_issue_mz195/plans/noise
  • golden/testdata/test_issue_mz195/plans/normal
  • golden/testdata/test_issue_mz195/plans/push
  • golden/testdata/test_issue_mz333/plans/plan
  • golden/testdata/test_issue_mz334/plans/cached
  • golden/testdata/test_issue_mz334/plans/eliminated
  • golden/testdata/test_issue_mz334/plans/inferred
  • golden/testdata/test_issue_mz334/plans/plan
  • golden/testdata/test_issue_mz338/plans/plan
  • golden/testdata/test_issue_mz480/Dockerfile
  • golden/testdata/test_issue_mz480/plans/final
  • golden/testdata/test_issue_mz480/plans/final_test
  • golden/testdata/test_issue_mz487/plans/plan
  • golden/testdata/test_issue_mz703/plans/legacy_stop_after_miss
  • golden/testdata/test_issue_mz703/plans/probe_after_miss
  • golden/testdata/test_issue_mz791/plans/resolved_one
  • golden/testdata/test_issue_mz791/plans/resolved_two
  • golden/testdata/test_issue_mz791/plans/unresolved_one
  • golden/testdata/test_issue_mz791/plans/unresolved_two
  • golden/testdata/test_issue_mz813/plans/resolved_one
  • golden/testdata/test_issue_mz813/plans/resolved_two
  • golden/testdata/test_issue_mz813/plans/unresolved_one
  • golden/testdata/test_issue_mz813/plans/unresolved_two
  • golden/testdata/test_issue_mz822/plans/resolved_one
  • golden/testdata/test_issue_mz822/plans/resolved_two
  • golden/testdata/test_issue_mz822/plans/unresolved
  • golden/testdata/test_issue_mz936/Dockerfile
  • golden/testdata/test_issue_mz936/plans/push
  • golden/testdata/test_issue_mz936/plans/shared
  • golden/testdata/test_issue_mz936/plans/streamed
  • golden/testdata/test_issue_mz989/Dockerfile
  • golden/testdata/test_issue_mz989/plans/cache_hit
  • golden/testdata/test_issue_mz989/plans/cache_miss
  • golden/testdata/test_issue_mz989/plans/plain
  • golden/testdata/test_issue_mz989/plans/rekeyed
  • golden/testdata/test_issue_mz989/plans/stored
  • golden/testdata/test_issue_mz989/test.go
  • golden/testdata/test_unittests/plans/alias
  • golden/testdata/test_unittests/plans/copyfrom_final
  • golden/testdata/test_unittests/plans/final_wo_deps_buzz
  • golden/testdata/test_unittests/plans/final_wo_deps_final
  • golden/testdata/test_unittests/plans/final_wo_deps_fizz
  • golden/testdata/test_unittests/plans/global_arg
  • golden/testdata/test_unittests/plans/global_arg_stage1
  • golden/testdata/test_unittests/plans/multiple_copy
  • golden/testdata/test_unittests/plans/two_copyfrom_and_arg_base
  • golden/testdata/test_unittests/plans/two_copyfrom_and_arg_final
  • golden/testdata/test_unittests/plans/two_copyfrom_final
  • golden/testdata/test_unittests/plans/wo_copyfrom_dev
  • golden/testdata/test_unittests/plans/wo_copyfrom_final
  • golden/testdata/test_unittests/plans/wo_copyfrom_prod
  • integration/images.go
  • pkg/cache/cache.go
  • pkg/config/featureflags.go
  • pkg/executor/build.go
  • pkg/executor/push.go
  • pkg/mounts/mounts.go

Comment thread pkg/executor/push.go Outdated
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.42623% with 19 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/executor/build.go 80.55% 7 Missing and 7 partials ⚠️
pkg/mounts/mounts.go 90.24% 2 Missing and 2 partials ⚠️
pkg/executor/push.go 83.33% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@mzihlmann mzihlmann changed the title Mount layers the destination registry already holds mount layers the destination registry already holds Aug 9, 2026
@mzihlmann
mzihlmann force-pushed the mz989-cross-repo-mount branch from b4c95a3 to 69e9107 Compare August 9, 2026 16:11
@mzihlmann

Copy link
Copy Markdown
Collaborator Author

Gated both recording sites on FF_KANIKO_CROSS_REPO_MOUNT in 69e9107. With the flag off the map is never written, so a default build does no work for a feature it is not using.

Not taking the golden suggestion. It rests on golden/golden_test.go setting the flag, which it does not, that is integration/images.go and a different suite. Goldens run with the flag off.

The deeper reason is that no golden can record a MOUNT today whatever the flag says. The plan renderer deliberately makes no decision of its own, it prints what the build already decided, and the build decides mountability at push time from state that does not exist when the plan is rendered. So every layer reads UPLOAD, which is the honest account of what kaniko knows before it runs. Teaching the plan about mounts means duplicating the decision in a second place that then has to stay in step with the first, which is a worse trade than the missing coverage.

The byte claim is backed by registry access logs in the PR description rather than by a fixture. Turning that into an assertion needs a counting proxy in front of the integration registry, which is tracked in #989.

@mzihlmann
mzihlmann force-pushed the mz989-cross-repo-mount branch 2 times, most recently from 3cc7aa5 to a6aa597 Compare August 9, 2026 17:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/executor/build.go`:
- Around line 1226-1241: Update mountSource to accept the registry-mapping
resolution context and resolve both the source repository and each destination
before comparing them. Use the resolved repository contexts with
mounts.Mountable, and return the resolved mount source so plan action selection
uses mapped registries and can choose MOUNT instead of UPLOAD.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4dde1f73-665b-4bb8-b232-12dc5d911776

📥 Commits

Reviewing files that changed from the base of the PR and between 69e9107 and a6aa597.

📒 Files selected for processing (7)
  • golden/testdata/test_issue_mz989/plans/cache_hit
  • golden/testdata/test_issue_mz989/plans/cache_miss
  • golden/testdata/test_issue_mz989/plans/rekeyed
  • golden/testdata/test_issue_mz989/test.go
  • pkg/executor/build.go
  • pkg/executor/push.go
  • pkg/mounts/mounts.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • golden/testdata/test_issue_mz989/plans/cache_miss
  • golden/testdata/test_issue_mz989/plans/cache_hit
  • golden/testdata/test_issue_mz989/plans/rekeyed
  • pkg/executor/push.go

Comment thread pkg/executor/build.go Outdated
@mzihlmann
mzihlmann force-pushed the mz989-cross-repo-mount branch 8 times, most recently from 50dfe91 to 5fde9d8 Compare August 9, 2026 19:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/executor/build.go`:
- Around line 1171-1196: In RenderStages, compute the same shouldTakeSnapshot
condition used by stageBuilder.build before planning cache actions. Gate cache
upload setup, planned digest assignment, and the pushedLayer append on that
condition; also require the retrieved cache image to contain a layer before
appending a pushed layer, so single-snapshot plans emit only the final snapshot
and never create empty-cache push actions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9bc8cf5c-4c90-4d19-805a-10c1d0b7a215

📥 Commits

Reviewing files that changed from the base of the PR and between 6803ca4 and 5fde9d8.

📒 Files selected for processing (4)
  • golden/golden_test.go
  • golden/testdata/test_issue_mz989/test.go
  • pkg/executor/build.go
  • pkg/mounts/mounts.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • golden/testdata/test_issue_mz989/test.go

Comment thread pkg/executor/build.go
@mzihlmann
mzihlmann force-pushed the mz989-cross-repo-mount branch 2 times, most recently from 38934f9 to fc8b4a0 Compare August 9, 2026 20:07
@mzihlmann
mzihlmann force-pushed the mz989-cross-repo-mount branch from fc8b4a0 to fb1dc8e Compare August 9, 2026 20:15
@mzihlmann

mzihlmann commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator Author

Performance measurement

Three configs, five rounds, config order shuffled within every round and the slot recorded.

  • main 447281326, no cross-repo mount code
  • off this branch, flag unset
  • on this branch, FF_KANIKO_CROSS_REPO_MOUNT=1

Against a private registry over a VPN link, so bytes cost what they cost in production. Bytes are the host interface TX delta, since a remote registry has no access log to read.

FROM <private registry>/<a real OCI image, 6 layers, ~65 MB>
RUN dd if=/dev/urandom of=/big bs=1M count=50

The layer is incompressible, so it costs a real 50 MB upload rather than compressing away. Destination and cache repo are both on that registry, which is the only case where a mount is possible.

Two scenarios:

  • cache miss, cache repo created for that run, so the layer is built, pushed to the cache repo, and then pushed again inside the image
  • cache hit, cache repo warmed once before the campaign, so the layer is read from it and then pushed inside the image

Every run pushes to a repository that did not hold the blob beforehand, otherwise checkExistingBlob short-circuits the upload and there is nothing left to measure.

Bytes sent, median of five:

scenario main off on
cache miss 113 MB 114 MB 57 MB
cache hit 58 MB 58 MB 1 MB

Wall clock, median of five:

scenario main off on
cache miss 16.9s 18.4s 13.3s
cache hit 16.2s 16.1s 12.7s

About 21% off the total build for the miss and 22% for the hit, on a build where the upload is one of several costs. The on sample ranges do not overlap main or off in either scenario.

The miss halves because the layer goes to the cache repo as it is built and then goes up again inside the image, and the second send becomes a mount. The hit drops to the config blob because the layer was read out of the cache repo minutes earlier and the push mounts it from there.

A no-cache control, run against a local registry:2, was unchanged across all three configs. With nothing written to a second repository there is nothing to mount, and the flag costs nothing.

Bound on the claim

The cache hit only wins when the cache entry and the image disagree on media type vendor.

A pilot against an older docker-schema2 base produced a docker-typed image and a docker-typed cache entry. convertLayerMediaType then has nothing to convert, the cached layer keeps the reference remote.Image gave it, and remote.Write mounts it unaided. All three configs uploaded the same 980 bytes on a hit, with the mount attempt visible in the registry log on main too.

So against a docker-media-type base the flag adds nothing to a cache hit. It still halves the miss, because that layer is built locally and never had a reference to lose. Modern base images are OCI, which is the configuration measured above.

Caveats

The shuffle did not balance slots. off landed mostly in positions 1 and 2, on mostly in position 3, and mean time by slot falls from 16.0s to 15.0s across the three. That biases toward on by a few tenths of a second, well short of the gap, but a balanced design would have been better than a random one.

Not measured: several destinations on one registry, where the first push becomes the source for the rest, and cross-registry pushes, where the mount is refused and the bytes go up regardless.

@mzihlmann
mzihlmann requested review from 0hlov3, BobDu, babs and nejch August 9, 2026 21:47
Comment thread pkg/executor/push.go

@0hlov3 0hlov3 left a comment

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.

LGTM

@mzihlmann
mzihlmann merged commit 106727f into main Aug 12, 2026
13 checks passed
@mzihlmann
mzihlmann deleted the mz989-cross-repo-mount branch August 12, 2026 09:22
@coderabbitai coderabbitai Bot mentioned this pull request Aug 17, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Build speed or resource usage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layers kaniko already pulled are uploaded instead of mounted once their source is lost

2 participants