Skip to content

lookout scan

lookout scan is the entry point for “something is wrong and I do not know what”: no target, no flags, nothing deployed. It runs every target-free incident check in one invocation, then drills into the dependency edges of whatever it flagged.

Every finding is stamped check=<command> — which is also the command to run for the detail behind it. That is why this page is grouped by stage rather than alphabetically: the heading is the follow-up call.

A kind absent from a run means the check looked and found nothing. A check that could not run says so explicitly, in the stream — see what scan says about itself at the bottom.

Stage 1 — the target-free incident checks

Section titled “Stage 1 — the target-free incident checks”

These run on every scan, in this order: the broadest check first, so the thing that is wrong is usually named before you finish reading.

Every abnormal object in one scan — the first call for “anything wrong in this cluster?”: broken/pending pods, stalled rollouts, workloads blocked from creating pods at all, node pressure/NPD/preemption, gridlocked PDBs, degraded kube-system add-ons, quotas at their limits.

KindSeverityWhat it means
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

When creates/updates hang or fail cluster-wide with “failed calling webhook”, or before relying on a policy engine: audit every admission webhook — dead backends × failurePolicy (Fail + dead backend rejects every matching admission), the namespace/rule blast radius, timeout stall risk, CA-bundle expiry. The full check; health’s webhooks category delegates here.

KindSeverityWhat it means
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

When pods hang in ContainerCreating with Multi-Attach or FailedAttachVolume events — join VolumeAttachment + PV/PVC + pods to name the exact conflict: RWO claims wanted on two nodes, attachments stuck in error, cross-zone PV locks, orphaned attachments.

KindSeverityWhat it means
volume.multi_attachcriticalan RWO claim is wanted by pods on more than one node — the second pod never starts
volume.zone_conflictcriticalthe PV is locked to a zone the pod’s node is not in
volume.attach_errorcritical, warningthe attach or detach is failing; critical once it has been failing long enough to be stuck rather than slow
volume.orphaned_attachmentinfoa VolumeAttachment survives its PV or its node

When a PersistentVolumeClaim sits Pending and the pod behind it will not schedule — name the reason: a StorageClass that does not exist, no class and no cluster default, a static-only class with nothing pre-provisioned, plus the default-class ambiguity and stranded volumes behind it.

KindSeverityWhat it means
storage.missing_classcriticalthe claim names a StorageClass that does not exist — it will stay Pending forever
storage.no_default_classcriticalthe claim names no class and the cluster has no default StorageClass
storage.no_provisionerwarningthe claim’s class is static-only (kubernetes.io/no-provisioner) and no matching PV is available
storage.multiple_defaultswarningmore than one StorageClass is annotated as the cluster default; which one wins is not defined
storage.pv_failedwarninga PersistentVolume is Failed: its reclaim did not complete, so the backing disk stays allocated and the volume cannot be reused
storage.pv_releasedinfoa PersistentVolume is Released — retained on purpose, but its capacity is unusable until spec.claimRef is cleared

When traffic through the Gateway API does not arrive — walk GatewayClass → Gateway → listener → HTTPRoute → Service and report every hop that is rejected, unprogrammed, or points at something that is not there. Silent, and cheap, on clusters without the Gateway API installed.

KindSeverityWhat it means
gateway.missing_classcriticalthe Gateway names a GatewayClass that does not exist — nothing will program it
gateway.class_not_acceptedcriticalthe Gateway’s GatewayClass is not Accepted by its controller
gateway.not_acceptedcriticalthe Gateway itself is not Accepted
gateway.not_programmedcriticalthe Gateway is Accepted but not Programmed: no data plane is carrying its traffic
gateway.listener_invalidwarningone listener of an otherwise working Gateway is not resolved or not programmed
route.missing_parentcriticalthe route’s parentRef names a Gateway that does not exist
route.not_acceptedcriticalthe Gateway refused the route’s attachment (listener, hostname, or namespace policy)
route.missing_backendcriticalthe route’s backendRef Service does not exist
route.backend_portcriticalthe route’s backendRef Service exists but does not expose the named port
crd.unavailableinfothe API group this check reads is not served by the cluster, so nothing was examined (no coverage lies)

When a GKE pod gets 403s or metadata-server errors calling GCP APIs, verify the Workload Identity chain — KSA annotation (iam.gke.io/gcp-service-account) → roles/iam.workloadIdentityUser binding on the GSA — reporting only the broken links; vanilla clusters report an explicit unavailable.

KindSeverityWhat it means
wi.gsa_missingcriticalthe annotated Google service account does not exist — every GCP call from these pods fails
wi.unboundcriticalthe KSA annotates a GSA but the roles/iam.workloadIdentityUser binding is missing or malformed
wi.unannotated_useinfoa pod sets GOOGLE_APPLICATION_CREDENTIALS but its ServiceAccount carries no Workload Identity annotation
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

Find spec fields of Deployments/StatefulSets/DaemonSets owned by a manager other than the GitOps controller (managedFields) — out-of-band kubectl edits and rogue co-managers. Reports manager strings (tool names, not people); —identity additionally resolves each drift write to the audited principal via the cloud provider’s audit trail (GKE Cloud Audit Logs), reporting an explicit unavailable on clusters without one. Default scope: all namespaces; scanned counts workload objects examined.

KindSeverityWhat it means
drift.manual_editcritical, warninga manager other than the GitOps controller owns spec fields on this object; critical when one of them is high blast radius (image, replicas, env)

