Skip to content

fix(ci): cache the Argo CD Redis image for the KWOK deployer lanes - #2959

Merged
mchmarny merged 2 commits into
mainfrom
fix/2501-cache-argocd-redis-image
Sep 28, 2026
Merged

mchmarny merged 2 commits into
mainfrom
fix/2501-cache-argocd-redis-image

Conversation

@mchmarny

Copy link
Copy Markdown
Member

Summary

Cache the Argo CD chart's Redis image for the KWOK argocd-* deployer lanes, so they stop pulling it anonymously from ECR Public at job time and shedding on the per-IP throttle.

Motivation / Context

The argocd-* lanes install the Argo CD chart, which pulls ecr-public.aws.com/docker/library/redis:8.2.3-alpine in-cluster at job time. That is the same throttle bucket registry_image already hit: we cached one image out of that namespace and left another one in it.

Six lanes died on this in run 36290149406:

Failed to pull image "ecr-public.aws.com/docker/library/redis:8.2.3-alpine":
  ... 429 Too Many Requests
[ERROR] Argo CD Helm install failed
[ERROR] install-infra.sh failed (exit code 30); cannot run argocd-oci deployer lane

This has escalated since the issue was filed. It was 3 of 127 lanes on 2026-08-31; every scheduled KWOK run since 2026-09-25 has been red the same way.

#2497 removed the two images install-infra.sh preloads from the job-time critical path. This is the uncovered scope: the image the deployer stack pulls for itself.

Fixes: #2501
Related: #2483, #2497, #2496, #843

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/) — kwok/README.md and the regenerated docs/contributor/registry-egress.md
  • Other: KWOK CI lanes (kwok/scripts, .github/workflows/kwok-recipes.yaml, .github/actions/kwok-test) and the egress allowlist (tools/registry-inventory/registry-allowlist.yaml)

Implementation Notes

Option 1 from the issue. Cache the one demonstrated failure, reusing the #2483 machinery as it already exists. prime-images pulls once per run and saves a tarball, actions/cache carries it, each argocd-* lane restores and docker loads it, and install_argocd calls preload_image before the Helm install so kind load side-loads it into the node. The kubelet then never contacts ECR Public.

The chart's other two images (quay.io/argoproj/argocd, ghcr.io/dexidp/dex) are deliberately left alone — option 3 in the issue — since neither has been observed shedding a pull.

The pin carries no renovate: annotation, on purpose. testing_tools is renovate-managed, so an annotated argocd_redis_image would be bumped to the newest Redis independently of argocd_chart — caching an image the chart never pulls and silently restoring the 429. Its only correct value is whatever the pinned chart renders.

Drift is reported, not gated. The issue defers the full freshness gate to option 2, and building it before we know it is needed means paying for it early. Instead, a failed Argo CD install renders the chart and compares, so the next reader sees the cause named rather than re-deriving it from an ImagePullBackOff:

