Skip to content

fix(monitors): emit monitor_up and DNSLatencyHigh, enable kubernetes monitors by default - #52

Open
mattmattox wants to merge 12 commits into
mainfrom
fix/a6-monitors-metrics
Open

mattmattox wants to merge 12 commits into
mainfrom
fix/a6-monitors-metrics

Conversation

@mattmattox

Copy link
Copy Markdown
Contributor

node_doctor_monitor_monitor_up was registered but never set, so four IPv6 dashboard panels read 0 forever. It is now 1 per exported status and 0 on a check failure; the never-populated monitor_type label is dropped.

The DNS monitor emits DNSLatencyHigh from the p95 of successful queries against the existing latencyThreshold (default 1s), so the NodeDoctorDNSLatencyHigh alert can fire.

The four kubernetes-* monitors gain DefaultConfig, and the chart enables kubelet, apiserver, runtime, capacity, gateway, and connectivity monitors by default, so the kubernetes-health dashboard and kubelet/apiserver alerts have data. Existing RBAC covers the default settings.

Dashboards now filter on conditions the code emits; the "Swap Pressure" stat had no backing condition and shows memory warning events instead. A new test checks every condition_type in dashboards and PrometheusRules against literals in pkg/, and a helm test renders the chart and runs it through the real validators.

deployment/prometheusrule.yaml and servicemonitor.yaml are now generated from the chart (make manifests-generate) and drift-checked in CI.

🤖 Generated with Claude Code

mattmattox and others added 12 commits October 7, 2026 09:39
Cast statfs Bsize to int64 so pkg/monitors/system builds on macOS, where
the field is uint32. Make the unit tests portable: short unix socket
paths under the 104-byte limit, squat the wildcard address in the
Prometheus bind-failure test, discover the loopback interface name, and
skip tests that need 127.0.0.0/8 aliases or inotify rename semantics
when the host lacks them.

Remove the unused command-call tracking and testKernelPanicLog from the
logpattern mocks, and revive the memory estimation assertions that were
commented out.