Stage 2 — the dependency-edge drill-down

Section titled “Stage 2 — the dependency-edge drill-down”

Every workload stage 1 flagged at warning or above then has its dependency edges verified: one cluster List pass and N in-memory state edges evaluations, each rolled up to its outermost controller, so twenty crashlooping pods of one Deployment are one drill-down rather than twenty. --max-drilldown bounds it (default 20) and the summary reports what it dropped.

KindSeverityWhat it means
edge.missing_refcriticala referenced ConfigMap, Secret, ServiceAccount, TLS secret, IngressClass, StorageClass, or governing Service does not exist
edge.missing_keycriticalthe referenced key is absent from an existing ConfigMap/Secret
edge.invalid_refwarningthe referenced object exists but is the wrong type to serve the reference
edge.unclassedwarningthe Ingress names no class and no IngressClass declares itself the cluster default — no controller will claim it
edge.selector_emptycriticala Service selector selects zero pods, so the service routes nowhere
edge.selector_unreadycritical, warningthe Service selects pods but some are not Ready; critical when none are
edge.endpoints_missingcriticala selecting Service has no EndpointSlices at all
edge.endpoints_orphanedwarningan endpoint targetRef names a pod that no longer exists
edge.endpoints_unreadycritical, warningthe endpoint ready-count disagrees with the selected pods (stale or lagging slices); critical at zero ready
edge.backend_missingcriticalan Ingress backend service, or the port it names, does not exist
edge.cert_expiredcriticala TLS certificate’s NotAfter is in the past
edge.cert_expiringwarninga TLS certificate expires within —cert-warn
edge.cert_invalidwarningtls.crt is missing or unparseable, or the secret is not kubernetes.io/tls
edge.rbac_danglingwarninga (Cluster)RoleBinding for the workload’s ServiceAccount points at a missing (Cluster)Role

Three groups are left out of a bare scan, each for a reason that is a property of the whole group. --include=all takes every one; - subtracts (all,-cloud).

Best-practice posture: the absence of a safety net around a workload or cluster that is currently healthy — a different claim from the incident groups, which is why it is a different group

Its 24 kinds have their own page — what lookout audit checks.

GCP-side reads: stockouts, orphaned resources, IP space, quota

Pod/Service/node CIDR utilization per subnet, judged: warning at 80%, critical at 95% — IP space is incompressible, an exhausted range fails the next node or pod block outright. Consumption rate/ETA lives in the sentinel’s capacity source.

KindSeverityWhat it means
ipspace.rangecritical, warning, infoa 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
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

Billing-active cloud leftovers: unattached GCE disks older than —min-age and forwarding rules/LBs routing to zero endpoints — cost and hygiene sweep, not an incident read.

KindSeverityWhat it means
orphan.diskwarninga GCE disk has been unattached for at least —min-age and is still billing
orphan.lbwarninga forwarding rule or load balancer routes to zero endpoints and is still billing
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

Per-project cloud quota usage vs limit, ranked nearest-to-exhaustion: findings from —quota-warn (default 80%), critical at 95% — quota is incompressible (scale-ups fail at the limit) and increases need lead time. Trend/ETA lives in the quota source.

KindSeverityWhat it means
quota.pressurecritical, warning, infoa cloud quota is at or above —quota-warn percent of its limit; critical from 95%, info for an —all row below the line
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

GCE capacity stockouts (ZONE_RESOURCE_POOL_EXHAUSTED) per zone/machine-type over —since (default 24h), with event-derived reroute candidates — the cloud-side why behind pods stuck Pending on failed scale-ups.

KindSeverityWhat it means
stockout.zonewarningthe cloud had no capacity for a machine type in this zone during the window — the reason a scale-up failed and pods stayed Pending
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

Control-plane and startup performance via Cloud Monitoring query packs

Control-plane and startup performance via metrics query packs: —pack=apiserver (p99 latency by verb/resource), apf (queue saturation + 429 rejects), etcd (WAL fsync p99 + DB size), startup (pod-first-ready p95 trend); apf/etcd need GKE control-plane metrics enabled — absence degrades to an explicit pack_unavailable finding.

KindSeverityWhat it means
perf.apiserver_p99critical, warningapiserver request latency p99 crossed the pack threshold for a verb/resource — warning from 1s, critical from 4s
perf.apf_saturationcritical, warningan API Priority and Fairness level is holding a sustained queue — warning from 10 queued, critical from 100
perf.apf_rejectscritical, warningAPF is shedding load: the apiserver is returning 429s at a priority level
perf.etcd_fsynccritical, warningetcd WAL fsync p99 crossed the pack threshold — warning from 10ms, critical from 100ms
perf.etcd_db_sizecritical, warningthe etcd database is approaching its quota — warning from 4 GiB, critical from 5.5 GiB
perf.startup_p95critical, warningpod first-ready p95 crossed the pack threshold — warning from 60s, critical from 300s
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)
cloud.unavailableinfothe cloud capability this check needs is unavailable, so nothing was examined — an explicit degradation record, never silence

A scan that could not run something reports that, rather than reporting a smaller cluster. These kinds are the coverage claim, and they are why an empty scan means “nothing is wrong” and not “nothing ran”.

KindSeverityWhat it means
scan.check_skippedinfoa stage declined this invocation because a zero-argument scan cannot supply something it needs — the coverage claim is smaller than it looks
scan.check_failedwarninga 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.incompletewarningthe —timeout expired with stages still to run; not_run names them