[ERROR] PIN DRIFT: chart 9.6.0 renders ecr-public.aws.com/docker/library/redis:8.4.0-alpine
[ERROR]   .settings.yaml testing_tools.argocd_redis_image is ecr-public.aws.com/docker/library/redis:8.2.3-alpine
[ERROR]   the preloaded image is not the one the chart pulls; update the pin (#2501)

This is not hypothetical: the chart changed Redis tags between 9.0.0 and 9.5.x, and changed the host between 8.x and 9.x.

That check re-fetches the chart the install just failed on, and an unreachable repo is one of the reasons it fails — so it is bounded by timeout and skipped outright if timeout is unavailable. Unbounded, a stalled fetch would replace the caller's exit 30 with a job cancellation and lose the exit-code taxonomy run-all-recipes.sh branches on.

One trade-off worth naming. The argocd-git lane now runs three preload_image calls in series (registry, Redis, Gitea). Each carries its own 180s budget, so a broad registry outage could in principle spend 540s on preload retries inside the 20-minute job ceiling, before Helm's 5m --wait. In practice the budget is only consumed on retry: with a warm cache preload_image finds the image already on the host and returns immediately. Sharing one deadline across a lane's preloads, the way KWOK_SYNC_DEADLINE_EPOCH already does for the sync gate, would be the tightening if this ever bites.

Adding an image to the cache is three edits (IMAGE_CACHE_IMAGES, the prime-images matrix, the kwok-test restore/load steps). Nothing gates that today, so the requirement is now stated at the array.

Needs a CODEOWNER look at registry-allowlist.yaml. Pinning the ref tripped tools/registry-inventory, which gates egress hosts reachable from structured sources. Worth being precise about what that means here: this is not new egress. The Argo CD chart has always pulled this host in-cluster; as a rendered chart image it sat outside the gate's scope. Pinning it in .settings.yaml is what makes it visible, so the allowlist entry records egress that was already happening.

It is also a genuinely distinct host from the already-allowlisted public.ecr.aws — the same AWS ECR Public registry under a second hostname. The pin has to match what the chart renders exactly, or the preloaded image is not the one the kubelet asks for and the cache is inert.

docs/contributor/registry-egress.md is regenerated by make registry-docs to match (26 → 27 hosts).

kwok/README.md's "Public image cache" section described the cache as covering exactly two images; it now enumerates all three in a table, records why the Redis pin is not independently versioned, and notes the per-lane restore gating.

Testing

bash kwok/scripts/lib/image-cache_test.sh      # 47 cases
bash kwok/scripts/lib/preload-image_test.sh    # 47 cases
bash kwok/scripts/lib/profile-select_test.sh   # 38 cases
bash kwok/scripts/lib/sync-budget_test.sh      #  9 cases
bash kwok/scripts/run-all-recipes_test.sh      # 17 cases
shellcheck -x -e SC1091 kwok/scripts/install-infra.sh kwok/scripts/lib/image-cache.sh
yamllint -c .yamllint.yaml .settings.yaml .github/workflows/kwok-recipes.yaml .github/actions/kwok-test/action.yml
make lint-yaml license check-agents-sync check-docs-filenames bom-pinning-check check-depproxy-kit check-upgrade-records

All 158 shell cases pass, shellcheck exits 0, yamllint is clean, and the seven non-Go lint targets pass. make test passes, and the pinned golangci-lint v2.13.2 reports 0 issues across ./....

Verified beyond compiling:

  • Rendered argo-cd 9.5.14. It pulls exactly three images; the cached one is the only ref on the throttled host.
  • Round-tripped the real image through save → removed it from the host → load. The restored image ID is sha256:08ad0b1d2808..., byte-identical to the digest in the CI 429 error.
  • Confirmed IMAGE_CACHE_IMAGES, the workflow matrix, and the action's restore/load steps all resolve to the same names and cache keys.
  • Exercised both branches of the drift report against real chart versions: 9.5.14 matches the pin, 9.0.0 renders redis:7.2.11-alpine, 8.0.0 renders a different host.

No Go files changed, so the coverage gate is unaffected.

One note for anyone reproducing locally: a Homebrew-upgraded golangci-lint v2.14.0 reports four gosec G703 findings in pkg/oci and tools/k8s-aibom-test that the pinned v2.13.2 does not (G703 ships in 2.14.0's gosec bundle). They are identical on a clean main tree and unrelated to this change. make tools-check renders golangci-lint as ✓ despite the mismatch, so check golangci-lint --version against .settings.yaml before chasing them.

The issue's second acceptance criterion — several consecutive green Tier 3 runs with no 429 — can only be confirmed after merge.

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert
  • Medium — Touches multiple components or has broader impact
  • High — Breaking change, affects critical paths, or complex rollout

CI-only; no product code changes. The cache is best effort on the consuming side: a miss warns and falls through to preload_image, then to the kubelet, which is today's behavior. The prime-images gate is the one hard failure, matching the existing registry and gitea entries. A first run after this merges primes a cold cache.

Rollout notes: N/A

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint) — Go half verified with the pinned v2.13.2, not the drifted local binary; see the note above
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed — n/a, CI-internal change with no user-facing surface
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

The argocd-* KWOK lanes pull the Argo CD chart's Redis anonymously at job
time from ECR Public, which sheds the request under the per-IP throttle the
matrix already trips. Six lanes died on `429 Too Many Requests` for
ecr-public.aws.com/docker/library/redis:8.2.3-alpine in run 36290149406,
and every scheduled run since 2026-09-25 has been red the same way.

#2497 removed the two images install-infra.sh preloads from the critical
path; this covers the one the deployer stack pulls for itself, out of the
same ECR Public namespace the registry image was already cached from. The
chart's quay.io and ghcr.io images are left to the kubelet, neither having
been observed shedding a pull.

Follows the #2483 machinery: prime-images pulls once per run and caches the
tarball, each argocd-* lane restores and loads it, and install_argocd side-
loads it into the Kind node before Helm's 5m --wait. The chart renders one
distinct Redis ref with redis-ha off, and the argocd-redis container is
imagePullPolicy: IfNotPresent, so the side-load is what the kubelet uses.

The pin carries no renovate annotation on purpose. testing_tools is
renovate-managed, so an annotated entry would be bumped to the newest Redis
independently of the chart, caching an image nothing pulls and restoring
the 429. Nothing gates the pin against the chart, so a failed install
renders the chart and reports the mismatch by name instead of leaving the
next reader to re-derive it from an ImagePullBackOff. That render is bound
by timeout: it re-fetches the chart the install just failed on, and an
unbounded stall there would replace exit 30 with a job cancellation.

Pinning the ref surfaces ecr-public.aws.com to tools/registry-inventory,
which gates structured sources only. This is not new egress: the chart has
always pulled that host in-cluster, but as a rendered chart image it sat
outside the gate's scope. Allowlisted with that justification, and
docs/contributor/registry-egress.md regenerated to match.

kwok/README.md described the cache as covering two images; it now covers
three, with the Redis pin's provenance and the per-lane restore gating.

Fixes: #2501
Signed-off-by: Mark Chmarny <mark@chmarny.com>
@mchmarny mchmarny added the theme/ci-dx CI pipelines, developer experience, and build tooling label Sep 28, 2026
@mchmarny mchmarny self-assigned this Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 85.0%
Threshold 83%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-85.0%25-brightgreen)

