Skip to content

fix(ingress): run the default backend on arm64 - #4517

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/ingress-default-backend-multiarch
Sep 28, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/ingress-default-backend-multiarch

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

The ingress default backend can't start on arm64 nodes: it crash-loops with exec /server: exec format error. packages/system/ingress-nginx turns the default backend on without setting an image, so it gets the chart default registry.k8s.io/defaultbackend-amd64:1.5. That image is amd64 only, and upstream never published a multi-arch defaultbackend.

This pins registry.k8s.io/ingress-nginx/custom-error-pages:v1.2.9 by its multi-arch index digest. Upstream ships it as the replacement default backend, with amd64, arm and arm64 builds. It answers 404 on :8080 and 200 on /healthz, so the chart's port and probes still fit. The host ingress, the tenant ingresses and the tenant Kubernetes ingress-nginx addon all render this package without their own image, so this one change covers them.

I ran the arm64 image locally under the chart's securityContext (uid 65534, read-only root filesystem, all capabilities dropped). /healthz returned 200, / returned 404, and memory stayed around 6Mi of the 20Mi limit. The same override runs on an arm64 test cluster. A new helm-unittest case checks the default backend image by digest; it fails on main and passes here.

There are two side effects. custom-error-pages logs a line for every request, because it looks for error page templates under /www that we don't ship and falls back to a plain 404 page not found. The old backend didn't log, and its body was default backend - 404. The status code is still 404.

Also, Renovate won't update this pin. The helm-values manager is disabled in .github/renovate.json, and no regex manager matches the registry/image/tag/digest layout. The controller image in the same file has the same problem, so I kept Renovate config out of this PR.

Screenshots

No UI changes.

Downstream repositories

The diff touches only packages/system/ingress-nginx/values.yaml and a new test in that package. No entry in the trigger map covers a system package's image values.

Release note

fix(ingress): the ingress default backend now uses the multi-arch custom-error-pages image instead of the amd64-only defaultbackend image, so it starts on arm64 nodes.

Summary by CodeRabbit

  • Bug Fixes
    • Pinned the default error-page image to a specific version and digest, improving consistency across deployments.
    • Added a chart test to verify the configured image.

The ingress-nginx chart defaults the default backend to
registry.k8s.io/defaultbackend-amd64, which ships no arm64 build and
crash-loops there with "exec format error". Every ingress controller
rendered from this package, the host and tenant ingresses and the
tenant Kubernetes addon, enables that backend without naming an image.

Pin the upstream multi-arch replacement, custom-error-pages, by its
index digest. It answers 404 on :8080 and serves /healthz, so the
chart's port, probes and restricted securityContext apply unchanged.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@github-actions github-actions Bot added area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files labels Sep 26, 2026
@coderabbitai

coderabbitai Bot commented Sep 26, 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: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 0c1fb136-313a-4418-9372-9eeab50a8f7c

📥 Commits

Reviewing files that changed from the base of the PR and between 101d1d9 and d4dfae6.

📒 Files selected for processing (2)
  • packages/system/ingress-nginx/tests/default_backend_image_test.yaml
  • packages/system/ingress-nginx/values.yaml

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


📝 Walkthrough

Walkthrough

The ingress-nginx chart now sets a versioned, digest-pinned default-backend image. A Helm test checks that the rendered deployment uses the specified image.

Changes

Default-backend image

Layer / File(s) Summary
Configure and verify the default-backend image
packages/system/ingress-nginx/values.yaml, packages/system/ingress-nginx/tests/default_backend_image_test.yaml
The chart values set the image registry, name, tag, and digest. A Helm test checks that the rendered container image matches the configured reference.

Priority: ➖ Normal

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Bug fix

Suggested reviewers: kvaps

Merge Risk: ⚪ Minimal · up to d4dfa

The pinned image supports arm64, and the added Helm test checks the rendered image reference. No material merge-blocking risk is identified.

Architecture Summary

Architecture risk: 🔵 Low · up to d4dfa

The change affects 1 system.

Changed systems: packages/system

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — packages/system (library) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in packages/system/ingress-nginx/tests/default_backend_image_test.yaml: Adds a Helm test targeting the default-backend deployment, with release and namespace settings, that checks the rendered container image against the specified version and digest.
  • observed — Modified behavior in packages/system/ingress-nginx/values.yaml: Added an explicit default-backend image configuration: registry registry.k8s.io, image ingress-nginx/custom-error-pages, tag v1.2.9, and a pinned digest. The accompanying comments describe the chart default’s lack of an arm64 build and the replacement image’s 404 and health-probe endpoints.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling the ingress-nginx default backend to run on arm64.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@IvanHunters IvanHunters 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.

Reviewed statically in a hermetic clone. Confirmed via skopeo the new default-backend image is a multi-arch index, ran it on a real arm64 host under the chart securityContext, and ran helm unittest (23/23) plus a mutation check. LGTM. Two minor non-blocking notes.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 17838d4 into main Sep 28, 2026
20 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/ingress-default-backend-multiarch branch September 28, 2026 16:36
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Sep 29, 2026
…leases on it (#4505)

## What this PR does

Pre-release builds now publish every image as an amd64 and arm64 index,
and a release fails if anything it ships is single-arch. Main, release
lines and pull requests keep building amd64 only. It builds on #4498,
which made every image buildable for both architectures. Part of #1961.

**This PR is on hold until everything under "Merge after" has landed.**
Merged earlier, the new gate fails the next rc on the images those PRs
fix.

### How a pre-release is built

A pre-release tag gets a second job, for arm64, next to the existing
release job. That covers `-rc.N`, `-beta.N` and `-alpha.N` tags. It runs
on the CNCF arm64 pool (`oracle-vm-24cpu-96gb-arm64`), builds the same
images with `PLATFORM=linux/arm64`, and pushes them as `<tag>-arm64`.
`prepare-release` waits for it, and if the arm64 job fails the rc fails
with it. There is no amd64-only fallback. Stable tags do not run it:
promotion copies the rc digests with `skopeo copy --multi-arch all`, so
a stable release inherits the indexes.

After the amd64 build, the stitch joins each amd64 image with its arm64
twin into one index. It lives in `hack/stitch-multiarch.sh`. Every tag
of the amd64 image moves to the index, the component-versioned ones
included. The script then rewrites the digests in the tree and
republishes the packages artifact and the installer chart pinned on
them. It reads refs through `hack/lib/image-refs.sh`, and it fails if an
old digest survives anywhere outside `charts/`.

### The gate

A new check fails the rc when a pinned digest in the tree is not an
index with both linux/amd64 and linux/arm64. It lives in
`hack/verify-multiarch.sh`. It checks first-party and third-party refs
alike. Images that are amd64 by nature go into
`hack/multiarch-allowlist`, one repository per line, each with a
mandatory reason. Today that is only the e2e sandbox. An entry that
matches nothing is reported as stale.

The static gate only sees digest-pinned refs in the tree. A tag-only
ref, an image from a vendored chart default, or one an operator starts
at runtime is invisible to it. So the rc e2e also audits every image its
nodes pulled, with the same check and the same allowlist. Neither covers
a package the e2e never installs.

### Nightly

A new nightly workflow builds main for arm64. It keeps the arm64 build
cache warm, since the rc job only reads it. It also prints the
single-arch refs that would block the next rc. Nothing it builds is
published.

### Smaller changes

- `CACHE_TAG` in `hack/common-envs.mk` moves the default cache ref, so
the arm64 build keeps a cache of its own.
- `MATRIX_ARCH=arm64` makes `hack/build-matrix.sh` leave out the
amd64-only e2e sandbox.
- The kamaji provider image is pushed under the build's `IMAGE_TAG` like
every other image. Its Makefile set `IMAGE_TAG` itself, so concurrent
builds overwrote each other's component tag. Closes #4503.
- `hack/nightly-mirror.sh` verifies a copy against the raw manifest
digest. Plain `skopeo inspect` resolves an index to one platform's
child.
- matchbox is rebuilt for both platforms in the amd64 release job. The
arm64 leg skips the talos package, so there is no arm64 half to stitch,
and its Dockerfile only copies files, so no QEMU is needed. Releases
then network-boot arm64 machines too. Closes #4524.

The stitch also rewrites the kamaji ref inside `files/components.gz`,
recompressed with `gzip -n` so the bytes are reproducible, so the kamaji
control-plane provider ships multi-arch too. keda and kuberture now name
the repository next to their pinned digest, so the gate can resolve
them; their rendered manifests do not change.

### Merge after

- #4552 builds the Talos installer, matchbox, Harbor and the Velero
KubeVirt plugin for arm64, and adds flux-plunger, keycloak-operator,
kilo and migration-controller to the root `build:` list. It replaces
#4507, #4515, #4525 and #4528.
- #4549 moves ingress-nginx, cozy-proxy, cozystack-scheduler and
keycloak-kms-proxy to their multi-arch releases, which are already
published.

The rest of the stack is merged: #4485, #4498, #4512, #4517, #4521 and
#4522 here, and the multi-arch PRs in
ingress-nginx-with-protobuf-exporter, cozy-proxy, cozystack-scheduler
and keycloak-kms-proxy.

### Verification

The bats suites pass: stitch, verify-multiarch, build-matrix,
common-envs, nightly-mirror and the release contracts. The first three
also pass under `hack/cozytest.sh` with dash. Each new test was red
before its implementation, and the verify-multiarch and stitch tests
each have a mutation that turns them red. I ran `verify-multiarch
--report` against main. It lists the first-party refs the stitch will
fix, plus the third-party and special cases above. The workflows have
not run yet. The arm64 job's tools and duration, the stitch against real
registries, and the audit are checked for the first time on the nightly
and on the next rc.

### Screenshots

Not a UI change.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

Nothing under `hack/` is moved or renamed, and no make target changes
its default behaviour: `CACHE_TAG` and `MATRIX_ARCH` are opt-in. The
satellite repositories listed under "Merge after" are prerequisites, not
follow-ups this change forces on them.

### Release note

```release-note
ci(release): pre-release builds publish every image as an amd64 and arm64 multi-arch index, and a release fails if any image it ships or pulls in e2e lacks either architecture. Stable releases inherit the indexes through promotion.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Release candidates now build amd64 and arm64 images in parallel and
combine eligible images into verified multi-architecture indexes before
publication.
* A nightly arm64 image build is available for testing and supplies a
build cache for release-candidate builds.
* **Bug Fixes**
* End-to-end checks audit whether pulled images support both
architectures. Audit failures block prerelease checks, while stable
releases continue with a warning.
* End-to-end test artifacts now include collected image references and
multi-architecture audit results.
* **Documentation**
* Updated release and image guidance covers multi-architecture builds,
verification checks, and troubleshooting, including arm64 build and
stitching failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants