Always-200 /livez and /readyz, and a container HEALTHCHECK¶
Status¶
Accepted — implemented 2026-08-01. Adopts the family probe and container-image
standards this repo was skipped by; see the family catch-up plan
2026-08-01-family-catch-up.md.
Context¶
/health was registered but empty: healthHandler wrote nothing and returned a
bare 200. The Helm chart pointed both the liveness and readiness probes at it,
and neither Dockerfile nor either compose file declared a health check, so a
container that had stopped serving looked identical to a healthy one.
This exporter is blackbox-style: /metrics?target= collects one BMC per
request, and the SnapshotStore is only built when OTLP is enabled. There is
therefore no background collection state a readiness gate could consult — and
no honest way to express "not ready".
Decision¶
Three fixed paths, all unconditionally 200:
/livezand/readyzare wired to onestaticOKHandlerthat reads no configuration, no collector and no snapshot. A probe here can never be the reason a working process is restarted or pulled from rotation./healthkeeps status 200 unconditionally and gains an informational JSON body:status,version,revision, and onehosts[]entry per configured BMC (host,scheme, anddefault_target, a bool flagging whether that host matches the deprecated root-leveldefault_targetconfig setting). Thedefaultmap key is a credential fallback, not a target, and is excluded. There is nolast_scrapeor per-hostokfield — per-host reachability is answered byidrac_upon a scrape.- Probes never point at
/metrics: a probe tick would drive a real Redfish scrape and can block behind an unreachable BMC.
The routes are registered with http.HandleFunc on http.DefaultServeMux,
alongside the six existing routes. This repo's server is deliberately left on
the default mux — matching the existing idiom was preferred over a refactor
whose only purpose would be family cosmetics.
Both Dockerfiles gain
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 running
wget --spider http://127.0.0.1:9348/livez, and both compose files gain a
matching healthcheck: with identical numbers. The address is 127.0.0.1, not
localhost: busybox wget resolves localhost to ::1 first and the exporter
binds IPv4 only.
The Alpine base tag is unpinned to alpine:latest, replacing alpine:3.23.
Consequences¶
Kubernetes and Docker probes stop depending on configuration or BMC
reachability, so a transient BMC outage can no longer restart the exporter or
remove it from a Service. /health becomes useful to a human — it says what the
exporter is configured to scrape — while remaining useless as a gate, which is
the point.
Unpinning the Alpine tag cuts against ADR 0001, which pins GitHub Actions by SHA, the tool versions, and the Go builder: the base image becomes the one input whose contents can change between two builds of the same commit, which is what the SBOM and provenance attestations exist to nail down. Uniformity across the fifteen family repos was chosen over reproducibility on the three that pinned. Revisiting it is a family-wide decision, not a per-repo one.
The Helm chart's liveness and readiness probes move from /health to /livez
and /readyz. /health remains served and remains 200, so any external check
pointed at it keeps working.
The HEALTHCHECK itself assumes a plaintext listener (wget --spider
http://127.0.0.1:9348/livez). Deployments that enable tls.enabled and
terminate TLS in the container must override it — either point wget at
https://127.0.0.1:9348/livez with --no-check-certificate, or disable the
check — or the probe fails against a perfectly healthy exporter. This is a
family-wide property of the standard, not an idrac-specific regression: TLS
is absent from both config.yaml and default-config.yml here, so plaintext
remains the right default, but the gap is real wherever a sibling repo (or a
future idrac deployment) turns TLS on.