lookout scan
Start here when you know something is wrong but not what: one call runs every target-free incident check across the cluster — broken workloads, dead admission webhooks, stuck volumes and PVCs, rejected Gateway routes, config drift — then drills into the dependency edges of whatever it flagged. Needs no target; --include=audit adds the posture sweep.
MCP tool: k8s_scan (MCP profile: triage, audit)
lookout scan [flags]| Flag | Type | Default | Meaning |
|---|---|---|---|
--include | string | — | additionally run these opt-in groups: audit,cloud,perf. Comma-separated, ‘all’ for every one, ’-’ to subtract (all,-cloud). Left out by default because they answer a different question (audit = posture, no incident) or need a provider build (cloud, perf) |
--max-drilldown | int | 20 | cap the stage-2 dependency-edge drill-down at this many workloads, worst severity first (0 disables it); the number dropped is reported as truncated= in the summary |
--cert-warn | duration | 720h | report TLS certificates expiring within this window (drill-down stage; same meaning as state edges --cert-warn) |
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 | 1m0s | 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 |
|---|---|---|
scan.check_skipped | info | a stage declined this invocation because a zero-argument scan cannot supply something it needs — the coverage claim is smaller than it looks |
scan.check_failed | warning | a stage errored; the scan continued without it, so this run saw less than a whole cluster — unless EVERY stage failed and none read anything, which is a runtime error (exit 1) rather than a scan |
scan.incomplete | warning | the —timeout expired with stages still to run; not_run names them |
pod.crashloop | critical | a container is crash looping |
pod.imagepull | critical | a container cannot pull its image |
pod.waiting | warning | a container is stuck in an error waiting state (CreateContainerConfigError, InvalidImageName, …) |
pod.oomkilled | warning | a container’s last termination was an OOM kill |
pod.restarts | warning | a container has restarted at least —restarts times |
pod.notready | warning | a container in a Running pod has been not-ready past the —pending-age grace |
pod.failed | warning | the pod reached phase Failed |
pod.pending | critical, warning | the 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.replicafailure | critical | the controller cannot create pods at all (quota, PodSecurity, admission) — no pod exists to diagnose |
workload.stalled | critical | a Deployment’s Progressing condition is False: the rollout has given up |
workload.rollout | critical, warning | replicas are short of desired; critical when nothing is serving at all |
job.failed | warning | a Job’s Failed condition is set |
cron.missed | critical, warning | an 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.unparseable | warning | a CronJob’s spec.schedule could not be parsed, so its activations cannot be judged at all |
node.notready | critical | the node’s Ready condition is not True |
node.pressure | critical | the node reports Memory/Disk/PID pressure |
node.condition | critical, warning | a non-standard node condition is True — NPD and its cousins publish problems that way |
node.cordoned | warning | the node is unschedulable but still holds pods: a stuck drain or a forgotten maintenance step |
node.preempt | critical, warning, info | a reclaim taint marks the node for termination; severity tracks how imminent |
pdb.gridlocked | critical, warning | the budget permits no disruptions; critical when healthy pods are already below the required minimum |
addon.degraded | critical, warning | a kube-system add-on (dns, proxy, cni, csi, metrics, connectivity) is short of replicas; critical when none are available |
quota.near | warning | a ResourceQuota resource is at or past —quota-warn percent of its hard limit |
quota.exhausted | critical | a ResourceQuota resource is at its hard limit: the next create is rejected |
webhook.failing_closed | critical | the webhook has no working backend and failurePolicy=Fail: every gated write is rejected cluster-wide |
webhook.dead_backend | warning | the webhook’s service backend is missing, has no ready endpoints, or does not serve the named port |
webhook.slow_risk | info | the webhook’s timeout is long enough to slow every gated write if the backend degrades |
webhook.ca_expired | critical | the webhook’s caBundle has expired: the API server cannot verify it |
webhook.ca_expiring | warning | the webhook’s caBundle expires within —cert-warn |
volume.multi_attach | critical | an RWO claim is wanted by pods on more than one node — the second pod never starts |
volume.zone_conflict | critical | the PV is locked to a zone the pod’s node is not in |
volume.attach_error | critical, warning | the attach or detach is failing; critical once it has been failing long enough to be stuck rather than slow |
volume.orphaned_attachment | info | a VolumeAttachment survives its PV or its node |
storage.missing_class | critical | the claim names a StorageClass that does not exist — it will stay Pending forever |
storage.no_default_class | critical | the claim names no class and the cluster has no default StorageClass |
storage.no_provisioner | warning | the claim’s class is static-only (kubernetes.io/no-provisioner) and no matching PV is available |
storage.multiple_defaults | warning | more than one StorageClass is annotated as the cluster default; which one wins is not defined |
storage.pv_failed | warning | a PersistentVolume is Failed: its reclaim did not complete, so the backing disk stays allocated and the volume cannot be reused |
storage.pv_released | info | a PersistentVolume is Released — retained on purpose, but its capacity is unusable until spec.claimRef is cleared |
gateway.missing_class | critical | the Gateway names a GatewayClass that does not exist — nothing will program it |
gateway.class_not_accepted | critical | the Gateway’s GatewayClass is not Accepted by its controller |
gateway.not_accepted | critical | the Gateway itself is not Accepted |
gateway.not_programmed | critical | the Gateway is Accepted but not Programmed: no data plane is carrying its traffic |
gateway.listener_invalid | warning | one listener of an otherwise working Gateway is not resolved or not programmed |
route.missing_parent | critical | the route’s parentRef names a Gateway that does not exist |
route.not_accepted | critical | the Gateway refused the route’s attachment (listener, hostname, or namespace policy) |
route.missing_backend | critical | the route’s backendRef Service does not exist |
route.backend_port | critical | the route’s backendRef Service exists but does not expose the named port |
crd.unavailable | info | the API group this check reads is not served by the cluster, so nothing was examined (no coverage lies) |
wi.gsa_missing | critical | the annotated Google service account does not exist — every GCP call from these pods fails |
wi.unbound | critical | the KSA annotates a GSA but the roles/iam.workloadIdentityUser binding is missing or malformed |
wi.unannotated_use | info | a pod sets GOOGLE_APPLICATION_CREDENTIALS but its ServiceAccount carries no Workload Identity annotation |
cloud.unavailable | info | the cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence |
drift.manual_edit | critical, warning | a manager other than the GitOps controller owns spec fields on this object; critical when one of them is high blast radius (image, replicas, env) |
audit.workload_identity_off | warning | Workload Identity is off cluster-wide, or a node pool bypasses it — pods authenticate to the cloud as the node |
audit.legacy_metadata | warning | a node pool still serves the pre-v1 instance-metadata endpoints, which any pod can read |
audit.public_control_plane | warning, info | the control-plane endpoint is reachable from the internet; info when authorized networks narrow it |
audit.exemption_expired | warning | an exemption entry has lapsed: the findings it used to annotate are being reported unqualified again |
audit.exemption_expiring | info | an exemption entry lapses within —within — renew it or let it go deliberately |
audit.privileged_container | warning | a container runs privileged or holds a node-root capability (ALL, SYS_ADMIN): a container escape is a node compromise |
audit.host_namespace | warning | the pod shares the node’s network, PID, or IPC namespace |
audit.hostpath_mount | warning, info | the pod mounts a host path; warning when it is writable, info when read-only |
audit.default_sa_automount | warning | the pod runs as the namespace’s default ServiceAccount with its token automounted, and something in the pod can use it |
audit.podsecurity_gaps | warning | the namespace enforces no Pod Security Admission level, so none of the above is prevented |
audit.netpol_missing | warning, info | nothing restricts this direction for the subject — a namespace with no policy at all, or a workload the covering policies’ selectors miss; info for the egress direction, where no policy is a defensible default |
audit.version_behind | warning, info | the control plane or a node pool is behind what the provider publishes, or a node pool has skewed from the control plane; info while the gap is still within the supported skew |
audit.upgrade_unmanaged | warning | nothing will close that gap on its own: no release channel, or node auto-upgrade/auto-repair off |
audit.upgrade_blocked | warning, info | an active maintenance exclusion, or a node image on the removed Docker runtime, will stop the upgrade when it comes |
audit.upgrade_unattended | info | upgrades will happen with nobody watching: no maintenance window, or no upgrade notifications |
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 |
ipspace.range | critical, warning, info | a pod/service/node range is at 80% of its CIDR or worse; critical from 95%, info for a range the cloud APIs cannot rate and for an —all row below the line |
orphan.disk | warning | a GCE disk has been unattached for at least —min-age and is still billing |
orphan.lb | warning | a forwarding rule or load balancer routes to zero endpoints and is still billing |
quota.pressure | critical, warning, info | a cloud quota is at or above —quota-warn percent of its limit; critical from 95%, info for an —all row below the line |
stockout.zone | warning | the cloud had no capacity for a machine type in this zone during the window — the reason a scale-up failed and pods stayed Pending |
perf.apiserver_p99 | critical, warning | apiserver request latency p99 crossed the pack threshold for a verb/resource — warning from 1s, critical from 4s |
perf.apf_saturation | critical, warning | an API Priority and Fairness level is holding a sustained queue — warning from 10 queued, critical from 100 |
perf.apf_rejects | critical, warning | APF is shedding load: the apiserver is returning 429s at a priority level |
perf.etcd_fsync | critical, warning | etcd WAL fsync p99 crossed the pack threshold — warning from 10ms, critical from 100ms |
perf.etcd_db_size | critical, warning | the etcd database is approaching its quota — warning from 4 GiB, critical from 5.5 GiB |
perf.startup_p95 | critical, warning | pod first-ready p95 crossed the pack threshold — warning from 60s, critical from 300s |
perf.pack_unavailable | warning | a 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) |
edge.missing_ref | critical | a referenced ConfigMap, Secret, ServiceAccount, TLS secret, IngressClass, StorageClass, or governing Service does not exist |
edge.missing_key | critical | the referenced key is absent from an existing ConfigMap/Secret |
edge.invalid_ref | warning | the referenced object exists but is the wrong type to serve the reference |
edge.unclassed | warning | the Ingress names no class and no IngressClass declares itself the cluster default — no controller will claim it |
edge.selector_empty | critical | a Service selector selects zero pods, so the service routes nowhere |
edge.selector_unready | critical, warning | the Service selects pods but some are not Ready; critical when none are |
edge.endpoints_missing | critical | a selecting Service has no EndpointSlices at all |
edge.endpoints_orphaned | warning | an endpoint targetRef names a pod that no longer exists |
edge.endpoints_unready | critical, warning | the endpoint ready-count disagrees with the selected pods (stale or lagging slices); critical at zero ready |
edge.backend_missing | critical | an Ingress backend service, or the port it names, does not exist |
edge.cert_expired | critical | a TLS certificate’s NotAfter is in the past |
edge.cert_expiring | warning | a TLS certificate expires within —cert-warn |
edge.cert_invalid | warning | tls.crt is missing or unparseable, or the secret is not kubernetes.io/tls |
edge.rbac_dangling | warning | a (Cluster)RoleBinding for the workload’s ServiceAccount points at a missing (Cluster)Role |
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 |
|---|---|
check | which registered command produced this finding — also the command to run for the full detail behind it |
not_run | on scan.incomplete: the checks the timeout left unrun, comma-separated |
checks | summary-line note: how many checks this scan actually ran |
skipped | summary-line note: opt-in groups this scan did NOT run (switch one on with —include=<group>) — stated so a quiet scan is never mistaken for a complete one |
drilldown | summary-line note: workloads the stage-2 dependency-edge drill-down covered |
truncated | summary-line note: drill-down candidates dropped by —max-drilldown |
container | container the finding is about (init containers prefixed init:) |
image | image reference that failed to pull |
restarts | container restart count |
exit_code | exit code of the container’s last termination |
last_state | reason of the container’s last termination (e.g. OOMKilled) |
age | how long the abnormal state has persisted |
desired | desired replica/scheduled count from spec |
ready | ready count from status |
updated | updated-to-current-revision count from status |
available | available count from status |
failed | failed pod count of a Job |
schedule | a CronJob’s spec.schedule |
expected | the activation a CronJob should have run and did not |
missed_runs | activations missed since the anchor; ≥N when the walk was capped |
anchor | what the missed count was measured from: last_schedule or creation |
time_zone | a CronJob’s spec.timeZone, when set |
last_schedule | a CronJob’s status.lastScheduleTime, or never |
active_jobs | Jobs a CronJob still has running |
condition | node condition type that is abnormal |
taint | taint key indicating reclaim/drain |
pods | pods affected (behind a cordoned node or a PDB) |
healthy | currently healthy pods behind a PDB |
required | pods the PDB requires healthy |
addon | system add-on role: dns, proxy, cni, csi, metrics, connectivity |
resource | ResourceQuota resource name at or near its limit |
used | quota usage from status |
hard | quota hard limit from status |
pct | quota usage as percent of the hard limit |
webhook | admission webhook as <configuration>/<webhook name> |
service | service backend the webhook points at, as <namespace>/<name> |
backend | why the backend is dead: service missing, no ready endpoints, or port <p> not on service |
gates | namespaces the webhook gates, from namespaceSelector: all namespaces, or <matched>/<total> namespaces with up to 5 names |
rules | compact operations/resources summary of the webhook’s rules, e.g. “CREATE,UPDATE pods,deployments.apps” |
object_selector | the webhook’s objectSelector, when one is set |
timeout | webhook timeoutSeconds as <n>s (nil defaults to the API’s 10s) |
subject | CA-bundle certificate subject (CN when set); never key material |
not_after | CA-bundle certificate NotAfter, RFC 3339 |
days_left | whole days until NotAfter (negative = expired) |
nodes | distinct nodes those pods are scheduled on, sorted |
access_modes | the claim’s declared access modes |
pv | PersistentVolume behind the claim or attachment |
pvc | PersistentVolumeClaim the pod mounts (same namespace as the pod) |
node | node the attachment targets or the pod is scheduled on |
attacher | CSI driver responsible for the attachment (spec.attacher) |
error | the attach/detach error message, truncated to 200 chars |
attached | the attachment’s status.attached at scan time |
pv_zones | zones the PV’s node affinity allows, sorted |
node_zone | zone label of the node the pod is scheduled on |
orphan | which referenced side is gone: “pv missing”, “node missing”, or both |
storage_class | StorageClass the claim names, or the class the finding is about |
classes | StorageClasses the cluster does have, sorted (empty when there are none) |
defaults | StorageClasses annotated as the cluster default, sorted |
provisioner | the class’s spec.provisioner |
phase | the claim’s or volume’s status.phase at scan time |
requested | storage the claim requests (spec.resources.requests.storage) |
capacity | the volume’s spec.capacity.storage |
reclaim_policy | the volume’s spec.persistentVolumeReclaimPolicy |
claim | the claim the volume was bound to, as namespace/name |
binding_mode | the class’s volumeBindingMode (Immediate when unset) |
gateway_class | GatewayClass the Gateway names |
controller | the GatewayClass’s spec.controllerName — which implementation owns it |
gateway | Gateway the route attaches to, as namespace/name |
listener | listener name within the Gateway |
port | listener port, or the backendRef port the Service does not expose |
protocol | listener protocol |
service_ports | ports the backend Service does expose, sorted |
api_group | crd.unavailable: the API group-version this command needed |
resources | crd.unavailable: the resources it would have read |
unavailable | summary-line note: why the group could not be read (absent CRDs, or discovery denied) |
gsa | the cloud identity (GSA email) the ServiceAccount’s annotation claims |
problem | machine-matchable problem code from the provider (e.g. no-workload-identity-binding) |
env | the credential-file env var found (GOOGLE_APPLICATION_CREDENTIALS) |
capability | cloud.unavailable: the provider capability this command needed (workload-identity) |
provider | cloud.unavailable: the provider that was asked |
manager | on findings: the foreign manager string from managedFields (a tool name like kubectl-edit — never a user identity; see —identity); on the summary line: the resolved GitOps manager |
detection | summary note: how the GitOps manager was resolved — declared (—manager), majority (auto-detected recognized GitOps controller owning >50% of the spec leaf fields in scope), or none (no manager resolved; nothing emitted) |
detection_reason | summary note on detection=none, naming why: no-spec-fields-in-scope (nothing in scope owns a spec field), no-majority-manager (a leading candidate exists but owns 50% or less), or not-a-gitops-manager (the majority owner is not a recognized GitOps controller — e.g. kubeadm or a kubectl manager on a cluster with no GitOps at all) |
candidate | summary note on detection=none: the leading manager that fell short (of the majority, or of being a recognized GitOps controller) — pass it to —manager if it is in fact the GitOps controller |
share | summary note: the resolved manager’s (or, on detection=none, the candidate’s) percentage of every spec leaf field owned across the scanned objects, rounded. A declared manager with a low share means most findings are other legitimate owners |
unmanaged | summary note, omitted at zero: scanned objects the resolved GitOps manager owns no spec field on. Nothing is reported for them — an object the manager never applied cannot have drifted from it — so a high count next to zero findings means the manager’s scope is narrower than the scan’s |
operation | managedFields operation of the foreign manager’s last write: Apply or Update |
tool | client tool recognized from the manager string (kubectl for kubectl-edit/kubectl-patch/kubectl-*) |
fields | compact spec paths the foreign manager owns (e.g. spec.template.spec.containers[app].image), capped at 8 with a +N more tail |
field_count | total spec leaf fields the foreign manager owns on this object (uncapped) |
principal | —identity: the audited principal of the write nearest the drift time (GKE: principalEmail), or the explicit sentinel none-in-audit-window / no-write-time-anchor when the trail cannot answer |
principal_agent | —identity: the caller-supplied client string of that write (a kubectl or controller user-agent), when the trail records one; caller-controlled text, display-only |
other_principals | —identity: other distinct principals that wrote the object inside the audit window, capped at 8 with a +N more tail |
identity | summary note when —identity could not be served: the unavailable marker naming why (no provider / audit capability absent) |
cluster | on a node-pool finding: the cluster the pool belongs to, so the record stands alone |
workload_pool | the cluster-wide workload identity pool that this node pool’s pods bypass |
metadata_mode | how the node pool exposes instance metadata to pods: node-identity means any pod can mint tokens for the node’s service account |
disable_legacy_endpoints | the pool’s legacy-metadata setting as the provider records it: enabled when someone turned the pre-v1 endpoints back on, unset when the pool was never configured either way |
node_pools | summary note: node pools examined — the cluster itself is the other unit scanned counts |
endpoint | the control plane’s internet-facing address |
authorized_networks | how many source ranges the allow-list permits |
authorized_network_cidrs | those ranges, sorted as the provider returned them and capped at 8 with a +N more tail |
gcp_public_cidrs | whether the provider’s own public ranges are admitted in addition to the allow-list |
exempt_kind | the finding kind the entry covers — this is the entry’s kind: field, not this finding’s own kind |
expires | when the entry stops applying, RFC 3339 (a bare YYYY-MM-DD in the file resolves to 00:00:00Z that day) |
expired_for | how long ago the entry lapsed, rounded to whole days — only on audit.exemption_expired |
expires_in | how long until the entry lapses, rounded to whole days — only on audit.exemption_expiring |
owner | the entry’s owner: field, absent if it has none — which is itself worth fixing, since “expired, and nobody knows whose it was” is where these files end up |
justification | the entry’s reason: field: why the exempted finding was accepted. Distinct from the envelope’s exempt_reason, which is the justification for THIS finding being exempt |
containers | containers implicated by the finding — those running privileged, or holding a node-root capability |
container_names | their names, capped at 8 with a +N more tail |
total_containers | containers in the pod template, init containers included, so containers reads as a fraction |
capabilities | the node-root capabilities added by those containers (ALL, SYS_ADMIN), sorted and deduplicated |
host_paths | hostPath volumes the template mounts; a declared but unmounted hostPath volume grants no access and is not counted |
host_path_names | the paths on the node, sorted and capped at 8 |
service_account | the ServiceAccount the finding is about — always default, the one every pod gets when its template names none |
mounting_workloads | workloads in the namespace running as the default ServiceAccount without disabling automount at the pod level; the finding does not fire at 0 |
mounting_workload_names | their Kind/name, sorted and capped at 8 |
pss_enforce | the namespace’s pod-security.kubernetes.io/enforce label, omitted when unset |
pss_warn | its /warn label, omitted when unset — set without /enforce means the namespace is in dry-run |
pss_audit | its /audit label, omitted when unset — same dry-run meaning |
workloads | pod templates this pass judged in the namespace, so an unenforced namespace with nothing in it reads differently from a busy one |
namespaces | summary note: namespaces examined — the denominator for every namespace-subject claim, which scanned (pod templates) does not cover |
policies | NetworkPolicies in the namespace naming this direction in policyTypes; 0 on a namespace-subject finding, and the number that failed to select the subject on a workload one |
total_policies | NetworkPolicies in the namespace in either direction, so an egress-only namespace does not read as an empty one |
host_network_workloads | pod templates excluded because they use the node’s network namespace, where NetworkPolicy does not apply; omitted at 0 |
covered_workloads | pod templates in the namespace that ARE selected for this direction — the neighbours the subject fell out of step with |
pod_labels | the template’s own labels, which are what the policies’ selectors failed to match, sorted and capped at 8 |
version | the current version of the finding’s subject — the control plane’s, or the node pool’s |
target_version | the version the provider would move this cluster to: its channel’s upgrade target where one is published, otherwise the channel’s default |
control_plane_version | on a node-pool skew finding: the control-plane version the pool is measured against |
minor_versions_behind | how many minor releases separate the two versions |
channel | the release channel the cluster is subscribed to, and the one whose published versions the comparison used; none when it is subscribed to no channel |
image_type | the provider’s name for the node image the pool runs |
exclusion | the operator’s name for the maintenance exclusion currently in force |
scope | how much of the upgrade stream that exclusion holds back: all-upgrades, minor-upgrades or minor-and-node-upgrades |
ends | when the exclusion lifts, or end-of-support for one that runs until the cluster’s version leaves support |
days_remaining | how much longer the exclusion has left to run |
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 |
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 |
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 |
pdbs | summary note: PodDisruptionBudgets seen in scope |
hpas | summary note: HorizontalPodAutoscalers seen in scope |
cidr | the range’s CIDR block |
purpose | what the range allocates: pods, services, or nodes |
zone | orphan.disk: the disk’s zone |
size_gb | orphan.disk: provisioned size in GB (billed whether used or not) |
disk_type | orphan.disk: disk type short name (pd-ssd bills ~4x pd-standard idle) |
unused_since | orphan.disk: last detach (or creation, if never attached), RFC3339; omitted when the provider cannot date it |
unused_for | orphan.disk: how long the disk has been unattached; “unknown” when undatable |
region | orphan.lb: the forwarding rule’s region (“global” for global rules) |
why | orphan.lb: the provider’s orphan judgment (e.g. which backend resolved empty) |
usage | current usage in the quota’s own unit |
limit | the quota limit |
unit | the quota’s unit, when the provider names one |
machine_type | the exhausted machine type (omitted when the log record does not name one) |
events | stockout events for this zone/machine-type pair in the window |
first_seen | earliest event in the window (RFC3339) |
last_seen | latest event in the window (RFC3339) |
reroute | same-region zones active in the window with no stockout for this machine type, comma-separated; omitted when the window offers no clean candidate |
window | summary-line note: the lookback the events cover |
pack | the pack this finding belongs to; also the summary-line note naming the pack that ran |
verb | apiserver request verb for this series (apiserver pack) |
priority_level | APF priority level for this series (apf pack) |
code | the HTTP status code the query matched (apf pack: 429) |
observed | the worst (maximum) aligned value in the window, in the query’s unit — the breach basis |
latest | the newest aligned value in the window |
threshold | the crossed threshold: the critical one when severity=critical, else the warning one |
trend | startup pack: second-half vs first-half mean delta of the window, e.g. “+34%” — the p95 trend direction |
workload | the target the edges were traced from as <Kind>/<namespace>/<name>, stamped on every finding — a workload, or the Service itself when entered from the service side |
likely_workload | on a Service-entry edge.selector_empty: the workload in that namespace whose pod labels best fit the broken selector, i.e. the one it was probably meant to select. Absent when two workloads fit equally well, because then naming one would be a guess |
volume | pod volume, or StatefulSet volumeClaimTemplate, whose reference is broken |
key | the referenced key that is missing from the ConfigMap/Secret |
selector | the Service label selector under scrutiny |
selected | pods the Service selector currently selects |
endpoints | total endpoints across the Service’s EndpointSlices |
slices | how many EndpointSlices back the Service |
pod | pod named by an orphaned endpoint targetRef |
via | how the broken reference is reached from the workload: mount, ingress, or imagePullSecret |
ingress | Ingress referencing the TLS secret, or the unserved Ingress itself |
host | Ingress rule host of the broken backend (empty for the default backend) |
path | Ingress rule path of the broken backend |
role_ref | dangling roleRef as <Kind>/<name> |
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 scanlookout scan --namespace=prodlookout scan --include=audit --format=jsonlookout scan --max-drilldown=0