Skip to content

fix(build, buildah): stop builds failing when a cached layer vanishes - #327

Draft
reyreavman wants to merge 7 commits into
mainfrom
fix/test/buildah-ci-flakes
Draft

reyreavman wants to merge 7 commits into
mainfrom
fix/test/buildah-ci-flakes

Conversation

@reyreavman

Copy link
Copy Markdown
Collaborator

Summary

Native Buildah e2e jobs on the shared CI host failed for reasons unrelated to the code under test: builds ran past the suite timeout, sibling specs saw "image not known" / "layer not known" from each other's cleanup, and the kube-run cancel spec killed werf before its image had finished building. Alongside the test and CI changes, a Dockerfile build with the Buildah backend no longer fails when another process removes a layer from the local storage while imagebuildah looks for a cache hit.

What

werf (Buildah backend)

  • A Dockerfile build whose local cache lookup fails with "layer not known" is rebuilt once with the local cache disabled instead of failing; a warning line "Local image cache lookup hit a layer that no longer exists, rebuilding without cache" is printed.
  • "image not known" is not retried: it also fires for a genuinely absent FROM <local-image>.
  • UNVERIFIED: the retry path itself; only the error predicate is unit-tested. A Linux CGO build with a concurrent buildah rmi during a build would settle it.

e2e test harness

  • Every ginkgo process in test/e2e/build gets its own rootless containers-storage (graph root and run root) under a per-process dir in HOME, removed after the suite; on non-Linux nothing changes.
  • Subprocess output is forwarded to GinkgoWriter as it arrives, so a spec that hits the suite timeout still shows what werf printed.
  • RmiByRepoRef lists only images under its own repo and treats an image that vanished before rmi as removed.
  • The kube-run cancel specs start their one-minute cancel window after "Executing into pod" instead of at process start; their SpecTimeout is 10 minutes.
  • UNVERIFIED: Native Buildah and kube-run specs were not run locally (macOS host, unreachable kind node); CI on this branch is the check.

CI

  • e2e_complex, e2e_extra and their daily *_per_k8s twins share one non-cancelling concurrency group (buildah-e2e, queue: max), so only one Buildah job runs on the host at a time and the rest wait rather than get cancelled. A PR's e2e_complex and e2e_extra now run one after the other.
  • Those jobs run ginkgo with --procs=6 (was 15 via -p) and --flake-attempts=2; e2e_simple gets --flake-attempts=2 only.
  • Daily e2e jobs pass an explicit ginkgo --timeout=110m below the 120-minute job limit; before, ginkgo's default one-hour timeout applied.
  • Daily e2e_complex_per_k8s and e2e_extra_per_k8s build werf without -race (coverage kept); the other daily jobs still build with it.
  • Every e2e job echoes loadavg before the tests, so a timed-out job still reports host load.

Why

Native Buildah resolves the rootless storage from XDG_DATA_HOME / XDG_RUNTIME_DIR, and the test harness never set them, so 15 ginkgo procs per job, times three or four concurrently scheduled jobs on the same runner host, all worked on one image graph. The observed cost: a spec that takes 14 minutes on a quiet host runs past 85 minutes under that contention, sibling specs' rmi removed layers underneath running builds, and every red run was retried blindly, hiding real regressions. The daily run made it worse by shipping a -race werf binary into the slowest jobs with ginkgo's implicit one-hour timeout.

A werf-side --buildah-graph-root flag was the alternative to setting the XDG variables in the tests; rejected because the tests can isolate themselves without adding user-facing surface.

