lookout watch
lookout watch is the watch-path half of the binary: a resident
per-cluster sentinel that turns leading indicators into per-incident agent
sessions on a core-agent daemon.
lookout watch [flags]Signal sources are individually enabled via --sources; the default,
auto, probes each portable source’s needs at startup and enables what the
deployment supports. Flags of a disabled source are
still validated — a nonsensical value is a config error in every mode.
The table is generated from the sentinel’s real flag declarations
(internal/watch.FlagInventory), sorted by name. The core flag
surface is
pinned by TestFlagSurfaceFrozen: removing or renaming one of those is a
breaking change to running deployments, never a refactor.
| Flag | Type | Default | Meaning |
|---|---|---|---|
--access-recheck | duration | 2m0s | How often to re-run the SelfSubjectAccessReview probe over every enabled source’s declared access, so a grant revoked AFTER startup surfaces as a kind=sentinel.access_revoked signal instead of a silently empty watch. A denial must repeat across two consecutive sweeps before it counts, so IAM propagation does not read as a revocation. Losing a REQUIRED permission stops this cluster’s runner (the same terminal path a startup refusal takes); an optional one degrades loudly and keeps running. 0 disables the re-check. |
--backoff-min-count | int | 3 | Require the crash-loop family (canonical CrashLoopBackOff — kubelet’s repeating BackOff cycle) to reach this Event.Count before firing, so a transient startup blip that self-heals does not open a noise session. Image-pull backoff is gated separately by —imagepull-transient-min-count. 1 fires on the first event. |
--burn-eta | duration | 30m0s | Budget-exhaustion projection inside this window fires token.burn at critical (with the linear forecast); clearance requires the ETA to recede beyond 2x this threshold. Must be > 0. |
--burn-multiple | float | 4 | Session token rate at or above this multiple of the cross-session trailing-median baseline (sustained 2 polls) fires token.burn at warning. Must be > 1. |
--capacity-poll | duration | 1m0s | Poll interval for the capacity source’s cluster-autoscaler-status ConfigMap read, provider scale-decision query, and pending-pod age sweep. Must be > 0. |
--cluster-name | string | — | Human-readable cluster name included in every inject payload. |
--clusters | string | — | Multi-cluster: comma-separated name=endpoint pairs to watch from one process, e.g. prod-us=abc.us-central1.gke.goog,prod-eu=def.europe-west1.gke.goog. A bare endpoint derives a short name from its first DNS label. Mutually exclusive with —clusters-from; needs a Fleet-capable provider (-tags gke). Leave empty for the one-sentinel-per-cluster default. |
--clusters-from | string | — | Multi-cluster: discover the clusters to watch instead of listing them. A project, or project/location, queried via the cloud provider’s cluster API (GKE: Container API ListClusters over the project; needs a Fleet-capable provider, -tags gke). Or the reserved value kubeconfig (optionally kubeconfig:<path>) to watch one cluster per context in a kubeconfig instead — no cloud API and no build tag, so EKS, on-prem and kind fleets work; with no path it follows client-go’s search ($KUBECONFIG, colon-separated and merged, else ~/.kube/config), which is also how you select a subset. Mutually exclusive with —clusters. |
--compute-class-dwell | duration | 10m0s | How long a compute class’s rank verdict must persist before the source raises a finding. The resolve dwell (30m) and the flap guard are not separately tunable. Must be > 0. |
--compute-class-infer | bool | true | Cross-check GKE’s ccc_priority_index node annotation by independently matching each node against its compute class’s priority rules, and count every disagreement (lookout_leeway_preference_disagreement). The annotation ALWAYS wins either way — this is a check on k8s-lookout’s model of the rules, not an override of GKE’s answer — so the only thing turning it off buys is silence on a cluster where the matcher is known to be behind the rules people write. Turning it off also blinds the unmatched and ambiguous counters, which are how that gap is meant to become visible. |
--compute-class-last-rank-ceiling | float | 0.9 | Raise leeway.rank_degraded when more than this fraction of a class’s windowed pod-time ran at its LEAST-preferred priority. The default of 0.9 admits only two readings — capacity at every better priority is chronically unavailable, or the priority list is in the wrong order — and both are findings. Pass 0 to turn the rule off, which is the right answer for a class whose last rung is the cheap capacity it was always meant to run on. |
--compute-class-rank0-floor | float | — | Raise leeway.rank_degraded when less than this fraction of a class’s windowed pod-time ran at its most-preferred priority. OFF by default (0), because on a class with three or more priorities the same reading also describes an estate that is merely mixed, and only somebody who knows what their class was for can say which. Set it when the first priority is the one that matters — a reservation, or the only family your licence covers. |
--compute-class-tier-c-signals | bool | — | Put Tier C compute-class findings on the wire. There is one: leeway.rank_tier_unused, a whole priority nothing has occupied for thirty days — a dead rung on the ladder, or reserved capacity being paid for and never drawn on. It is exported as a metric by default and not as a signal, because an unused priority is frequently the intended configuration and the finding is a bill to look at rather than a page. The rank_wedged, rank_degraded and rank_no_migration kinds always signal. |
--compute-class-window | duration | 1h0m0s | How much recent history a compute class’s rank shares are a share of. The counters are cumulative, so a share has to be taken over a bounded window or a bad week in March goes on reporting in June. Longer smooths a class whose capacity comes and goes; shorter notices a fallback sooner and reads a rolling estate replacement as one. Composes with —compute-class-dwell rather than duplicating it: the window decides whether the condition is true now, the dwell decides whether it has been true long enough to say. Must be > 0. |
--daemon-url | string | — | Base URL of the core-agent daemon (http://… or https://…). Required. |
--dedup-persist | string | — | Optional path to persist dedup cache across sidecar restart. In multi-cluster mode this is a stem: each runner gets its own file, suffixed with the cluster’s project/location/name, so snapshots never clobber each other. |
--dedup-window | duration | 5m0s | Rolling window for (uid,reason) dedup. |
--degradation-drop | float | 0.3 | Minimum ready-ratio decline from window start (with >= 2 distinct downward steps) that fires degradation.capacity. Must be in (0, 1]. |
--degradation-window | duration | 15m0s | Trend window for the degradation source’s ready-ratio series and probe-flap counting. Must be > 0. |
--distill-interval | duration | 6h0m0s | How often the distiller pass converts recurring occurrences into durable memory facts (requires —store; the pass reads the last 7d of occurrences). 0 disables distillation. Must be >= 0. |
--dry-run | bool | — | Watch the cluster for real (informers, sources, filter/dedup/routing all run) but print inject payloads to stdout instead of calling the daemon/sink. Needs cluster access like a normal run. |
--enrich | string | critical | Which severities get enrichment on their per-incident session’s initial inject: critical (default), warning (critical+warning), or off. |
--enrich-cap | int | 4096 | Byte budget for the attached enrichment bundle (fixed budget). Kept under —inject-max-bytes so the bundle plus the rest of the payload clears the daemon’s per-inject ceiling with headroom for the double-JSON envelope. Truncation happens at section boundaries; dropped sections become overflow trailers naming the lookout command that reproduces them. |
--enrich-lists | string | all | Which cluster resources the scoped-list enrichment fallback reads: ‘all’ (default), a comma-separated allowlist (pods,deployments), or subtractions (all,-secrets) to keep the watcher SA least-privilege. Denied or deselected lists degrade to a partial bundle with a skipped= note on the head, never a resolve failure. |
--enrich-lists-preflight | bool | — | Before the scoped-list pass, SelfSubjectAccessReview each selected resource and drop the denied ones proactively (fewer 403s in the watcher log); falls back to reactive Forbidden-skip if SSAR is not permitted. |
--enrich-log-lines | int | 200 | Log tail per container stream distilled into the enrichment bundle’s logs section. Must be >= 1. |
--enrich-timeout | duration | 5s | Hard wall-clock budget for one enrichment run; on expiry the inject fires with whatever sections completed plus enrichment_error trailers. Must be > 0. |
--exclude-namespace | string | — | Comma-separated deny-list of namespaces. Scopes the watch: the informers list and watch with a metadata.namespace!= field selector, so these namespaces never enter the cache. Cluster-scoped objects (nodes) are unaffected. |
--expiry-interval | duration | 1h0m0s | Interval between expiry scans (periodic paged LISTs — deliberately no Secret informer). Must be > 0. |
--expiry-namespaces | string | — | Comma-separated namespaces the expiry scan LISTs secrets/serviceaccounts/Certificates in. Empty = all namespaces. Scopes the sensitive secrets-list grant — the startup RBAC probe verifies exactly this scope. |
--expiry-warn | duration | 336h0m0s | Warning threshold for expiry.warning: certificates with notAfter inside this window fire at warning severity (critical at the design-fixed 72h). Must be >= 72h. |
--gateway-grace | duration | 5m0s | How long a Gateway/HTTPRoute status condition (Programmed/Accepted/ResolvedRefs=False, reason != Pending) must be sustained — timed from its lastTransitionTime — before gateway.programming_failed / gateway.route_rejected fires. Absorbs normal LB provisioning latency. Must be > 0. |
--graph-snapshot-interval | duration | 5m0s | How often to persist a compressed topology snapshot to —store (the per-delta change log is written continuously). Effective only with —store AND storm correlation on (the graph feed). Must be > 0. |
--imagepull-transient-min-count | int | 3 | Require an image-pull failure whose cause is RETRYABLE (registry 429/quota, 5xx, timeout, connection reset) to reach this Event.Count before firing, so a rate limit kubelet clears on its own does not open a noise session. Terminal causes (bad tag, denied, no space) and unrecognized ones still fire on the first event. 1 fires on the first event. |
--in-cluster | bool | — | Use in-cluster service account credentials. Auto-detected inside a pod. |
--inject-max-bytes | int | 8192 | Per-inject wire-body ceiling the dispatcher fits payloads to before POSTing (default matches the core-agent daemon’s 8192-byte limit). An over-limit payload is shrunk least-signal-first — enrichment dropped, then message truncated — never identity, so the incident still routes; without this the daemon 400s the whole inject and a new incident lands as an empty session (issue #198). |
--kubeconfig | string | — | Explicit kubeconfig path. Used outside a pod. |
--log-level | string | info | One of: debug, info, warn, error. |
--metrics-addr | string | — | Prometheus /metrics + /healthz + /readyz listener address (host:port). Empty = disabled. |
--mode | string | per-incident | Session routing mode: per-incident (create per (uid,reason)) or shared (all to —target-session). |
--namespace | string | — | Comma-separated allow-list of namespaces. Empty = all namespaces. A post-watch output filter, NOT a watch scope or a security boundary: every namespace is still listed, watched and cached. Use —exclude-namespace to shrink what is watched. |
--notifications-subscription | string | — | Subscription the notifications source reads (GKE: a Pub/Sub subscription on the cluster’s notificationConfig topic) — either projects/<p>/subscriptions/<name> or a bare name resolved against the provider project. Required when the notifications source is enabled. |
--otel-exporter | string | none | OpenTelemetry span exporter: none | console | otlp. The OTEL_TRACES_EXPORTER env var overrides this. |
--owner | string | — | X-Asserted-Caller value for POST /sessions in per-incident mode. Sidecar must be in daemon’s proxy_identities. |
--pending-age | duration | 5m0s | How long a pod must be Pending+Unschedulable before capacity.pending-aged fires at warning (critical at the design-fixed 15m, or at this value when set higher). Must be > 0. |
--project | string | — | Cloud project/account the cluster runs in, stamped into payloads. Empty = detect from the cloud provider’s metadata when a provider is compiled in; vanilla clusters can set it explicitly. |
--quota-poll | duration | 15m0s | Poll interval for the quota source’s inventory read and per-watched-quota history query. Must be > 0. |
--quota-warn | float | 0.8 | Usage/limit ratio above which a quota is always watched (history fetched every poll) in addition to the top-10 nearest exhaustion. Must be in (0, 1). |
--quota-window | duration | 168h0m0s | History window the quota usage slope is fitted over (the linear-<window> confidence basis); a forecast needs usage points spanning at least half of it. Must be > 0. |
--reason | string | — | Comma-separated allow-list of Event.Reason values. Empty = shipped default set. |
--recovery-stable-for | duration | 5m0s | How long a cleared symptom must stay clear before kind=resolved is injected into the incident’s session; recurrence within this window after a resolve fires kind=resolved.reverted. 0 disables recovery tracking. |
--region | string | — | Region the cluster runs in, stamped into payloads. Set for zonal and regional clusters alike. Empty = detect from the cloud provider’s metadata when a provider is compiled in; vanilla clusters can set it explicitly. Setting either —region or —zone stops metadata detection for BOTH — a half-flagged location would mix a flag’s region with a metadata zone somewhere else. |
--rollout-observe | duration | 3m0s | How long a new revision must make zero ready-count progress (while the old revision stays healthy) before rollout.stall fires. Fired well before progressDeadlineSeconds. |
--saturation-interval | duration | 30s | Sampling interval for the saturation source (metrics.k8s.io + kubelet volume stats). |
--saturation-warn | duration | 1h0m0s | Forecast ETA below which saturation.forecast fires at severity warning (critical fires below 15m); clearance requires the ETA to recede beyond 2x this threshold. |
--saturation-window | duration | 1h30m0s | Regression window for saturation forecasts; a forecast needs samples spanning at least half of it (the linear-<window>-window confidence basis). |
--severity | repeatable | — | Per-kind severity override(s): kind=level[,kind=level…] with level one of critical|warning|info. Repeatable and additive; overrides the source-stamped default for that kind. Each kind may appear at most once. |
--sink | string | core-agent | Agent sink receiving incident payloads: core-agent (default: POST /sessions + /sessions/<sid>/inject against —daemon-url) or webhook (generic receiver: POST <sink-url>/incidents opens an incident with the schema-v1 payload JSON as the body; POST <sink-url>/incidents/<id>/events appends follow-ups). |
--sink-token-env | string | — | Env var name holding the bearer token the webhook sink sends as Authorization: Bearer. Optional (unset = unauthenticated POSTs); only valid with —sink=webhook. |
--sink-url | string | — | Base URL of the generic webhook receiver (no trailing slash). Required with —sink=webhook. https is STRONGLY recommended: plain http is allowed (remote receivers are the point) but warns loudly at startup — incident payloads and the bearer token ride unencrypted. |
--snapshot-interval | duration | 30s | How often to persist the dedup cache when —dedup-persist is set. 0 = only on shutdown. |
--sources | string | auto | Comma-separated signal sources to enable, or auto (the default): probe the portable sources’ needs at startup — RBAC via SelfSubjectAccessReview, plus metrics.k8s.io presence for saturation — and enable what this deployment supports, skipping misses with one loud line each (k8s-events must pass; a sentinel that cannot watch events is misdeployed). Known sources: k8s-events, object-state, rollout, workload, autoscaling, saturation, degradation, expiry, capacity, ingress, gateway, topology-drift, compute-class, quota, notifications, token-burn. quota (project tier), notifications (needs —notifications-subscription), and token-burn (core-agent cost stack) are never auto-enabled. An explicit list keeps semantics: a named source’s missing REQUIRED grant is fatal (optional dimensions — saturation’s nodes/proxy PVC read — still degrade loudly instead, issue #145). |
--store | string | — | Path to the sentinel-local SQLite occurrence store, e.g. /var/lib/lookout/lookout.db — put it on the —dedup-persist volume. Every emitted signal is recorded with its routing outcome; info-severity signals are persisted instead of dropped. In multi-cluster mode this is a stem: each runner opens its own file, suffixed with the cluster name, and —store-max-mb bounds EACH one. Reach a fleet file from the CLI with the same stem plus —store-cluster. Empty (default) disables the store. |
--store-max-mb | int | 512 | Size bound for the occurrence store in MiB; when exceeded, the oldest occurrences are pruned first (loudly). Bounds EACH store, so a multi-cluster fleet may use this much per cluster — a deliberate choice over a fleet budget divided N ways, which would make one cluster’s retention depend on how many clusters discovery found. Must be >= 1. |
--store-ttl | duration | 720h0m0s | Retention for stored occurrences (default 30 days); the prune loop deletes older rows. Must be > 0. |
--storm | string | auto | Storm correlation: auto (the default — probe the graph informers’ grants at startup: pods/nodes/replicasets list+watch; all present resolves on, a miss resolves off with one loud line naming the grant), on (fatal at startup when a grant is missing), or off. true/false are aliases for on/off; bare —storm is no longer valid syntax. When on, new incidents sharing a blast-radius key (nearest common topology ancestor) group into one kind=storm session. |
--storm-cluster-fallback | bool | true | Group simultaneous NODE failures that nothing else groups (issue #334) under a synthetic Cluster ancestor, so a fleet-wide outage is one page instead of one per node. Applies only to nodes carrying no topology.kubernetes.io/zone label (a zone key is the modelled answer and always wins) and is deliberately expensive to trigger: a fifth of the fleet, at least 3 nodes, all inside 20s, and the storm expires after 5 idle minutes. Set false to keep one session per node. Requires storm correlation on. |
--storm-min | int | 3 | Minimum incidents sharing a blast-radius key within —storm-window to form a storm. Must be >= 2. |
--storm-mine | bool | — | Also correlate on DISCOVERED keys (issue #225): when —storm-mine-min incidents in the window share an exact image reference, node or container, group them into one storm even though no topology ancestor or external dependency connects them. Off by default — a mined key is circumstantial, so it needs more members than a modelled one, and every mined storm names what it grouped on. Requires storm correlation on. |
--storm-mine-min | int | — | Minimum incidents sharing a mined attribute value to form a storm. 0 (the default) means auto: the larger of 5 and —storm-min. An explicit value must be >= —storm-min — a discovered key must never be cheaper to form than a modelled one. Effective only with —storm-mine. |
--storm-window | duration | 1m0s | Second-level correlation window for storm formation. 0 disables correlation even with —storm=on. |
--target-session | string | — | Required when —mode=shared: SessionID to post all injects to. |
--token-budget-usd | float | — | Per-session spend budget in USD for the token-burn source’s critical trigger; 0 (default) = unknown, budget trigger disarmed. Lookout-side config because core-agent v2.7.0 does not expose its CostCeiling over the attach API (TODO(core-agent) in pkg/sources/tokenburn). Must be >= 0. |
--token-endpoint | string | — | Override base URL for the core-agent cost stack (default: —daemon-url — the boundary rides the same daemon the injector talks to). No trailing slash. |
--token-env | string | — | Env var name holding the bearer token for the daemon. Required. |
--token-poll | duration | 1m0s | Poll interval for the token-burn source’s cost-stack reads (core-agent GET /sessions + per-session /usage). Must be > 0. |
--topology-baseline-band | float | 4 | How many learned deviations wide a baseline’s tolerance band is — the false-positive knob for Tier C. A subject breaches when a domain’s share leaves the band around what it learned. Raise it if learned baselines are noisy on your cluster; the default is deliberately wide, because Tier C is the tier where nobody asked to be watched. Must be > 0. |
--topology-baseline-half-life | duration | 12h0m0s | How long a learned baseline takes to half-absorb a step change in placement. Shorter follows a cluster that is legitimately rebalancing and stops calling it drift; longer keeps a longer memory of normal and so keeps noticing a slow slide that a short half-life would quietly adopt as the new normal. Must be > 0. |
--topology-capacity-ratio | float | 1.25 | How unequal a subject’s eligible zones have to be, as a max/min ratio of allocatable CPU, before an even split stops being the expectation and capacity does. Three zones where one is a quarter the size of the others cannot hold a third of anything; scoring them evenly reports drift on a cluster behaving exactly as its shape requires. Applies only where nobody declared a weighting — a policy that names one is always honoured. Raise it to keep the even expectation on mildly uneven clusters; below 1 it fires on any inequality at all. Must be > 0. |
--topology-cluster-defaults | string | — | Your cluster’s kube-scheduler PodTopologySpread defaultConstraints, as key=maxSkew[:DoNotSchedule|ScheduleAnyway] comma-separated — they are not readable from a managed control plane, so leeway cannot find them out. THREE STATES: leave this unset and the upstream system defaults are ASSUMED (every intent from them is labelled source=cluster-default-assumed and can never raise a critical finding); pass “none” to assert your cluster configures none; or name them to be scored against your real numbers. These only ever apply to pods that declare no topologySpreadConstraints of their own. |
--topology-domain-unavailable-keys | string | topology.kubernetes.io/zone,topology.kubernetes.io/region | Comma-separated topology axes on which a domain with no usable node raises leeway.domain_unavailable. The subject is the domain, so a zone that goes away is one finding for the cluster instead of one per workload that drifted because of it, and the workloads stay suppressed. Defaults to zone and region. Only name axes whose domains hold many nodes: an axis that is unique per node — kubernetes.io/hostname is one, and is a topology key — would raise a finding per NotReady node, which is objectstate’s job and not this one. Pass an empty string to turn the detector off. |
--topology-dwell | duration | 10m0s | How long a placement breach must persist before the topology-drift source raises a finding. Placement is rebuilt constantly — by rollouts, by the descheduler, by a drain — so the dwell is what separates drift from motion. Shorter pages you during a routine rollout; the resolve dwell (30m) and the flap guard are not separately tunable. |
--topology-keys | string | topology.kubernetes.io/zone,topology.kubernetes.io/region | Comma-separated node labels the topology-drift source treats as topology axes, in precedence order. The defaults are the two standard well-known labels; a cluster that partitions on something else (a rack or cell label) names it here. |
--topology-learn-baselines | bool | true | Learn each workload’s normal placement, so that a workload which declared no spread constraint is scored against what it actually does instead of against an even split. Learning is passive and cheap: it samples every subject once a minute, matures after 6h, and only ever applies where nothing else expressed an intent — it cannot override a declared constraint. Turn it off to score every undeclared workload against an even apportionment, which is what happened before this existed. |
--topology-max-node-groups | int | 200 | How many node groups may be tracked before leeway tracks none of them. Past the bound nothing is scored and nothing is exported except the discovered count, which is the reading that sends you to —topology-node-group-keys; truncating instead would score an arbitrary subset that changes every pass. Pass a negative value to turn node-group subjects off entirely. |
--topology-node-group-keys | string | cloud.google.com/compute-class,karpenter.sh/nodepool,eks.amazonaws.com/nodegroup,cloud.google.com/gke-nodepool,kops.k8s.io/instancegroup,agentpool | Comma-separated node labels a node group’s name is read from, in precedence order, first match wins (FR-3). Node groups are tracked as subjects of their own, so a pool that was configured for three zones and has all its nodes in one is one finding naming the pool rather than one per workload riding it. Compute class comes before node pool in the default because auto-provisioned pools are named per machine type and are numerous and short-lived. A label that is unique per node turns every node into a group — see —topology-max-node-groups, which is what stops that reaching your metrics. |
--topology-per-domain-collapse-states | bool | true | Halve the per-domain series count by folding the four scheduling states onto two labels: state=“active” for an object holding the domain’s capacity (running or terminating) and state=“waiting” for one that is not (pending or unschedulable). The distinction needs between the four is upstream of the metric and is unaffected — this changes the label, not the count or any finding. Turn it off to get running/pending/unschedulable/terminating back, at twice the series. |
--topology-per-domain-exclude-namespaces | string | — | Comma-separated namespaces whose subjects never export a per-domain breakdown, applied AHEAD of —topology-per-domain-namespaces — naming a namespace in both excludes it. This is the knob for the one churning namespace that dominates the series count; it does not stop the namespace being watched, scored or alerted on, only its per-domain breakdown being exported. |
--topology-per-domain-max-keys | int | 4 | How many topology axes one subject may contribute a per-domain breakdown on. A cluster that names five or six axes in —topology-keys multiplies every admitted subject’s series by that many, and the axes past the first two or three are almost never the one being read. Which survive is the —topology-keys precedence order, so the cap is stable across scrapes rather than following whichever axis drifted. What it dropped is counted by lookout_leeway_domain_series_withheld{reason=“key_cap”}. Pass a negative value for no cap. |
--topology-per-domain-min-drift | float | 0.05 | Drift (ρ, the fraction of a subject’s objects that would have to move) at which a subject’s per-domain breakdown is exported anyway. The default keeps the breakdown for the subjects somebody is about to investigate and withholds it for the rest, which is what makes the standing cost the aggregate one. Pass a negative value for every scored subject; —topology-per-domain-series overrides this entirely. |
--topology-per-domain-namespaces | string | — | Comma-separated namespaces whose subjects may export a per-domain breakdown. Empty — the default — admits every namespace, so this is the narrowing knob for a cluster that wants the breakdown standing for the namespaces it cares about and the aggregate everywhere else. Cluster-scoped subjects (node groups, and the domains themselves) are never filtered by this: they belong to no namespace, and dropping them would silently remove the cluster-wide reading. What it dropped is counted by lookout_leeway_domain_series_withheld{reason=“namespace”}. |
--topology-per-domain-series | bool | — | Export lookout_leeway_domain_objects and lookout_leeway_domain_expected for EVERY tracked subject, not only the drifting ones. OFF by default because the count is multiplicative: roughly 480k series on a 20k-subject cluster, against ~3.5k for every other leeway metric combined. See —topology-per-domain-min-drift for what you get without it. Turn this on to debug one cluster’s placement, not as a standing posture. |
--topology-tier-c-signals | bool | — | Put Tier C topology-drift findings on the wire. Tier C is the tier where nobody declared anything: the workload expressed no spread constraint or anti-affinity, so it was scored against an even apportionment over the domains it can reach — or, once one is learned, against its own baseline — and a breach says “this changed” rather than “this is wrong”. Those findings are exported as metrics only by default. Tiers A and B — a declared contract, or an intent inferred from what the workload does say — always signal. |
--triage-regress-factor | int | 3 | A downgraded incident (severity_override) whose dedup-window count reaches this multiple of its count at downgrade time gets ONE kind=triage.regressed evidence followup into its bound session — never an automatic re-page (docs/triage-status-write-design.md). Must be >= 2; 0 disables. |
--unhealthy-min-count | int | 3 | Require this many consecutive Unhealthy events before firing. |
--watchboard-batch | int | 5 | Buffered warning-class signals that trigger a watchboard digest flush (per-incident mode). Must be >= 1. |
--watchboard-flush | duration | 1m0s | Maximum age of a buffered warning before the watchboard digest flushes regardless of batch size. Must be > 0. |
--watchboard-rotate | int | 200 | Digest injects per watchboard session before size-based rotation opens a fresh session. Must be >= 1. |
--zone | string | — | Zone the cluster runs in, stamped into payloads. Set it only for a ZONAL cluster: a regional cluster has no zone of its own (its nodes are spread across the region’s zones), and leaving it empty there is the correct answer, not a gap. The failure domain — this zone when set, else —region — is what enters the signal fingerprint hash, so a deployment that stamps neither produces domain-less fingerprints: stable, but cross-cluster joins within a failure domain need one stamped. |
See also: Signal kinds — everything the sentinel can inject; Prometheus metrics — the --metrics-addr surface.