No Go source files changed in this PR.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/aicr/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: bb5bbf2c-e1b1-4183-978b-866177e1926a

📥 Commits

Reviewing files that changed from the base of the PR and between 2168df8 and 3f2219c.

📒 Files selected for processing (10)
  • .github/actions/kwok-test/action.yml
  • .github/workflows/kwok-recipes.yaml
  • .settings.yaml
  • docs/contributor/registry-egress.md
  • kwok/README.md
  • kwok/scripts/install-infra.sh
  • kwok/scripts/lib/image-cache.sh
  • kwok/scripts/lib/image-cache_test.sh
  • kwok/scripts/lib/preload-image.sh
  • tools/registry-inventory/registry-allowlist.yaml

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Walkthrough

The change adds an Argo CD Redis image pin and preloads that image before Argo CD installation. It adds Redis to KWOK image-cache priming and configures Argo CD deployers to restore and load the cached image. After a failed Helm install, the script can render the chart and report whether its Redis image matches the configured pin. Registry inventory, allowlist entries, tests, and documentation now include the image or its host.

Priority: ⬆️ High

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: yuanchen8911

Merge Risk: ⚪ Minimal · up to 3f221

The cache wiring is consistent and no merge-blocking issue was identified. Confirm that the pinned Redis image matches the chart and that post-merge KWOK runs avoid the reported pull failures.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses the coding objective in [#2501]. .github/workflows/kwok-recipes.yaml primes argocd_redis. .github/actions/kwok-test/action.yml restores and loads that cache only for argocd-* …
Out of Scope Changes check ✅ Passed The reviewed changes stay within [#2501]. The registry allowlist, generated registry documentation, kwok/README.md, cache comments, pin-drift diagnostic, and focused cache test updates support the R…
Title check ✅ Passed The title clearly and concisely describes the main change: caching the Argo CD Redis image for KWOK deployer lanes.
Description check ✅ Passed The description is directly related to the changeset. It explains the ECR Public throttling problem, the cache implementation, image pinning, drift reporting, testing, and affected documentation and a…
✨ Finishing Touches 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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

@mchmarny
mchmarny marked this pull request as ready for review September 28, 2026 11:57
@mchmarny
mchmarny requested review from a team as code owners September 28, 2026 11:57
@mchmarny
mchmarny merged commit ec06473 into main Sep 28, 2026
86 of 88 checks passed
@mchmarny
mchmarny deleted the fix/2501-cache-argocd-redis-image branch September 28, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci area/docs size/M theme/ci-dx CI pipelines, developer experience, and build tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: KWOK deployer charts pull their own images anonymously at job time

2 participants