ci: move the shipped binaries to AlmaLinux 8, get the test images off EOL Debian - #790
Conversation
❌ ErrorsYour PR has failed checks. Please review the issues below and take necessary action before merging. 🚦 2 Pipeline jobs failed
Useful? React with 👍 / 👎 This comment will be updated automatically if new data arrives.🔗 Commit SHA: 68cf4d8 | Docs | View more details | Give us feedback! |
Scan-Build Report
Bug Summary
Reports
|
||||||||||||||||||||||||||||||||||||
bd98bb8 to
5386860
Compare
CI Test ResultsRun: #35117227796 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-09-16 16:01:52 UTC |
There was a problem hiding this comment.
More details
The CI jobs and rebuild scripts use consistent new image names. The Bullseye fallback already fails on the base branch, so it is not a regression from this PR.
🤖 Datadog Autotest · Commit 7bfc401 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7bfc40165c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The x64-2.17 image's Dockerfile defaulted to openjdk:11-slim-buster. Debian 10 buster left even LTS support in June 2024 and its repositories are archived, so `apt-get update` against them now fails with "does not have a Release file" -- and rebuild-images.sh builds without --build-arg BASE_IMAGE whenever BASE_IMAGE_LIBC_2_17 is unset, which reaches that default. The body of the file is yum-based, so a Debian base cannot work there at all; the default now names what CI actually passes (centos:7), with the glibc 2.17 rationale for staying on an EOL distro recorded next to the vault mirror URLs it already needs. The amd64 build image moves to Debian 13 trixie, the current stable release. It compiles no shipped native code -- build:x64 uses the glibc 2.17 image -- so its glibc is not a customer-facing floor. The arm64 image stays on bullseye: it is where build:arm64 compiles the shipped linux-arm64 libjavaProfiler.so, so that glibc (2.31) is the runtime floor for arm64 customers and trixie would raise it to 2.41. Both variables now say which of the two they are and why. Also drop the `|| true` from the base image's package install, and with it the dead `apk` branch -- Alpine has its own Dockerfile.musl. As written, a total install failure produced a "successful" image whose tools were simply missing, surfacing later as an unrelated job failure. Verified against the real archives: buster's apt repositories fail as described, bullseye's still resolve, and the full package list installs on trixie with hexdump present (bsdmainutils is still a real package there). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Release builds link -static-libstdc++ -static-libgcc, so the C++ runtime and
unwinder in the shipped libjavaProfiler.so come out of the build image's
toolchain. That makes the build image's support status a property of the
artifact, not just of CI: on CentOS 7 those objects can never receive another
security rebuild, and no gcc newer than devtoolset-10 was ever built for
aarch64 there (the dts-11 collection ships binutils, elfutils and its own
metapackage for aarch64, but no compiler).
AlmaLinux 8 with gcc-toolset-14 is supported until 2029 and publishes the
toolset for x86_64 and aarch64 alike, so one image definition now covers both
architectures with one compiler version, replacing the hand-rolled CentOS 7
image and its vault/Rocky mirror repositories. almalinux:8 is a multi-arch
manifest, so a single BASE_IMAGE_GLIBC serves both.
Measured on the real artifact, built in this image for both architectures:
max required symbol version GLIBC_2.27 (both x86_64 and aarch64)
symbols above 2.17 expf (2.27), getentropy (2.25)
dynamic dependencies libc, libdl, libm, libpthread, librt
GLIBCXX/CXXABI references none
So the runtime floor moves 2.17 -> 2.27, not to the base image's 2.28: symbol
versions are per-function. 2.27 keeps Ubuntu 18.04 (glibc 2.27) in support and
drops RHEL 7 (2.17), Amazon Linux 2 (2.26) and SLES 15 SP1 (2.26). Both
symbols are addressable with a .symver pin if a lower floor is needed later.
With the shipped arm64 build moved off the Debian image, that image no longer
sets a customer-facing floor, so it moves to trixie alongside amd64 -- which
also restores apt on the arm64 sanitizer jobs.
Names now describe the purpose rather than a version that has changed twice:
BUILD_IMAGE_{X64,ARM64}_GLIBC and BASE_IMAGE_GLIBC, with tag suffixes
{x64,arm64}-glibc-base. The variables still hold the current images until
rebuild-images.sh publishes the new tags.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The sanitizer jobs installed cmake, libgtest-dev, libgmock-dev, binutils, libc6-dbg and llvm in a before_script, which made every run depend on the distro archive still serving them, and which does not survive the move to trixie: sanitizer/asan_interface.h is no longer part of the llvm package there, so compiling linearAllocator.cpp fails with fatal error: 'sanitizer/asan_interface.h' file not found The header comes from libclang-rt-<n>-dev. The packages now live in the image instead, using the unversioned libclang-rt-dev metapackage so the right clang runtime is picked up on whatever release the image is built from. Dropping the before_script override also stops these jobs bypassing the pipeline default, so they get the CANCELLED guard and the maven proxy export that every other job already had. Verified on a locally built trixie image (clang 19.1.7, gtest 1.16.0), aarch64: buildGtestAsan and buildGtestTsan both compile, and all 62 asan and 62 tsan binaries pass. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Pins from the rebuild in pipeline 137281788, which built every image from the definitions on this branch. Verified by pulling each digest: x64-glibc-base AlmaLinux 8.10, glibc 2.28, gcc 14.2.1 arm64-glibc-base AlmaLinux 8.10, glibc 2.28, gcc 14.2.1 x64-base Debian 13 trixie, glibc 2.41, clang 19.1.7 arm64-base Debian 13 trixie The musl, datadog-ci and benchmark images come from the same rebuild; their definitions did not change on this branch. These pins belong here rather than in the image-update PR the rebuild opened against main: main has no BUILD_IMAGE_*_GLIBC variables, so the two shipped-binary pins silently did nothing there, while BUILD_IMAGE_ARM64 -- which main still uses for build:arm64 and stresstest:arm64 -- would have moved the shipped arm64 binary onto a trixie image and its glibc 2.41 floor. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The ARG default was still the bullseye digest while both OPENJDK_BASE_IMAGE variables moved to trixie, and the comment justified it as "the more conservative of the two" for an arm64 glibc floor that no longer exists here: the shipped binaries come from .gitlab/base/el8/Dockerfile, so neither arch of this image sets a customer floor. It also stopped being a harmless default. With the package install no longer ending in `|| true`, a direct `docker build` of this file -- or rebuild-images.sh reaching its documented unset-variable fallback -- hit bullseye's pruned security pool and failed on exactly the 404s this PR exists to fix. Verified: building with no BASE_IMAGE argument now produces a trixie image. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The oldest host the shipped library can load on is decided entirely by the build image: the highest versioned glibc symbol it references sets the glibc floor, and the static-link flags decide whether it needs a C++ runtime from the host at all. Nothing checked either, so both could move without a source change and without a failing job. check-abi-floor.sh asserts both against SHIPPED_GLIBC_FLOOR, declared in config.env next to the image pin that determines it, and build.sh runs it on each glibc artifact before the job ends. musl targets carry no glibc symbol versions and are skipped. Failures name the offending symbols or libraries, since the point of the check is to say what needs fixing. Three cases fail loudly rather than reporting a pass: an artifact with no versioned glibc symbols, one with no NEEDED entries, and a sort that cannot order versions. Each would otherwise leave the check quietly inoperative, which is worse than not having it. The checker reads the artifact through $OBJDUMP, so its tests drive it with canned output and need no compiler or real shared object; they run in a new ci-script-tests job alongside shellcheck. Every guard was mutation-checked: disabling it individually turns a named assertion red. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
glibc 2.27 added a faster expf under a new symbol version and made it the default, so building on AlmaLinux 8 made the shipped library require expf@GLIBC_2.27 from source that has not changed. glibcCompat.h selects the GLIBC_2.17 interface explicitly, which is still exported and still maintained; .symver picks which of the versions already in the host's libc is called and bundles nothing, so a host on a newer glibc still runs its own implementation. Measured on the AlmaLinux 8 + gcc-toolset-14 build: expf moves from GLIBC_2.27 to GLIBC_2.17 and the artifact's requirement drops from 2.27 to 2.25, so the floor follows to 2.25. The remaining above-2.17 symbol is getentropy, which libstdc++ references from its own std::random_device and which glibc exports in only one version -- no pin can reach it, so 2.17 waits on removing the std::random_device uses. The pin is guarded to glibc: musl has no symbol versioning and a .symver naming a GLIBC_* version fails to link there, which would have broken both musl targets. Verified by linking and running a TU that reaches expf through poissonSampler.h under musl, where it emits no GLIBC_ references at all. The floor check could not read the artifact the pin produces. objdump parenthesises a binding to a non-default version, which is exactly what .symver creates, and the parser matched only the bare form -- so it found no versioned references on a pinned library. The empty-result guard turned that into a loud failure rather than the silent pass it would otherwise have been, which is what it exists for. The parser now accepts both forms, with fixtures for a pinned artifact at and above the floor. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The expf pin named a version that does not exist on x86_64, so every link against it failed there: nativeSocketSampler.o: undefined reference to `expf@GLIBC_2.17' no symbol version section for versioned symbol `expf@GLIBC_2.17' A symbol's oldest version is the baseline of the glibc release that introduced the port, which differs per architecture: x86_64 has expf@GLIBC_2.2.5 and aarch64 expf@GLIBC_2.17, both alongside the GLIBC_2.27 default added in 2.27. Naming a version the architecture does not export is a link error rather than a fallback, so the pin now selects per architecture and an unlisted one gets no pin at all, leaving the ABI floor check to report what it requires. This took out every x86_64 job that links the library: the two sanitizer gtest builds, and the CodeQL and Analyze jobs, which build linkRelease. Verified by linking and running a translation unit that reaches expf through poissonSampler.h under clang on Debian trixie -- the toolchain the sanitizer jobs use -- on both architectures, and by building the release artifact on AlmaLinux 8 for both: expf binds to each architecture's oldest version, getentropy@GLIBC_2.25 is the only remaining reference above 2.17, and the floor check passes at the declared 2.25 on both. Separately, ci-script-tests invoked the test suites directly. includes_test.sh is not executable and documents being run through bash, so the job failed with exit 126 after its own assertions had all passed. Both suites now run through bash, which leaves the existing file's mode alone. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
#794 removed the std::random_device uses, so libstdc++'s getentropy reference no longer enters the link and the last symbol above 2.17 is gone. With expf already pinned per architecture, the artifact requires nothing newer than the glibc that EL7 ships. Measured on release builds in the AlmaLinux 8 image on both architectures: no references above 2.17 at all, and the floor check passes at 2.17 where it previously named getentropy@GLIBC_2.25. Both artifacts also load on Oracle Linux 7.9 (glibc 2.17) with no unresolved symbol versions, which tests the dynamic linker rather than the symbol table. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
ab651e5 to
68cf4d8
Compare
Reliability & Chaos Results✅ All reliability & chaos checks passed Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/137918131 |
Summary
Spun out of the CI investigation on #786. Gets the GitLab pipeline off end-of-life base images, and moves the shipped binaries onto a toolchain that still receives security rebuilds.
Root cause of the current failures
bullseye-security's pool files have been pruned upstream while the index still advertises them — Debian 11 left LTS on 2026-08-31. Soapt-get updatesucceeds andapt-get installthen 404s on the individual.debs:Reproduced outside CI, straight from Fastly with no proxy in the path, so it is neither the fabric egress gateway nor a stale mirror. The
+deb11uNNversions and glibc 2.31 also identify the running image as bullseye.Bullseye cannot be rescued in place:
bullseye-securityis not published onarchive.debian.orgunder either its own name or the olderbullseye/updatesname, and the main archive alone leaves held broken packages. A bullseye image can no longer even be rebuilt, since its ownapt-get installlayer fails. Verified: the job's exact package list installs cleanly on trixie.Why the shipped-binary image matters more than it looks
Release builds link
-static-libstdc++ -static-libgcc(ConfigurationPresets.kt:121-122), so the C++ runtime and unwinder in the shippedlibjavaProfiler.socome out of the build image's toolchain. The build image's support status is therefore a property of the artifact, not just of CI.On CentOS 7 those objects can never get another security rebuild. Worse for consistency: no gcc newer than devtoolset-10 was ever built for aarch64 anywhere in the EL7 ecosystem — the dts-11 collection ships
binutils2.36.1,elfutils,dwzand its owntoolchainmetapackage for aarch64, but no compiler. Confirmed against CentOS vault altarch, Oracle's SCL repo and the CentOS SIG buildlogs. Oracle Linux 7 is the best-maintained EL7 derivative (live CDN, both arches, working collections) but its public repos sit at the same 7.9 package levels, with later content behind an ELS subscription.Changes
1. Shipped binaries: AlmaLinux 8 + gcc-toolset-14, both arches — new
.gitlab/base/el8/Dockerfile, replacing.gitlab/base/centos7/Dockerfileand itsvault.centos.org/ Rocky-mirror repository plumbing. Supported until 2029, and the toolset is published for x86_64 and aarch64 alike, so one image definition covers both architectures with one compiler version.almalinux:8is a multi-arch OCI index (amd64, arm64, ppc64le, s390x), so a singleBASE_IMAGE_GLIBCdigest serves both.2. Test/tooling images: Debian 13 trixie, both arches. With the shipped arm64 build moved off the Debian image, that image no longer sets a customer-facing floor, so it moves to current stable alongside amd64 — which is what restores apt on the arm64 sanitizer jobs.
3. The archived buster default is gone with
centos7/Dockerfile. It defaulted toopenjdk:11-slim-buster; buster's repos are archived (does not have a Release file) andrebuild-images.shreaches that default whenever the base-image variable is unset, since it onlylog_warns.4.
.gitlab/base/Dockerfile: no more swallowed install failures. Both theapt-get installandapk addlines ended in|| true, so a total package-install failure produced a "successful" image whose tools were simply missing. Theapkbranch was dead — Alpine/musl has its ownDockerfile.musl.5. Names describe purpose, not a version that has now changed twice:
BUILD_IMAGE_{X64,ARM64}_GLIBC,BASE_IMAGE_GLIBC, tag suffixes{x64,arm64}-glibc-base.6. The shipped artifact's ABI floor is now asserted in CI rather than being a property of whichever image the pipeline pulled — see below.
7.
ddprof-lib/src/main/cpp/glibcCompat.hholds the one.symverpin needed to keep the floor at 2.17, guarded to glibc (musl has no symbol versioning, and aGLIBC_*.symverfails to link there) and carrying its own deletion instructions for when the oldest supported host moves.The glibc floor is held at 2.17
The floor does not move. Building on a newer glibc does not by itself raise what
the artifact requires — symbol versions are per-function — but two references
did creep above 2.17, and both are now closed:
expf@GLIBC_2.27expfunder a new version and made it the default, so a build on 2.28 picks it up from unchanged sourceglibcCompat.hpins it to each architecture's oldest interface —GLIBC_2.2.5on x86_64,GLIBC_2.17on aarch64getentropy@GLIBC_2.25std::random_devicewithgetentropy, and-static-libstdc++copies that object into the library. Not referenced by anything in this sourcestd::random_deviceuses. glibc exports only one version ofgetentropy, so no pin could have reached itThe version to pin is per-architecture because a symbol's oldest version is the
baseline of the glibc release that introduced that port.
expf@GLIBC_2.17doesnot exist on x86_64, where the symbol predates 2.17 by a decade.
Measured on the real artifact, built in this image on both architectures:
GLIBC_2.17GLIBC_2.17libstdc++/GLIBCXX/CXXABIAnd loaded on a real EL7 host rather than inferred from symbol tables — Oracle
Linux 7.9, glibc 2.17, both architectures:
So RHEL 7.9 and its derivatives keep working, and this PR has no
customer-facing consequence. Everything in it is CI-internal.
An ABI floor check, so this cannot drift again
The 2.27 regression above reached a green pipeline and was found by accident
while chasing an unrelated failure. Nothing checked what the shipped library
requires, so the floor was whatever the current base image happened to produce.
.gitlab/scripts/check-abi-floor.shnow asserts two properties of every glibcartifact, at the end of the build job that produces it:
SHIPPED_GLIBC_FLOOR(.gitlab/config.env, declared next to the image pinthat determines it), failing with the offending symbols named;
DT_NEEDEDset is within an allowlist — which catches a dropped-static-libstdc++, the more dangerous regression of the two, since thelibrary would then need a C++ runtime from the host JVM's environment.
Three conditions fail loudly rather than reporting a pass: an artifact with no
versioned glibc symbols, one with no
NEEDEDentries, and asortthat cannotorder versions. Each would otherwise leave the check silently inoperative,
which is worse than not having it — and one of them earned its place during
development, when a parser that read only bare (non-parenthesised) symbol
versions saw a
.symver-pinned artifact as having no versioned references atall.
The checker reads through
$OBJDUMP, so its tests drive it with canned outputand need no compiler or shared object: 25 assertions in
.gitlab/scripts/tests/check_abi_floor_test.sh, run by a newci-script-testsjob alongside
shellcheck. Every guard was mutation-checked — disabling itindividually turns a named assertion red.
Images rebuilt and repinned
The images were rebuilt from this branch's definitions in pipeline 137281788 and the digests are pinned here. Each was verified by pulling it:
x64-glibc-base,arm64-glibc-basex64-base,arm64-baseThe musl, datadog-ci and benchmark images come from the same rebuild; their definitions are unchanged on this branch.
For the record on why the pins are here rather than in the image-update PR that rebuild opened against
main(#791, closed):mainhas noBUILD_IMAGE_*_GLIBCvariables, so the two shipped-binary pins silently did nothing there, whileBUILD_IMAGE_ARM64— whichmainstill uses forbuild:arm64andstresstest:arm64— would have moved the shipped arm64 binary onto a trixie image and its glibc 2.41 floor.Worth knowing for the future: the pins live in this repo, and every rebuild creates new pipeline-ID-prefixed tags rather than overwriting old ones, so rebuilding affects no branch until a repin is merged.
Verified locally
.gitlab/base/el8/Dockerfilebuilds for both platforms;gcc-toolset-14provideslibstdc++.a/libgcc.a, the profiler's exact link flags succeed, andjqneeds no EPEL../gradlew :ddprof-lib:buildReleasesucceeds inside the image on both architectures, and the measurements above come from those artifacts.expfpin links under clang on trixie — the sanitizer jobs' toolchain — on both architectures, and under musl, where the guard correctly skips it.One coverage regression this introduces, and how to undo it
The stress tests run in the same image as the build they consume, so moving the
build host moved them too:
stresstest:x64stresstest:arm64arm64 moves toward the floor. x64 moves away from it, and that was the only
place anything executed against glibc 2.17 — the functional matrix runs on
ubuntu-latestandalpine, and the sanitizer jobs on trixie, so nothing elsecomes near it.
Net: ABI coverage at the floor is much better than before (the check above, plus
loading the artifact on a real EL7 host), while runtime coverage at the floor
went from thin to zero. For a profiler that hooks PLT entries, walks
dl_iterate_phdr, and unwinds through libc's own frames, that is the half worthhaving.
It is separable from this PR, which is why it is not fixed here:
build:x64genuinely cannot stay on CentOS 7 — that is the point of the change — but
stresstest:x64has no such constraint. It compiles nothing, consuming theprebuilt library through
-Pskip-native -Pwith-libs(stresstests.sh:36-37),so it can run in an EL7 container while the build stays on EL8. That is
better than the old arrangement, because it would exercise the el8-built
artifact on 2.17 rather than a 2.17-built one.
Proposed follow-up, happy to open it alongside this:
.stresstest_jobits own image variable so the runtime environment stopsbeing tied to the build environment, and point the glibc targets at an EL7
image;
Temurin 8, 11, 17, 21 and 25 all start on glibc 2.17 (verified), so JDK
support is not a constraint — a two- or three-JDK subset on a nightly cadence
would cover the libc-sensitive suites without multiplying the matrix.
Worth being precise about what that would and would not test: a container gives
EL7 userspace on the host kernel, so it covers the glibc surface (linking,
PLT layout,
dl_iterate_phdr, TLS, unwinding through libc) but notperf_events,timer_createor signal delivery, which are the host kernel's.Real EL7-kernel coverage needs a VM and is a separate question.
Also noticed, not changed
DOCKER_IMAGE: …/images/docker:24.0.4-gbi-focal(images.yml) is Ubuntu 20.04, past standard support since April 2025. Left alone because I cannot enumerate replacement tags in that ECR registry..gitlab/fuzzing/.gitlab-ci.yml:20and.gitlab/jdk-integration/.gitlab-ci.yml:32alsoapt-get installat job time, so they depend on a live archive on every run, exactly as the sanitizer jobs do. Moving those packages into the images would make the pipeline independent of archive availability.ubuntu-latest/ubuntu-22.04runners andalpine:3.23containers are all still supported.🤖 Generated with Claude Code