Why health is separate from deploy status
A component’s deploy status answers “did the last apply succeed?”. That is a statement about the past. Health answers “is it running right now?”. Those two diverge constantly. Terraform and Helm skip when state already matches, so a component can report Active while its pods are stuck.helm upgrade can exit 0 while the release sits in
pending-install and nothing serves traffic. A component fixed by hand out of band keeps showing
yesterday’s error.
Health is therefore a separate axis. Nuon never overwrites deploy status with health or the other
way round, and it never guesses: if we cannot see a component, its health is unknown, never
healthy and never unhealthy.
Verdicts
A component that has never deployed reports
not-applicable and shows no resources — probes and
pushed checks describe a workload, and until one exists there is nothing for them to describe.
A rollout that cannot converge is degraded, not progressing. If a pod is stuck pulling a bad
image tag, crash-looping, or unschedulable, that surfaces on its workload immediately rather than
waiting out the Kubernetes progress deadline.
Verdicts are debounced: it takes three consecutive bad observations to flip a component bad and
two good ones to bring it back, so a single restart or a controller backing off does not page anyone.
Observations stop arriving for five minutes and the verdict becomes unknown, which is visually
distinct from a failure and never alerts.
Nuon also computes an install-level rollup, so an install reads “all components healthy” or “2
components unhealthy” at a glance.
What gets checked, with no configuration
Helm chart and Kubernetes manifest components are checked automatically. The runner lists the resources each component owns roughly every minute, assesses each one, and reports. Deployments, StatefulSets, DaemonSets, Jobs, PVCs, Ingresses and Pods are all covered, plus the Helm release’s own status. Controller-side failures that never appear in an object’s own status — an Ingress whose load balancer rejected a certificate, for example — are picked up from Kubernetes warning events. Terraform components have their cloud resources listed so you can see what a component owns. Those rows are inventory, not assessment, so they do not affect the verdict. Everything the runner sees is browsable per install under Resources, filterable by component, kind, namespace and health.Tuning it
Configuration is optional. It only tunes or extends what already happens.Probes
Probes assert that your app is actually serving, above the level of infrastructure. A Deployment can be perfectly ready while the thing inside it returns 500.http probe passes on 2xx/3xx and never follows redirects. A tcp probe passes if the port
accepts a connection. An exec probe passes on exit code 0 and reports the command’s output when it
fails; the command is an argv, never a shell string, and runs with a minimal environment (PATH,
HOME, TMPDIR) — it does not inherit the runner’s credentials. Every probe is bounded by a short
timeout, and probes run once per report cycle.
Probe results are ordinary health observations: they feed the same verdict, the same debounce, the
same alerts and the same history as everything else.
Custom checks
Probes cover what the runner can reach. For anything else — a business metric, a queue depth, a result your CI already computed, a Datadog monitor — push the check to Nuon and it becomes part of the component’s health:status is one of healthy, degraded, unhealthy, unknown. Check names are 1-100 characters of
letters, digits, dots, dashes, or underscores. A custom check can make a component
worse but never better — reporting healthy will not paper over crash-looping pods. Checks are
merged in on the runner’s report cadence, so the normal debounce applies to them unchanged.
A pushed check’s last report stands for 5 minutes by default. If yours reports less often than that,
say so with stale_after (up to 60m) or it will read as unknown between reports:
unknown rather than keeping its last verdict — it stays visible and
counted, but stops voting, because a stale answer is not an answer. unknown never alerts and never
outranks a check that did report.
Custom checks work on any component type, including Terraform components that have no Kubernetes
footprint.
Alerting
Health transitions fan out through webhooks and Slack like any other Nuon event. Subscribe with a single per-resource flag:component_healthoncomponents— delivers both directions, so a channel that hears about a failure always hears the recovery.install_degradedoninstalls— the install-level rollup crossing.
unknown never alerts, because a runner going
offline is already reported as runner inactivity and would otherwise page you once per component. And
when a component fails because something it depends on failed, the dependents are labelled
downstream of <component> and only the root cause alerts — one outage, one alert.
See Webhooks for the full subscription model.
Verified deploys
By default a deploy finishes when the apply succeeds. Setblock_deploy and it finishes only when
the component has held healthy for stabilization_window afterwards:
verify health step on the deploy workflow, narrating as it waits —
how much of the window is left, what it is watching, and which check is holding things up. It judges
the runner’s raw observations directly: everything observed inside the window is post-apply
evidence, so a failing observation fails the step immediately, and an all-healthy window passes the
moment the window ends. The wait is measured from the moment the apply finished, so a component that
was already healthy beforehand still has to prove it survived the change — and a deploy that fixes
an unhealthy component passes as soon as its own observations come back healthy.
This is off by default. Turning it on is the only way health can affect whether a deploy passes, and
a component Nuon cannot observe never blocks a deploy — absence of data is not treated as failure.
Gate probes must assert what the component itself provides. A probe that checks the public
endpoint belongs on the load-balancer component that exposes it — not on the app behind it. If a
component’s gate asserts something a downstream component provides, a first install deadlocks:
the app’s gate can never pass before the load balancer deploys, and the load balancer (which
depends on the app) never deploys because the gate is holding it back.
Gating a rollout: canary and bake periods
The pieces above compose into progressive delivery. The primitive is a fleet health summary, scoped by install label:tier=canary, poll until all_healthy is
true and has stayed true for your bake period, and only then continue to the rest of the fleet.
all_healthy is never true unless at least one install was actually evaluated, and installs whose
health has never been evaluated are counted separately in unset. A rollout must never read “no
data” as a pass, so the endpoint will not let it.
Combine this with custom checks and the gate becomes whatever you need it to be — error rate,
latency, or a business metric your own systems already track.
Proving it stayed up
Every debounced transition is recorded for 90 days, which gives each component and install a health timeline and an uptime percentage, rendered as status-page-style daily bars in the dashboard. Time spent inunknown is excluded from uptime rather than counted as either up or down: we will not
claim availability we did not observe. An install with no observations reports zero observed time
instead of a misleading 100%.
There is also an incident bundle per component, pulling together the failing transition, the captured
diagnosis (Kubernetes events, restart counts, termination reasons such as OOMKilled) and the deploy
it followed — useful as input to a runbook or an agent.
Enabling it
Component health is currently behind thecomponent-health org feature. Ask Nuon to switch it on for
your organization.