You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Under sustained high-concurrency LIST load (~7–9k req/s, list-heavy), the ipam-apiserver suffers process-wide Go heap corruption and crashes (CrashLoopBackOff), returning a 503 storm through the aggregation layer. This is a real, architecture-independent, pre-existing stability bug in the shared serving path — not introduced by the IPClass work (PR #70), which is proven race-free. Filed as a decoupled follow-up so the validated IPClass feature can land; the existing MaxConns=10 mitigation in cmd/ipam/serve.go keeps normal-load exposure low.
Severity
High — apiserver process crash under load; affects availability of all IPAM resources during read spikes.
Evidence it is real (not a test artifact)
Reproduces on two architectures: amd64 (emulated) ~9,300 req/s → ipam_read_success_rate 0.29%, restarts; native arm64 (no emulation) ~7,026 req/s → 1.67% success, both pods CrashLoopBackOff. Rules out qemu emulation.
Crash signature:fatal error: found bad pointer in Go heap / found pointer to free object + SIGSEGV on fault addresses that are ASCII string bytes used as pointers (e.g. 0x64726f63657280="record", "finish ", "…prefix"). Classic single-unsynchronized-writer-over-shared-heap data race.
Process-wide corruption: the fatal fault lands in a different subsystem each crash — JSON encode of the discovery doc, http.Header.Clone on an outbound client-go auth roundtrip, LIST decode metav1.Time.UnmarshalJSON, TLS handshake (cryptobyte), APF queueSet — i.e. the crashing goroutine is a victim, not the cause.
Disk ruled out: reproduced with /var bounded (audit-log rotation cap applied); not OOM (mem limit 4Gi, ample).
Not IPAM code
Zero IPAM frames in any crashing goroutine stack (grep miloapis in stacks = 0).
IPAM read/store/convert/watch paths proven race-free by exhaustive go test -race (added in PR docs: propose IPClass, a policy layer for claiming IP space #70): concurrent Store.GetList decode→convert→encode (JSON + protobuf) against real Postgres, live watchers + readers + writers, and the apiserver codec — all clean.
The IPClass conversion diff is purely additive read-only copies; it only enlarges IPPool objects slightly (more GC/alloc pressure), an aggravator at most.
Pre-existing
cmd/ipam/serve.go on origin/main already documents this exact bug: MaxConns is pinned to 10 as a mitigation for "intermittent heap corruption seen under sustained ~4–8k req/s load … inside Go's stdlib context.(*cancelCtx).propagateCancel map assignment … concurrency-induced runtime state corruption that surfaces only when many request goroutines overlap." The read spike at ~9k req/s exceeds that ceiling and re-exposes it.
Why the race detector can't name it (heisenbug)
Offline go test -race and a live -race build both came back clean. The -race binary is ~25× slower (~395 req/s), far below the crash threshold, so it never reaches the interleaving. No positive control available via -race.
Prime suspect: OTel HTTP instrumentation
Every crashing run has the apiserver's OTel instrumentation active (inbound otelhttpWithTracing middleware + the outbound delegated-auth otelhttp.Transport wrapping — the Header.Clone frame). This is driven by the APIServerTracing feature gate, which is LockToDefault:true on k8s 1.35 (vendored k8s.io/apiserver v0.35.3) — so it cannot be disabled by config (serve doesn't even expose --feature-gates).
Deps: go.opentelemetry.io/otel v1.43.0, otelhttp v0.68.0, otelpgx v0.11.1. otelhttp had data-race fixes but in the v0.48–v0.54 era (already included in v0.68.0); no obvious post-v0.68 fix found — a dependency bump is not a confirmed fix.
Reproduction
Deploy IPAM to kind.
task test/load:setup
task test/load:reads (0→100 VU list-heavy spike).
Crash is deterministic within one run once concurrency reaches ~7k+ req/s.
Suggested next steps
Confirm the OTel-instrumentation hypothesis with a code-patched build that strips the otelhttp wrapping (inbound WithTracing + outbound auth transport); this lives in vendored k8s.io/apiserver + client-go, so it's a non-trivial seam.
If confirmed: evaluate (a) a targeted apiserver-wiring change to avoid the crashing otel path (cost: lose apiserver tracing), (b) an otel dependency bump (verify newest changelog), or (c) upstream escalation to k8s/otel.
Decoupled. PR #70 is feature-complete and race-clean (e2e green; 3/4 perf thresholds pass — throughput 147/s, class-claim 93.9/s, exhaustion-deny; only read-success-rate blocked by this bug). Race-regression tests guarding the store/convert path shipped with PR #70.
Summary
Under sustained high-concurrency LIST load (~7–9k req/s, list-heavy), the
ipam-apiserversuffers process-wide Go heap corruption and crashes (CrashLoopBackOff), returning a 503 storm through the aggregation layer. This is a real, architecture-independent, pre-existing stability bug in the shared serving path — not introduced by the IPClass work (PR #70), which is proven race-free. Filed as a decoupled follow-up so the validated IPClass feature can land; the existingMaxConns=10mitigation incmd/ipam/serve.gokeeps normal-load exposure low.Severity
High — apiserver process crash under load; affects availability of all IPAM resources during read spikes.
Evidence it is real (not a test artifact)
ipam_read_success_rate0.29%, restarts; native arm64 (no emulation) ~7,026 req/s → 1.67% success, both pods CrashLoopBackOff. Rules out qemu emulation.fatal error: found bad pointer in Go heap/found pointer to free object+SIGSEGVon fault addresses that are ASCII string bytes used as pointers (e.g.0x64726f63657280="record", "finish ", "…prefix"). Classic single-unsynchronized-writer-over-shared-heap data race.http.Header.Cloneon an outbound client-go auth roundtrip, LIST decodemetav1.Time.UnmarshalJSON, TLS handshake (cryptobyte), APF queueSet — i.e. the crashing goroutine is a victim, not the cause./varbounded (audit-log rotation cap applied); not OOM (mem limit 4Gi, ample).Not IPAM code
grep miloapisin stacks = 0).go test -race(added in PR docs: propose IPClass, a policy layer for claiming IP space #70): concurrentStore.GetListdecode→convert→encode (JSON + protobuf) against real Postgres, live watchers + readers + writers, and the apiserver codec — all clean.Pre-existing
cmd/ipam/serve.goonorigin/mainalready documents this exact bug:MaxConnsis pinned to 10 as a mitigation for "intermittent heap corruption seen under sustained ~4–8k req/s load … inside Go's stdlibcontext.(*cancelCtx).propagateCancelmap assignment … concurrency-induced runtime state corruption that surfaces only when many request goroutines overlap." The read spike at ~9k req/s exceeds that ceiling and re-exposes it.Why the race detector can't name it (heisenbug)
go test -raceand a live-racebuild both came back clean. The-racebinary is ~25× slower (~395 req/s), far below the crash threshold, so it never reaches the interleaving. No positive control available via-race.Prime suspect: OTel HTTP instrumentation
otelhttpWithTracingmiddleware + the outbound delegated-authotelhttp.Transportwrapping — theHeader.Cloneframe). This is driven by theAPIServerTracingfeature gate, which isLockToDefault:trueon k8s 1.35 (vendoredk8s.io/apiserverv0.35.3) — so it cannot be disabled by config (servedoesn't even expose--feature-gates).go.opentelemetry.io/otelv1.43.0,otelhttpv0.68.0,otelpgxv0.11.1. otelhttp had data-race fixes but in the v0.48–v0.54 era (already included in v0.68.0); no obvious post-v0.68 fix found — a dependency bump is not a confirmed fix.Reproduction
task test/load:setuptask test/load:reads(0→100 VU list-heavy spike).Crash is deterministic within one run once concurrency reaches ~7k+ req/s.
Suggested next steps
otelhttpwrapping (inboundWithTracing+ outbound auth transport); this lives in vendoredk8s.io/apiserver+client-go, so it's a non-trivial seam.MaxConns=10mitigation until fixed.Relationship to PR #70 (IPClass)
Decoupled. PR #70 is feature-complete and race-clean (e2e green; 3/4 perf thresholds pass — throughput 147/s, class-claim 93.9/s, exhaustion-deny; only
read-success-rateblocked by this bug). Race-regression tests guarding the store/convert path shipped with PR #70.