Closes #20

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Delete process docs copied from another project's template
(docs/development/*, docs/deployment.md, .githooks/README.md) and
replace docs/README.md with an index of the docs that exist, grouped
for operators and developers. Repoint the links that targeted the
deleted files and add scripts/check-doc-links.sh to catch broken
relative links.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ftovers

scripts/validate-pipeline-local.sh only had case branches for another
project's components, so it ran zero checks and reported success. It now
runs gofmt, GOOS=linux go vet, golangci-lint (SKIP_LINT=1 to allow it
missing), gosec when installed, go test -short -race, helm lint and
helm-verify-generated, and exits non-zero on any failure.

Makefile: validate-component passes --component=; test-e2e uses
-tags=e2e and checks for kind; COVERAGE_THRESHOLD is 70 and ci.yml
calls make coverage-threshold so there is one source; docker-push and
push-*-image require an explicit VERSION and never tag or push :latest.
Removed the template header, helm-publish stub, deploy-*, bump*,
qa-check, devils-advocate, check-kubeconfig-* and their variables.

Deleted dead template files: init-from-template.sh,
substitute-variables.sh, validate-api.sh, the GPG scripts, .githooks
pre-push and setup.sh, hooks/, .validation.json, .version-rc.

validate-repo-cleanliness.sh no longer rejects the repo's own
Dockerfile.overlay-test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The chart and deploy manifests referenced supporttools/node-doctor-controller,
but nothing built or published it, so enabling the controller always ended in
ImagePullBackOff. Add Dockerfile.controller (static Go binary on scratch,
non-root, writable /data for SQLite), wire it into the Makefile image targets,
build it in ci.yml, and publish, sign and guard it in release.yml alongside
the other two images.

The chart's controller tag now follows image.tag instead of appVersion, which
carries a v prefix that no published image tag has. deploy/controller pins the
current release instead of :latest, and the e2e harness builds and loads the
controller image kind expects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…load strategy validation

The registry only received the circuit breaker config on hot reload, so a
fresh process ran with the package default (5 / 5m / 2) instead of the
configured values, and circuitBreaker.enabled was read nowhere outside
defaults and validation. NewRegistryFromConfig now wires the registry at
startup through the same conversion ApplyConfig uses, and a disabled
breaker bypasses trip/open checks while reporting Closed. Circuit breaker
defaults apply regardless of enabled so a disabled block no longer leaves
zeros the reload guard rejects.

Remediate read dryRun without the lock and recordRateLimitEntry read
maxPerHour before taking it; both raced with the setters reachable from
hot reload.

The reload validator accepted only restart/recreate/script/webhook, none
of which exist, so every real strategy failed reload validation. It now
uses the exported strategy list from pkg/types, and the config error
message builds from the same list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A plain bool could not tell an absent circuitBreaker block from an
explicit enabled: false, so omitting the block disabled the breaker.
CircuitBreakerConfig.Enabled is now a *bool like ReloadConfig.Enabled:
nil becomes true in ApplyDefaults and only an explicit false disables.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t, fix health server details

main never called det.Stop(): it cancelled its own context, which the
detector does not use, then stopped exporters while the status loop was
still exporting to them. The 30s shutdown select had a default branch,
so it never waited and always logged success. Shutdown now runs
detector -> exporters (reverse start order, health server last) in a
goroutine and waits on it against the timeout.

Detector: Stop no longer closes statusChan, which a fan-in goroutine
from a monitor that timed out on Stop could still send on. The monitor
Stop call moved inside the timed goroutine so a hung Stop cannot wedge
shutdown. stopMonitorByName removes the handle only after a successful
stop. Exporter iterations copy the slice under RLock.

Health server: Content-Type is set before WriteHeader on /healthz and
/ready, /status reports the version passed via SetVersion, Start honors
Enabled and stops when its context is cancelled, and SetReady(false)
holds against incoming statuses so main can withdraw readiness before
monitors stop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…alidators

Top-level config now fails to load on unknown keys (yaml KnownFields / json DisallowUnknownFields) and the error names the key. Monitor config maps stay free-form, but every monitor validator now logs a [WARN] line listing keys its parser does not read, including nested mount points, log patterns, endpoints, kubelet auth and the DNS sections. ValidateWithRegistry runs each enabled monitor's registered validator so -validate-config catches per-type errors instead of reporting success on configs that fail at startup. Memory, disk and CPU thresholds accept YAML integers, and the disk and DNS built-in defaults use the keys their parsers read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hart

The default config, every example, and the chart ConfigMap used keys no parser reads (paths, loadAverageThresholds, memoryThresholds, domains, count, checkPods, pattern, exitCodeMapping, and others), so those monitors silently ran on built-in defaults and the logpattern examples failed to start. All of them now use the parser keys; the chart values are renamed to match (cpu.warningLoadFactor/criticalLoadFactor, memory.*Threshold/checkOOMKills, disk.mountPoints/checkReadonly/checkIOHealth) and the dead exporters.http bind/port/tls/endpoints and settings.enableRemediation values are removed. A new test loads every YAML under config/ plus the rendered chart ConfigMap, runs registry validation, constructs each monitor, and fails on any unknown-key warning.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…monitors by default

monitor_up was registered but never set, so the IPv6 dashboard panels reading
it were permanently 0. ExportStatus now sets it to 1 per exported status and
to 0 when the status is the BaseMonitor check-failure status (error, panic or
timeout). The unused monitor_type label is dropped so the label set matches
the other per-monitor metrics.

The DNS monitor now emits a DNSLatencyHigh condition from the p95 of the
cycle's successful queries against the existing latencyThreshold key, so the
NodeDoctorDNSLatencyHigh alert can fire.

The four kubernetes-* monitors gain DefaultConfig so ApplyDefaultMonitors adds
them, and the chart enables kubelet, apiserver, runtime, capacity, gateway and
connectivity monitors by default so the kubernetes-health dashboard and the
kubelet/apiserver alerts have data in a default install.

Dashboards now filter on conditions the code actually emits (KubeletDown,
KubeletUnhealthy, APIServerUnreachable, CPUThermalHealthy, DNSResolutionDown);
a dashboards test enforces this for every dashboard and PrometheusRule.

deployment/prometheusrule.yaml and deployment/servicemonitor.yaml are now
rendered from the chart via make manifests-generate and checked in CI with
make manifests-verify-generated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant