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)
lookout audit workloads [flags]| Flag | Type | Default | Meaning |
|---|---|---|---|
--cron-suspended | duration | 168h0m0s | how 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 |
Common flags (every lookout command)
Section titled “Common flags (every lookout command)”| Flag | Type | Default | Meaning |
|---|---|---|---|
--namespace | string | — | limit the scan to one namespace |
-A | bool | — | scan all namespaces |
--workload | string | — | target one workload as <Kind>/<namespace>/<name>, e.g. Deployment/prod/api |
--since | duration | — | how far back to look (0 = command default) |
--format | string | logfmt | output format: logfmt|json (one record per line either way) |
--timeout | duration | 10s | abort the invocation after this long (exit 1) |
--kubeconfig | string | — | path to a kubeconfig file, instead of $KUBECONFIG / ~/.kube/config |
--context | string | — | 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 |
--exemptions | string | — | 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 |
Finding kinds
Section titled “Finding kinds”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.
| Kind | Severity | Claim |
|---|---|---|
audit.no_pdb | warning | the workload has no PodDisruptionBudget: a drain can take every replica at once |
audit.single_replica | warning | the workload runs a single replica, so any disruption is an outage |
audit.no_readiness_probe | warning | a container has no readiness probe, so traffic reaches it before it can serve |
audit.no_liveness_probe | info | a container has no liveness probe, so a wedged process is never restarted |
audit.no_spread | info | the workload’s replicas are not spread across nodes or zones |
audit.rigid_scheduling | warning, info | placement constraints pin the workload to too few nodes to survive losing one |
audit.hpa_cannot_scale | warning | the 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_cronjob | warning | a 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 |
Output fields
Section titled “Output fields”Beyond the shared envelope fields (kind, severity, namespace, kind_of_object, name, reason, message, fingerprint, exempt_reason, exempt_expires):
| Field | Meaning |
|---|---|
replicas | the workload’s spec.replicas (nil defaults to 1, matching the API server); absent on DaemonSets, whose replica count is the node count |
namespace_pdbs | PodDisruptionBudgets in the workload’s namespace — 0 says the namespace has no PDB culture at all, a non-zero value says this workload was missed |
containers | containers implicated by the finding: those missing the probe, or missing the request the autoscaler’s utilization target divides by |
container_names | their names, capped at 8 with a +N more tail |
total_containers | containers in the pod template, so containers reads as a fraction |
min_replicas | the HPA’s spec.minReplicas (nil defaults to 1, matching the API server) |
max_replicas | the HPA’s spec.maxReplicas |
metric | the utilization metric the HPA cannot compute, comma-separated if more than one |
scale_target | the HPA’s scaleTargetRef as Kind/name |
eligible_nodes | nodes satisfying the workload’s REQUIRED placement constraint; an upper bound, since taints and cordons are not subtracted |
cluster_nodes | nodes in the cluster, so eligible_nodes reads as a fraction |
constraint | the label and field keys that narrow placement, sorted and capped at 8 |
schedule | the suspended CronJob’s spec.schedule |
time_zone | the CronJob’s spec.timeZone, when set |
suspended_for | how long spec.suspend has been true, rounded to whole days |
suspended_since | when the suspension is estimated to have started, RFC 3339 |
anchor | the evidence that estimate came from: managed_field (the managedFields entry owning spec.suspend), last_schedule, or creation |
missed_runs | activations skipped since then; ≥N when the walk was capped, unknown when the schedule does not parse |
pdbs | summary note: PodDisruptionBudgets seen in scope |
hpas | summary note: HorizontalPodAutoscalers seen in scope |
nodes | summary note: nodes in the cluster — the denominator every placement claim is resolved against |
workloads | summary note: workloads examined, broken down as deployments/statefulsets/daemonsets/cronjobs |
Output contract
Section titled “Output contract”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=
Examples
Section titled “Examples”lookout audit workloads -Alookout audit workloads --namespace=prodlookout audit workloads --workload=Deployment/prod/checkoutlookout audit workloads --workload=CronJob/prod/nightly-backuplookout audit workloads -A --exemptions=exemptions.yaml --format=json