RunCommandWithOptions buffered the whole werf output and wrote it to
GinkgoWriter only after Wait() returned. When a spec hit the suite
timeout mid-build the report contained nothing from werf, so the stage
it was stuck on was invisible. Tee every write into GinkgoWriter so a
timed-out spec still shows what the subprocess printed.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Native Buildah resolves the rootless graph root from XDG_DATA_HOME and
the run root from XDG_RUNTIME_DIR, so all parallel specs of every e2e
job on a host shared one image graph and one mount table. Sibling specs
deleting their project images raced with builds in progress ("layer not
known", "image not known") and every build contended for the same
storage locks.

Point both at a per-process dir under HOME in the build suite and remove
it via buildah unshare after the suite, since rootless storage holds
files owned by mapped sub-UIDs.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The helper listed the whole containers storage and then removed every
match one by one; a sibling spec removing its own image between the two
steps failed the caller with "image not known". Filter on the buildah
side by reference and treat an image that is already gone as removed.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The cancel specs waited one minute from process start for the script's
first log line and then sent SIGTERM. On a loaded host the image build
alone took longer than that, so werf was killed mid-build and the spec
failed on a missing "Creating namespace" instead of testing cancel.

Add CancelOnOutputAfter to the command helper so the one-minute window
opens only after "Executing into pod", and widen SpecTimeout to leave
room for a slow build.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The complex and extra e2e jobs run Native Buildah under ginkgo -p, which
picks 15 procs on the CI host, and several PRs' jobs land on the same
machine at once. Under that load a Native Buildah spec that normally
finishes in minutes runs past the suite timeout.

Put the buildah-heavy jobs in one non-cancelling concurrency group, cap
them at 6 procs, allow one flake retry, give the daily suites an
explicit ginkgo timeout below the job limit, and log loadavg before the
run so a timed-out job still reports it.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
The daily complex and extra e2e jobs ran a werf built with both
coverage instrumentation and -race. The race-instrumented binary runs
Native Buildah several times slower, which is what pushed those specs
past the one-hour suite timeout. Keep coverage, drop -race for the two
buildah-heavy jobs; the simple, integration and unit jobs still run
under the race detector.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
imagebuildah walks every image in the local containers storage while
looking for a cache hit. When another process on the host removes one
of those images during the walk, the top-layer lookup returns "layer
not known" and the whole build failed, although nothing was wrong with
the build itself. This is routine on shared build hosts where several
werf processes prune their own images concurrently.

Treat that storage error from the first attempt as a cache miss and
rebuild once with the local cache disabled. Only the error predicate is
unit-tested; the retry path needs a Linux CGO build and a concurrent
prune to exercise.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
@reyreavman

Copy link
Copy Markdown
Collaborator Author

Verification

  • pkg/buildah unit suite executed inside a golang:1.25 linux/arm64 container (macOS host cannot run the _linux.go tests): 47/47 passed.
  • test/e2e/build and test/e2e/kube-run linux test binaries compiled via GOOS=linux GOARCH=amd64 task test:unit paths=....
  • Complex build … using Docker (both entries) passed locally against localhost:5001.
  • Mutation: removed the CancelOnOutputAfter gate in RunCommandWithOptionsstarts the cancel timeout only after CancelOnOutputAfter appears failed (cancel fired at ~2 s instead of ≥3 s). Removed the tee into GinkgoWriterforwards output to GinkgoWriter while the command is still running failed.
  • Not run: Native Buildah e2e entries (Linux CGO only) and kube-run specs (local kind node NotReady); this PR's CI run is the check for those claims.

Review focus

  • test/pkg/suite_init/containers_storage_data.go: XDG_RUNTIME_DIR is now per-process too. Confirm nothing else in the build suite relies on the host's runtime dir.
  • Failed jobs before this branch ended with ~11 orphaned fuse-overlayfs processes at runner cleanup. buildah unshare rm -rf removes the per-process dir but does not unmount; if this run still shows orphans, an explicit unmount before rm -rf is the next step.
  • --flake-attempts=2 masks exactly the flake class this PR targets.

Follow-up

  • After a few green runs, drop --flake-attempts=2 from the e2e jobs in .github/workflows/tests.yml and test_daily.yml to confirm the isolation alone holds.
  • pkg/sbom/externalref DATA RACE in helpers_test.go fails every test:daily unit job; unrelated to Buildah, separate PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant