Skip to content

Commit 26aef0e

Browse files
authored
Merge pull request #2512 from ArangoGutierrez/build/multiarch-cross-compile
build(image): cross-compile from $BUILDPLATFORM via tonistiigi/xx
2 parents 5fbdf91 + 2634886 commit 26aef0e

2 files changed

Lines changed: 102 additions & 16 deletions

File tree

Dockerfile

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,111 @@
1+
# BUILDER_IMAGE MUST be Debian-family (xx-apt-get requires apt-get).
2+
# Project's Makefile pins it to golang:1.26-trixie (matches go.mod
3+
# minimum Go version) which satisfies this. Override with a non-Debian
4+
# image only if you also swap xx-apt-get for the matching package manager.
5+
#
6+
# tonistiigi/xx is pinned by sha256 digest below. Minimum acceptable
7+
# version is 1.5.0 (arm/v7 + $TARGETVARIANT handling). To update:
8+
# docker pull tonistiigi/xx:1.x.y
9+
# docker inspect --format='{{index .RepoDigests 0}}' tonistiigi/xx:1.x.y
10+
# Track in Renovate / Dependabot alongside the runtime base-image pins.
111
ARG BUILDER_IMAGE
212
ARG BASE_IMAGE_FULL
313
ARG BASE_IMAGE_MINIMAL
14+
ARG XX_IMAGE=tonistiigi/xx@sha256:010d4b66aed389848b0694f91c7aaee9df59a6f20be7f5d12e53663a37bd14e2
415

5-
# Build node feature discovery
6-
FROM ${BUILDER_IMAGE:-golang} AS builder
16+
# Cross-build helper (host arch; never shipped)
17+
FROM --platform=$BUILDPLATFORM ${XX_IMAGE} AS xx
718

8-
# Get (cache) deps in a separate layer
19+
# Build node-feature-discovery on the build host, cross-compile to TARGET.
20+
# Running here on $BUILDPLATFORM avoids the QEMU translation-cache instability
21+
# that crashed go mod download under emulation (see PR commit body for the
22+
# crash signatures).
23+
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE:-golang} AS builder
24+
COPY --from=xx / /
25+
26+
ARG TARGETPLATFORM
27+
ARG TARGETARCH
28+
# Cross-toolchain packages (gcc, libc6-dev) are intentionally unpinned —
29+
# Debian point releases shift these frequently and the build-stage is
30+
# discarded; pinning would create frequent CI churn for no security gain.
31+
# hadolint ignore=DL3008
32+
RUN xx-apt-get update && \
33+
xx-apt-get install -y --no-install-recommends gcc libc6-dev && \
34+
rm -rf /var/lib/apt/lists/*
35+
36+
# Module cache fetched on $BUILDPLATFORM. Modules are arch-independent;
37+
# no QEMU involvement here.
938
COPY go.mod go.sum /go/node-feature-discovery/
1039
COPY api/nfd/go.mod api/nfd/go.sum /go/node-feature-discovery/api/nfd/
11-
1240
WORKDIR /go/node-feature-discovery
1341

1442
RUN --mount=type=cache,target=/go/pkg/mod/ \
1543
go mod download
1644

17-
# Do actual build
45+
# Force CGO on. xx-go --wrap defaults CGO_ENABLED=0 for cross-compile
46+
# targets, which would build-tag-filter out source/cpu/cpuid_linux_*.go
47+
# (which use #include <sys/auxv.h>) and silently fall back to the
48+
# cpuid_stub.go no-op variant.
49+
ENV CGO_ENABLED=1
50+
51+
# Cross-compile via xx-go --wrap. After --wrap, /usr/local/go/bin/go is a
52+
# shim that sets CC, GOOS, GOARCH, GOARM from $TARGETPLATFORM for every
53+
# subsequent invocation in this stage. Use explicit per-binary go build
54+
# -o to bypass go install's $GOPATH/bin/$GOOS_$GOARCH/<name> redirect for
55+
# cross-targets (Go refuses GOBIN override on cross-compile).
1856
ARG VERSION
1957
ARG HOSTMOUNT_PREFIX
2058

2159
RUN --mount=type=cache,target=/go/pkg/mod/ \
2260
--mount=src=.,target=. \
23-
make install VERSION=$VERSION HOSTMOUNT_PREFIX=$HOSTMOUNT_PREFIX
61+
xx-go --wrap && \
62+
# Silent-CGO-disable guard: source/cpu/cpuid_linux_{arm,arm64,ppc64le,s390x}.go
63+
# each `import "C"` and wrap glibc's getauxval() via a small inline C
64+
# function called gethwcap(). If xx-apt-get install silently failed
65+
# or xx-go --wrap unset CGO_ENABLED, those files get build-tag-filtered
66+
# out and the binary would ship with empty CPU feature labels at
67+
# runtime with no build error.
68+
#
69+
# We probe by building an UNSTRIPPED nfd-worker (the production build
70+
# below uses -s -w which destroys the symbol table) and grepping for
71+
# the CGO-emitted wrapper symbol that can only exist if the linux
72+
# cpuid file was compiled+linked. The Go package object is then
73+
# cached, so the production -s -w link is cheap.
74+
#
75+
# amd64 is excluded: source/cpu/cpuid_amd64.go uses pure-Go cpuid via
76+
# github.com/klauspost/cpuid/v2 — no CGO required on that arch.
77+
case "$TARGETARCH" in \
78+
arm64|arm|ppc64le|s390x) \
79+
go build -v -tags osusergo,netgo -o /tmp/nfd-worker-guard ./cmd/nfd-worker && \
80+
go tool nm /tmp/nfd-worker-guard | grep -q 'source/cpu\._Cfunc_gethwcap' \
81+
|| { echo "FAIL: source/cpu._Cfunc_gethwcap missing from nfd-worker — CGO likely disabled for $TARGETPLATFORM"; exit 1; } && \
82+
rm -f /tmp/nfd-worker-guard \
83+
;; \
84+
esac && \
85+
for bin in nfd-master nfd-worker nfd-topology-updater nfd-gc kubectl-nfd nfd; do \
86+
go build -v -tags osusergo,netgo \
87+
-ldflags "-s -w -extldflags=-static -X sigs.k8s.io/node-feature-discovery/pkg/version.version=${VERSION} -X sigs.k8s.io/node-feature-discovery/pkg/utils/hostpath.pathPrefix=${HOSTMOUNT_PREFIX}" \
88+
-o /go/bin/$bin ./cmd/$bin || exit 1; \
89+
done && \
90+
xx-verify /go/bin/nfd-master
91+
92+
# Runtime stages run at $TARGETPLATFORM (implicit). They only COPY, set USER,
93+
# and set ENV — trivial work under QEMU.
2494

25-
# Create full variant of the production image
2695
FROM ${BASE_IMAGE_FULL:-debian:stable-slim} AS full
2796

28-
# Run as unprivileged user
2997
USER 65534:65534
30-
31-
# Use more verbose logging of gRPC
3298
ENV GRPC_GO_LOG_SEVERITY_LEVEL="INFO"
3399

34100
COPY deployment/components/worker-config/nfd-worker.conf.example /etc/kubernetes/node-feature-discovery/nfd-worker.conf
35-
COPY --from=builder /go/bin/* /usr/bin/
101+
# Enumerate explicitly — matches Makefile BUILD_BINARIES. Defensive against
102+
# any future shim-leakage from xx-go.
103+
COPY --from=builder /go/bin/nfd-master /go/bin/nfd-worker /go/bin/nfd-topology-updater /go/bin/nfd-gc /go/bin/kubectl-nfd /go/bin/nfd /usr/bin/
36104

37-
# Create minimal variant of the production image
38105
FROM ${BASE_IMAGE_MINIMAL:-scratch} AS minimal
39106

40-
# Run as unprivileged user
41107
USER 65534:65534
42-
43-
# Use more verbose logging of gRPC
44108
ENV GRPC_GO_LOG_SEVERITY_LEVEL="INFO"
45109

46110
COPY deployment/components/worker-config/nfd-worker.conf.example /etc/kubernetes/node-feature-discovery/nfd-worker.conf
47-
COPY --from=builder /go/bin/* /usr/bin/
111+
COPY --from=builder /go/bin/nfd-master /go/bin/nfd-worker /go/bin/nfd-topology-updater /go/bin/nfd-gc /go/bin/kubectl-nfd /go/bin/nfd /usr/bin/

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,28 @@ image-all: ensure-buildx yamls
117117
$(IMAGE_BUILDX_CMD) $(IMAGE_BUILD_ARGS) $(IMAGE_BUILD_ARGS_FULL)
118118
$(IMAGE_BUILDX_CMD) $(IMAGE_BUILD_ARGS) $(IMAGE_BUILD_ARGS_MINIMAL)
119119

120+
# Local cross-build smoke test for the full release matrix. Does NOT push;
121+
# does NOT --load. Exits non-zero on the first platform that fails to build.
122+
# Use to reproduce CI cross-build issues locally; leaves images named
123+
# nfd-cross-test:<plat> in the local cache for inspection (run
124+
# `docker image prune` or `docker rmi nfd-cross-test:*` to clean up).
125+
#
126+
# This recipe re-spells the docker buildx flags from IMAGE_BUILDX_CMD
127+
# rather than reusing the variable because IMAGE_BUILDX_CMD bakes in
128+
# --platform=$(IMAGE_ALL_PLATFORMS) (multi-arch in one invocation), and
129+
# we need a per-platform single-arch loop here. Keep the flags in sync
130+
# with IMAGE_BUILDX_CMD (line 78) if either changes. --pull is preserved
131+
# to match CI behaviour (fresh base images per smoke run).
132+
image-cross-test: ensure-buildx yamls
133+
@for plat in $$(echo $(IMAGE_ALL_PLATFORMS) | tr , ' '); do \
134+
echo "===> $$plat"; \
135+
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build \
136+
--builder=nfd-builder --platform=$$plat --progress=auto --pull \
137+
$(IMAGE_BUILD_ARGS) --target minimal \
138+
-t nfd-cross-test:$$(echo $$plat | tr / -) . \
139+
|| { echo "FAIL $$plat"; exit 1; }; \
140+
done
141+
120142
# clean NFD labels on all nodes
121143
# devel only
122144
deploy-prune:

0 commit comments

Comments
 (0)