Repository navigation
Pro auto-evidence: kwatch runs the read-only checks and shows the result - #584
Merged
Merged
Conversation
Replace the first-match investigators with a parallel check registry. The root-kind investigators lead the bounded channel summary, and every check adds a section to a dashboard report. A correlation pass names the failure signature and the object it points to, the nearest prior change, the spread and the rollout diff, and confirms the cause when the evidence proves it. Pro messages drop the read-only steps a check covered and pin the fix so provider limits cut the proof, never the fix. The dashboard gains an Evidence tab and the API gains evidence and correlation. Adds the investigation.logs setting and the Helm rbac.readyz and rbac.metricsApi values (default on). Community output is unchanged.
Fit Pro messages in the evidence cut order (quote lines, proof, quote, headline; never the fix) and pin the result with goldens per provider and cut level. Teams and email lay out the evidence; the structured webhook payload gains an additive evidence object. A memory kill at the limit gets a sized fix step from the observed peak. Sections carry their read time, shown on the dashboard. Discord incidents become one embed (status colour, title, description holding cause, proof, quote and fix, footer with cluster and time), bounded to the embed limits; plain notices stay content.
A check that looked and found nothing (no previous run, no events) now covers its read-only step: running the command shows nothing more. A read that failed for another reason gets the new error status and keeps its step. The usage check reports requests, limits and the 24h peak from the model when metrics-server is missing, events include the containers' notes, and "kubectl describe service" is covered by the Service check.
kwatch must not depend on add-ons such as metrics-server. The usage check reads the kubelet stats kwatch already polls (nodes/stats). The metrics.k8s.io read, its RBAC rule and the rbac.metricsApi Helm value are removed.
The DNS servers were only recognised by the Deployment names coredns and kube-dns, missing OpenShift's dns-default DaemonSet, RKE2's rke2-coredns-rke2-coredns and Helm installs with a release prefix. They are now the pods the DNS Service selects (kube-system/kube-dns, openshift-dns/dns-default, or a kube-system Service named for CoreDNS or selecting k8s-app=kube-dns) and their owners of any kind. The names stay as the fallback. The DNS metrics probe and the DNS outage rules use it.
The plan budget rose to 10s but announcements still waited only 5s for evidence, so on a busy cluster a crash loop could be announced before its checks returned, and the late result joined no message. The wait now covers the plan budget plus the worker's grace. Community has no investigation and never waits. The e2e evidence assertion also counts entries a roll-up or digest carried, and lists every matching entry when it fails.
A crash loop is announced on its first failure, when the kubelet still reports the exit as the container's current state and no last termination exists yet. The crash confirmation read only the last termination, so a fresh crash stayed at high confidence. It now takes the current exit code when there is no last one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements Pro auto-evidence (AUTO_EVIDENCE_PLAN.md, delivery row 2): if a command only reads state, kwatch runs it, correlates it and shows the result. Only commands that change the cluster stay as steps.
What changed
internal/pipeline/investigate): every applicable check runs in parallel under a 10 s plan budget, with per-check timeouts, at most 10 checks, at most 4 API reads in flight, and panic isolation. The 7 investigators are ported and still lead the bounded channel summary in their old priority order. New checks: pod state, logs (previous and current, per container, deduplicated by signature), events, memory use (kubelet stats only; no metrics-server needed), node pods, volume, Service and Ingress chains, rollout, control plane (/readyzand component pods), finalizers, object state, image and pull secrets, certificates, quota, HPA, Job, probes, and config key names (never values).Resultnow carriesReport []incident.Section(status ok, denied, timeout or absent, with a reason of rbac or off) and aCorrelation. The report is redacted at capture and again at serve, capped at about 64 KB and 500 lines per section, and kept in memory only (no schema change).correlate.go): classifies the log signature and links the host to a Service, Pod or Node. It also finds the nearest prior change, the spread across pods and nodes, and the rollout image and resources diff. Confirm rules cover an unreachable Service, OOM, a missing ConfigMap or Secret, a missing StorageClass, a missing pull secret, a kubelet waiting reason, node conditions, a selector that matches no pod, and a crash exit code.TestStepChecksCoverReadOnlyStepsscans everynotification.Stepliteral in compose and fails when a read-only command has no covering check.TestChecksCoverEveryStepCheckfails when that check isn't registered. A confirmed cause adds "… Confirmed." and sets confidence toconfirmed.RenderBlocksWithincuts the proof and keeps the pinned fix whole. Tests cover Discord (2000), Telegram and Slack sections, plus a sharedprovidertest.ProFixfixture.GET /api/incidents/{id}addsevidenceandcorrelation(omitted without a report). The new Evidence tab (?tab=evidence) has collapsible sections with badges (Read / Not allowed / Timed out / Nothing to read / Turned off), a log viewer with a container select, a previous/current toggle and marked lines, a related-objects chain, the correlation card, and Fix at the bottom. Copy followsspecs/auto-evidence.md, with the "Update menu" clause dropped as decided.investigation.logs(default true; false reads no log). Helmrbac.readyz(default true). Audit entries addevidence(check:status) andreadSteps.ExpectGatheredEvidence(audit based) on CrashLoop, OOM, ImagePull, missing Secret, Pending PVC, a Service with no endpoints, and node pressure. It requires 0 read-only steps,confirmed, and at least one check that read something.Community: no teaser and no dashboard link (per the decisions). Existing compose, replay and scenario goldens are unchanged.
Checks run locally
make verify: every stage passed, including race tests and coverage. The final deadcode stage flagged one unused helper; it was removed andmake deadcode-checkwas re-run and passes.make test-licensetestpasses.helm lintanddeploy/chart/test_helm.shpass; renders checked with the new values on and off.CI must still verify
e2e), especially the newconfirmedassertions, which have only been tested against fakes.Follow-ups in this PR
Cut order and goldens: a Pro message that overflows loses quote lines from the top first (down to 2), then proof from the bottom, then the quote, and last the headline. The fix is never cut (
safetext.FitPinned, also used by Telegram). Goldens cover each cut level 1 to 5 for Discord, Telegram and Slack (testdata/cut/level-N.golden).Teams and Email layouts: a Pro message with evidence gets a Teams card (cause, what kwatch found, monospace quote, fix; no action). Email gets the same order as plain text plus an HTML alternative. Both have goldens. Teams Workflows is unchanged: it sends one plain text block.
Computed fix: a workload whose container was killed at its memory limit gets
kubectl set resources … --limits=memory=<N>Mi(Mutating). N is 1.5 × the larger of the 24h peak and the limit, rounded up to 64Mi. Pro only.Webhook evidence: the structured payload (webhook, n8n, Zapier) adds
evidence{confirmed, checks, facts, quote, fix}. It is absent without a report, so existing fields and Community payloads are unchanged.Read times: each section carries the time it was read, from the injected clock. The API exposes it as
readAt, and the dashboard shows "Read at …".Discord embed (deliberate format change, both editions): incidents are one embed. The title is the headline, the colour follows the status, and the description holds cause, proof, quote and fix (4096, embed 6000; proof cut first, never the fix). The footer has kwatch, the cluster and the time. There is no field per section (Message too long for Discord #194, config doesn't filter Events in Discord messages (ref #175) #416). Mentions are neutralized and thread_id works. Plain notices stay content, cut to 2000. The delivery payload bound for Discord is now 6000. Contract goldens are updated.
e2e fixes: a check that read and found nothing (
absent) now covers its step. A read that failed for another reason gets the newerrorstatus and keeps its step. The usage check reports requests, limits and the 24h peak from kubelet stats. Events include container notes.kubectl describe serviceis covered by the Service check.No add-on dependency: the usage check reads only kubelet stats (nodes/stats, already granted). The metrics.k8s.io read, its RBAC rule and
rbac.metricsApiare removed.Cluster DNS discovery: DNS servers are found through the DNS Service (
kube-system/kube-dns,openshift-dns/dns-default, or a kube-system Service named for CoreDNS or selectingk8s-app=kube-dns), then the pods it selects and their owners of any kind. The coredns and kube-dns names are now only a fallback. This replaceskube.DNSServerNameswithkube.DNSServerPods,IsDNSServer,IsDNSWorkloadandTopOwner, used by the DNS metrics probe and the DNS outage rules. Tests cover OpenShift, RKE2, Helm-prefix and fallback setups.Contract changes
evidence[].readAt, and the section statuserror.evidenceobject.notification.Message.Evidence(json omitempty).Deferred