Skip to content

lookout audit workloads

Workload reliability posture for workloads that are healthy right now: no PodDisruptionBudget, only one replica, no readiness/liveness probe, no spread across nodes, placement pinned to too few nodes, autoscalers that structurally cannot scale, and CronJobs left suspended long enough to have skipped runs. Answers “what has no safety net”, as against stab drain, which answers “what breaks if I drain THIS node now”. Scope with —namespace, -A, or —workload; scanned counts workloads examined.

MCP tool: k8s_audit_workloads (MCP profile: audit)

Terminal window
lookout audit workloads [flags]
FlagTypeDefaultMeaning
--cron-suspendedduration168h0m0show long a CronJob must have been suspended before it reads as forgotten rather than as maintenance in progress; it must also have skipped at least one activation, so the claim scales to the schedule
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
audit.no_pdbwarningthe workload has no PodDisruptionBudget: a drain can take every replica at once
audit.single_replicawarningthe workload runs a single replica, so any disruption is an outage
audit.no_readiness_probewarninga container has no readiness probe, so traffic reaches it before it can serve
audit.no_liveness_probeinfoa container has no liveness probe, so a wedged process is never restarted
audit.no_spreadinfothe workload’s replicas are not spread across nodes or zones
audit.rigid_schedulingwarning, infoplacement constraints pin the workload to too few nodes to survive losing one
audit.hpa_cannot_scalewarningthe autoscaler structurally cannot scale: min equals max, the target is missing, or a container has no request for its utilization target to divide by
audit.suspended_cronjobwarninga CronJob has been suspended past —cron-suspended and has skipped activations because of it: whatever it does is not happening, and nothing else reports that

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

FieldMeaning
replicasthe workload’s spec.replicas (nil defaults to 1, matching the API server); absent on DaemonSets, whose replica count is the node count
namespace_pdbsPodDisruptionBudgets in the workload’s namespace — 0 says the namespace has no PDB culture at all, a non-zero value says this workload was missed
containerscontainers implicated by the finding: those missing the probe, or missing the request the autoscaler’s utilization target divides by
container_namestheir names, capped at 8 with a +N more tail
total_containerscontainers in the pod template, so containers reads as a fraction
min_replicasthe HPA’s spec.minReplicas (nil defaults to 1, matching the API server)
max_replicasthe HPA’s spec.maxReplicas
metricthe utilization metric the HPA cannot compute, comma-separated if more than one
scale_targetthe HPA’s scaleTargetRef as Kind/name
eligible_nodesnodes satisfying the workload’s REQUIRED placement constraint; an upper bound, since taints and cordons are not subtracted
cluster_nodesnodes in the cluster, so eligible_nodes reads as a fraction
constraintthe label and field keys that narrow placement, sorted and capped at 8
schedulethe suspended CronJob’s spec.schedule
time_zonethe CronJob’s spec.timeZone, when set
suspended_forhow long spec.suspend has been true, rounded to whole days
suspended_sincewhen the suspension is estimated to have started, RFC 3339
anchorthe evidence that estimate came from: managed_field (the managedFields entry owning spec.suspend), last_schedule, or creation
missed_runsactivations skipped since then; ≥N when the walk was capped, unknown when the schedule does not parse
pdbssummary note: PodDisruptionBudgets seen in scope
hpassummary note: HorizontalPodAutoscalers seen in scope
nodessummary note: nodes in the cluster — the denominator every placement claim is resolved against
workloadssummary note: workloads examined, broken down as deployments/statefulsets/daemonsets/cronjobs

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 audit workloads -A
lookout audit workloads --namespace=prod
lookout audit workloads --workload=Deployment/prod/checkout
lookout audit workloads --workload=CronJob/prod/nightly-backup
lookout audit workloads -A --exemptions=exemptions.yaml --format=json