Skip to content

lookout triage top

Point-in-time CPU/memory saturation vs limits: kubectl top, but judged — usage-vs-limit percent per container with the OOM asymmetry built in (memory ≥95% of limit is critical, CPU caps at warning: it throttles, it does not kill); -A adds node usage vs allocatable. Trends/ETAs live in the sentinel’s saturation source; —history adds window stats via the cloud provider.

MCP tool: k8s_resource_top

Terminal window
lookout triage top [flags]
FlagTypeDefaultMeaning
--top-warnint80report a container/node only at or above this percent of its limit/allocatable (zero nominal state; memory additionally turns critical at 95%)
--allbool—exploratory dump: emit every sampled row regardless of —top-warn (info severity below it), sorted by pct descending; containers capped at —limit
--limitint50row cap for the —all container dump
--show-unlimitedbool—list each container missing a cpu or memory limit individually (default: one aggregate count)
--show-unrequestedbool—list each container missing a cpu or memory request individually (default: one aggregate count); a missing request is the scheduler-side half of the census, always a subset of —show-unlimited
--historyduration—enrich container findings with max/avg/p95 usage-vs-limit over this window via the cloud provider metrics backend; no provider → explicit unavailable finding + summary marker, point-in-time output unaffected
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
top.saturationcritical, warning, infoa container’s usage is close to its limit — critical near the limit, info for an —all row below the threshold
top.nodecritical, warning, infoa node’s allocatable is close to committed — critical near the limit, info for an —all row below the threshold
top.unlimitedinfohow many containers in scope set no cpu/memory limit, and are therefore invisible to saturation analysis
top.unlimited_containerinfoone container that sets no cpu/memory limit (—show-unlimited)
top.unrequestedinfohow many containers in scope set no cpu/memory request, so the scheduler bin-packs them as zero
top.unrequested_containerinfoone container that sets no cpu/memory request (—show-unrequested)
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

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

FieldMeaning
resourcethe judged dimension: cpu or memory
usagecurrent usage in the dimension’s natural unit (millicores for cpu, IEC bytes for memory)
limitthe container’s configured limit (top.saturation), same unit as usage
allocatablethe node’s allocatable capacity (top.node), same unit as usage
pctusage as a percent of the limit/allocatable, one decimal
containercontainer name within the pod
nodenode the pod runs on (top.saturation; top.node carries the node as name)
podstop.unlimited/top.unrequested: pods in scope with at least one container missing a cpu or memory limit (resp. request)
containerstop.unlimited/top.unrequested: containers in scope missing a cpu or memory limit (resp. request)
missingtop.unlimited_container/top.unrequested_container: which dimensions are absent (cpu, memory, or both)
limitrangetop.unlimited_container/top.unrequested_container: the namespace LimitRange(s) that default a dimension this container is missing — the pod predates them, so recreating it picks the value up
limitrange_defaultedtop.unlimited/top.unrequested: how many of the counted containers sit in a namespace whose LimitRange now defaults the dimension they lack
max_pct—history: highest usage-vs-limit percent observed in the window
avg_pct—history: mean usage-vs-limit percent over the window
p95_pct—history: 95th-percentile usage-vs-limit percent over the window
capabilitycloud.unavailable: the provider capability —history needed (metrics)
providercloud.unavailable: the provider that was asked
historysummary-line note: the —history window the stats cover
unavailablesummary-line note: why —history could not be served

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 triage top --namespace=prod
lookout triage top -A
lookout triage top --workload=Deployment/prod/api
lookout triage top --namespace=prod --all --limit=20
lookout triage top -A --top-warn=90 --show-unlimited
lookout triage top -A --show-unrequested
lookout triage top --namespace=prod --history=1h --format=json