Skip to content

lookout health

“Any issues with this cluster?” in one call: a ten-category scorecard (control-plane, nodes, crash loops, pending, rollouts, storage, add-ons, quotas, certs, webhooks) — every category answers healthy|degraded|unavailable, degraded ones with details. With —store, findings merge the sentinel’s open triage-status records: a scan mid-incident reports the diagnosis and the agent’s severity judgment, not a fresh unknown.

MCP tool: k8s_cluster_health

Terminal window
lookout health [flags]
FlagTypeDefaultMeaning
--topint3how many findings to name inline on a degraded category’s scorecard line
--cert-warnduration720hreport TLS certificates expiring within this window (certs category)
--storestring—path to a sentinel’s SQLite store (its —store file); merges open triage-status records so findings carry triage_* fields and severity reflects the agent’s override
--store-clusterstring—read/write the store for THIS cluster, treating —store as the multi-cluster stem the sentinel was given: —store=/var/lib/lookout/lookout.db —store-cluster=prod-us opens /var/lib/lookout/lookout-prod-us.db (issue #410). Set it only against a sentinel running —clusters/—clusters-from; a single-cluster sentinel writes the literal —store path
FlagTypeDefaultMeaning
--namespacestring—limit the scan to one namespace
-Abool—scan all namespaces
--workloadstring—target one workload as <Kind>/<namespace>/<name>, e.g. Deployment/prod/api
--sinceduration—how far back to look (0 = command default)
--formatstringlogfmtoutput format: logfmt|json (one record per line either way)
--timeoutduration10sabort the invocation after this long (exit 1)
--kubeconfigstring—path to a kubeconfig file, instead of $KUBECONFIG / ~/.kube/config
--contextstring—kubeconfig context to read, instead of its current-context. Selects a cluster for THIS invocation only — nothing is written back — so concurrent invocations can target different clusters. Reported as context=<name> in the summary line
--exemptionsstring—path to a git-reviewed exemption file (YAML); covered findings are ANNOTATED with their reason and expiry and counted as exempt=<n> in the summary, never dropped

Every kind= this command can emit, and the severities it carries them at. Nothing else appears in its output; a kind absent from a run means the check looked and found nothing. See the finding-kind glossary for the whole vocabulary.

KindSeverityClaim
health.categorycritical, warning, infoone scorecard line: how this category answered — healthy, degraded, or unavailable. The scorecard always answers, so healthy is explicit rather than silent; the line carries the worst severity found inside the category
pvc.pendingwarninga PersistentVolumeClaim is not bound; pods mounting it cannot start
pvc.lostcriticala PersistentVolumeClaim’s bound volume is lost
cert.expiredcriticala TLS secret’s certificate has expired
cert.expiringwarninga TLS secret’s certificate expires within —cert-warn
cert.invalidwarninga TLS secret’s tls.crt does not contain a parseable X.509 certificate
pod.crashloopcriticala container is crash looping
pod.imagepullcriticala container cannot pull its image
pod.waitingwarninga container is stuck in an error waiting state (CreateContainerConfigError, InvalidImageName, …)
pod.oomkilledwarninga container’s last termination was an OOM kill
pod.restartswarninga container has restarted at least —restarts times
pod.notreadywarninga container in a Running pod has been not-ready past the —pending-age grace
pod.failedwarningthe pod reached phase Failed
pod.pendingcritical, warningthe pod has been Pending longer than —pending-age with no container-level diagnosis; critical when the scheduler has declared it Unschedulable, which is a capacity or constraint problem rather than latency
workload.replicafailurecriticalthe controller cannot create pods at all (quota, PodSecurity, admission) — no pod exists to diagnose
workload.stalledcriticala Deployment’s Progressing condition is False: the rollout has given up
workload.rolloutcritical, warningreplicas are short of desired; critical when nothing is serving at all
job.failedwarninga Job’s Failed condition is set
cron.missedcritical, warningan unsuspended CronJob’s schedule said to run more than —cron-grace ago and status says it did not; critical once several activations in a row are gone
cron.unparseablewarninga CronJob’s spec.schedule could not be parsed, so its activations cannot be judged at all
node.notreadycriticalthe node’s Ready condition is not True
node.pressurecriticalthe node reports Memory/Disk/PID pressure
node.conditioncritical, warninga non-standard node condition is True — NPD and its cousins publish problems that way
node.cordonedwarningthe node is unschedulable but still holds pods: a stuck drain or a forgotten maintenance step
node.preemptcritical, warning, infoa reclaim taint marks the node for termination; severity tracks how imminent
pdb.gridlockedcritical, warningthe budget permits no disruptions; critical when healthy pods are already below the required minimum
addon.degradedcritical, warninga kube-system add-on (dns, proxy, cni, csi, metrics, connectivity) is short of replicas; critical when none are available
quota.nearwarninga ResourceQuota resource is at or past —quota-warn percent of its hard limit
quota.exhaustedcriticala ResourceQuota resource is at its hard limit: the next create is rejected
webhook.failing_closedcriticalthe webhook has no working backend and failurePolicy=Fail: every gated write is rejected cluster-wide
webhook.dead_backendwarningthe webhook’s service backend is missing, has no ready endpoints, or does not serve the named port
webhook.slow_riskinfothe webhook’s timeout is long enough to slow every gated write if the backend degrades
webhook.ca_expiredcriticalthe webhook’s caBundle has expired: the API server cannot verify it
webhook.ca_expiringwarningthe webhook’s caBundle expires within —cert-warn
perf.apiserver_p99critical, warningapiserver request latency p99 crossed the pack threshold for a verb/resource — warning from 1s, critical from 4s
perf.pack_unavailablewarninga metric the requested pack needs is not in the metrics workspace, so part of the pack could not run; the rest still did (no coverage lies)

Beyond the shared envelope fields (kind, severity, namespace, kind_of_object, name, reason, message, fingerprint, exempt_reason, exempt_expires):

FieldMeaning
categoryscorecard category the finding belongs to (on health.category: which category this line scores)
statuscategory status: healthy|degraded|unavailable (the scorecard always answers — healthy is explicit)
totalfindings in a degraded category
topworst findings of a degraded category inline, as kind[ namespace/name]; capped by —top
subjectTLS certificate subject (CN when set); never key material
not_afterTLS certificate NotAfter, RFC 3339
days_leftwhole days until NotAfter (negative = expired)
phasePersistentVolumeClaim phase on storage findings (Pending or Lost)
webhookadmission webhook as <configuration>/<webhook name>
serviceservice backend a webhook points at, as <namespace>/<name>
backendwhy a webhook backend is dead: service missing, no ready endpoints, or port <p> not on service
gatesnamespaces a webhook gates, from namespaceSelector: all namespaces, or <matched>/<total> namespaces with up to 5 names
rulescompact operations/resources summary of a webhook’s rules, e.g. “CREATE,UPDATE pods,deployments.apps”
object_selectora webhook’s objectSelector, when one is set
timeoutwebhook timeoutSeconds as <n>s (nil defaults to the API’s 10s)
triage_statustriage state from the matched record (investigating|triaged|actioned|escalated) — present only with —store on merged findings
triage_root_causethe incident agent’s root-cause hypothesis, from the matched triage-status record
triage_actionthe incident agent’s paper trail (PRs opened, escalations), from the matched triage-status record
triage_sessionincident session that wrote the matched triage-status record
triage_agehow long ago the matched triage-status record was last updated
containercontainer the finding is about (init containers prefixed init:)
imageimage reference that failed to pull
restartscontainer restart count
exit_codeexit code of the container’s last termination
last_statereason of the container’s last termination (e.g. OOMKilled)
agehow long the abnormal state has persisted
desireddesired replica/scheduled count from spec
readyready count from status
updatedupdated-to-current-revision count from status
availableavailable count from status
failedfailed pod count of a Job
schedulea CronJob’s spec.schedule
expectedthe activation a CronJob should have run and did not
missed_runsactivations missed since the anchor; ≥N when the walk was capped
anchorwhat the missed count was measured from: last_schedule or creation
time_zonea CronJob’s spec.timeZone, when set
last_schedulea CronJob’s status.lastScheduleTime, or never
active_jobsJobs a CronJob still has running
conditionnode condition type that is abnormal
tainttaint key indicating reclaim/drain
podspods affected (behind a cordoned node or a PDB)
healthycurrently healthy pods behind a PDB
requiredpods the PDB requires healthy
addonsystem add-on role: dns, proxy, cni, csi, metrics, connectivity
resourceResourceQuota resource name at or near its limit
usedquota usage from status
hardquota hard limit from status
pctquota usage as percent of the hard limit
packthe pack this finding belongs to; also the summary-line note naming the pack that ran
metricthe backend-neutral metric the query measured (pack_unavailable: the absent metric)
verbapiserver request verb for this series (apiserver pack)
observedthe worst (maximum) aligned value in the window, in the query’s unit — the breach basis
latestthe newest aligned value in the window
thresholdthe crossed threshold: the critical one when severity=critical, else the warning one
windowthe lookback the series cover (—since, or the pack default); also a summary-line note

Output: one finding per line (logfmt; —format=json for one JSON object per line), keys in fixed order; healthy resources emit nothing. The final line is always the summary: scanned= findings= elapsed= — findings=0 with a summary present means “scanned and healthy”; a stream without a summary line is void. Exit 0 data, 1 runtime error (diagnostics on stderr only), 2 usage.

Terminal window
lookout health
lookout health --format=json --top=5
lookout health --namespace=prod