k8s-cluster-fleet-manager #638) it writes both
customer databases directly — nscale-inventory-prod-db (the primary, which drives delivery
success/retry and the 5-minute SLA) and nscale-inventory-dev-db (a best-effort mirror that gets
the same rows at the same moment). The legacy netbox-matrix-agent, kusto-copy, and kusto-monitor
are retired; rollback for the whole cutover is reverting that one PR, which restores the legacy
pipeline and points the connector back at the shadow DB.
- For operators — tell if it is healthy and, if not, who to call. No deep knowledge needed.
- For engineers — every error class the connector can hit, each with a ready-to-paste sentinel prompt, the raw queries, the cause, and the fix.
Instances
The connector is multi-instance: one process per delivery scope, sharing identical code. There is no routing inside a single pod — a second scope means a second Deployment, with its own delivery-queue partition and JetStream durable cursor, so the two never share processing state and one instance’s incident cannot stall the other’s deliveries. Every command and query below is written for sin1/Portugal, the one instance live today; substitute the column values from this table to target a different instance. Each process delivers to exactly one primary Kusto database, but two instances may be pointed at the same one when a customer wants several sites consolidated there. Instances are separated by NetBox site, so their device and cable rows occupy disjoint key spaces even in a shared database (see Disabling or rolling back one instance). The one table two instances can both write isDataCenters_staging — each also emits a row per cable far-end site, which
may be the other instance’s — and there the two rows are identical in content, so the MV’s
arg_max dedup resolves them. A shared database does mean both sites’ inventory is visible to
anyone who can read it.
useLegacyResourceNames: true in the
chart) so the multi-instance rollout did not recreate its Deployment — renaming a live Deployment
makes Argo delete-and-recreate rather than roll it, which would have re-opened the cold-start
freshness gap the readiness gate exists to close. Every other instance, Ward included, gets its
component and resource names suffixed with its instance key (-wdc1) instead.enabled: false until its Kusto delivery target
(its own database, or Portugal’s) and a production Fleet Manager Site exist. Nothing in this
guide’s Ward column is actionable until Fleet Manager confirms the cutover; if you see a
matrix-wdc1-prod alert or a matrix-connector-wdc1 pod before that announcement, treat it as
unexpected and escalate.
Who to call
Work down this table — most incidents end at the first row.The dashboard
The Matrix Connector dashboard (prod-hub Grafana — runtsh apps login fleet-mgr-glo1-grafana if it asks you to authenticate) is
the fastest way to see the connector’s health. It has four rows; Freshness and SLA is the one
that matters.
The SLA is: inventory in Kusto within 5 minutes. The dashboard tracks the age of the oldest
inventory change not yet shipped — under 5 minutes (300 s) is green, at or over is red.
Alerts
APrometheusRule fires the following, all evaluated by (connector) so a firing alert already
names the instance in its connector label — no separate rule per instance to maintain, and
AlertManager can route or silence on that label like any other.
connector label they carry — a Ward-only page is handled exactly like a sin1 one, just
scoped to that customer’s data when you investigate.
For operators
Health check (30 seconds)
Look at the dashboard
(If needed) check the pod(s)
tsh kube login fleet-mgr-prod-glo1, then list every connector pod at once
(matches both instances’ component selectors — see Instances):Running and 1/1 — today that is one pod for sin1 only,
since Ward is not yet enabled (a matrix-connector-wdc1 pod appearing before that announcement is
unexpected). Two pods for the same instance for a few minutes during a deploy is the safe rolling
update and is normal.If it is not green
For engineers
Access
Dashboard (start here)
Dashboard (start here)
tsh apps login fleet-mgr-glo1-grafana) is the quickest overview — start there before ad-hoc queries. Note it is
a different Grafana from the central one used for the queries below.Ad-hoc metrics and logs (Grafana Explore, no repo needed)
Ad-hoc metrics and logs (Grafana Explore, no repo needed)
tsh apps login observability-grafana, then open
observability-grafana.nscale.teleport.sh →
Explore. For a promql query pick the metrics-all (Prometheus) data source; for a
logql query pick product-loki (Loki). Paste the query and set the time range top-right.Pods (kubectl)
Pods (kubectl)
tsh kube login fleet-mgr-prod-glo1 makes the prod hub your current context, so the kubectl -n fleet-manager … commands below work with no --context flag. (The full context name is
nscale.teleport.sh-fleet-mgr-prod-glo1.)CLI query shortcut (optional)
CLI query shortcut (optional)
source .agents/skills/sentinel/scripts/grafana.sh defines
promql '<query>' and loki_pretty '<query>' 1h 100 so you can run the queries from a terminal
instead of Grafana Explore.fleet-mgr-glo1 (no -prod-) · namespace
fleet-manager. Every example below targets sin1 — deployment
fleet-manager-hub-matrix-connector, connector slug matrix-prod — since it is the only
instance live today; swap in the wdc1 column from Instances once Ward is
enabled. service_name is fleet-manager-matrix-connector for every instance (OTel’s
OTEL_SERVICE_NAME is not per-instance), so Loki queries below already cover both — Prometheus
queries need the connector label from the table to tell them apart.
Troubleshooting with sentinel
Each error class below carries a sentinel prompt — a copy-paste task for the read-only sentinel skill (Claude Code), which queries Loki / Prometheus / Kubernetes and summarises the answer so you do not have to remember every query. Open Claude Code, invokesentinel, and paste
the prompt. No sentinel? Run the raw query in the same block.
Error classes
Each is the same shape: Symptom → Ask sentinel → Or check manually → Cause & fix → Escalate.1. Pod down or crashlooping
1. Pod down or crashlooping
CrashLoopBackOff / Error, or 0/1 beyond the ~10-minute warm-up.Ask sentinel:ShadowRetentionErrorin the traceback → the Kusto retention policy has drifted. The connector refuses to start on purpose. Do not restart-loop it — fix the KustoSoftDeletePeriod(must be 7 days) first.- A site-slug / config
ValueErrorat startup → a badsite_filteror slug map; needs a config fix. OOMKilledin events → the memory limit needs raising.- No error, still warming → warm-up is allowed ~10 minutes; wait before treating it as a crashloop.
ShadowRetentionError, a config error, or a pod that does not recover after one
rollout restart.2. Readiness stuck (0/1, not warming)
2. Readiness stuck (0/1, not warming)
Running but 0/1 for more than ~10 minutes.Ask sentinel:/readyz reason needs cluster access operators do not have,
so you route on 0/1 + the backlog + the logs. The reasons are bootstrap_pending /
queue_unsampled (still starting — wait), queue_backlog:<seconds>s (see the Delivery-queue
backlog class), or netbox_direct_full_pending (only if NetBox-direct is on — see the
NetBox-direct note).Escalate. A pod that never crosses to 1/1 and is not backlog-bound.3. NATS consumer not advancing
3. NATS consumer not advancing
events_total is flat.Ask sentinel:Failed to subscribe to subject means it could not bind its durable consumer
(often a subject/durable mismatch). Restart re-attaches it:events_total on its own is not a fault — it just means no inventory changed. Only
treat it as an incident alongside Failed to subscribe to subject or a stalled heartbeat.Failed to subscribe to subject that returns immediately after a restart — the
durable consumer is misprovisioned.4. Delivery-queue backlog not draining
4. Delivery-queue backlog not draining
/readyz reports queue_backlog:<n>s (pod 0/1), or the backlog gauge climbs and
stays above 120 seconds.Ask sentinel:Matrix worker pool cycle failed — retrying→ a transient DB blip; self-heals.Recovered orphaned IN_PROGRESS jobs→ normal self-repair after a restart.Delivery job dead-lettered→ a job failed repeatedly and was parked. The Dead-lettered jobs stat on the dashboard counts these rows and stays red while any exist — the freshness panel deliberately excludes them, so a green freshness with a red dead-letter count means terminally failed work is sitting unshipped. A dead-lettered delete is permanent customer staleness (the backstop regenerates live rows, never deletes). Note itserror_code(see the Kusto ingestion and Devices skipped classes); this needs a data or downstream fix, not a restart.
0 = empty = healthy). No result at all is a
broken metrics scrape, not “quiet” — investigate scraping / escalate to OBS; do not assume the
queue is empty.Delivery job dead-lettered (with the
error_code).5. Kusto ingestion failing
5. Kusto ingestion failing
error_code=kusto_ingest_failed.Ask sentinel:kusto_ingest_failed usually means
Azure SP auth (the ClientSecretCredential), Kusto throttling, or a schema mismatch — all need
Fleet Manager / Azure.Escalate. Sustained kusto_ingest_failed, with the quoted error.5b. Mirror ingest failing (customer dev DB)
5b. Mirror ingest failing (customer dev DB)
fleet_manager_azure_kusto_mirror_ingest_failures_total is increasing, or warning logs
Mirror Kusto ingest failed. Prod-db freshness stays green. Read the reason label first:
dropped (warning log Mirror upload dropped) is pending-byte-budget back-pressure — excluded from the
alert and self-healing on the next sweep. Only failed / tombstone_timeout mean the mirror is not
accepting rows. The alert deliberately ignores dropped, so a mirror that is persistently behind the
budget (every window drops, nothing fails) is not paged — watch
sum by (mirror_database)(rate(fleet_manager_azure_kusto_mirror_ingest_failures_total{reason="dropped"}[1h]))
on the dashboard; a sustained non-zero rate means the dev mirror is falling behind, not failing.What it means. The connector could not queue rows into the mirror destination
(nscale-inventory-dev-db on the dev cluster). For live rows this is deliberately contained —
the delivery succeeds and the prod SLA is unaffected (most commonly an outage or throttling on the
customer’s dev Kusto cluster, a different Azure region, which nothing on our side can write through
anyway). Two tombstone (IsDeleted=true) subtleties: the legacy-schema dev-DB mirror is
deliberately skipped for tombstones (supportsTombstones: false — its tables lack the
IsDeleted column, so a mirrored tombstone would land as a blank live-looking row and blank the
real device in its MV; deletes keep legacy semantics there, stale rows linger). For a mirror that
does carry the V2 schema, a tombstone batch that fails on it fails the delivery job on
purpose and retries until the mirror accepts it — deletes are never regenerated by the backstop
(the repeated primary ingest is absorbed by the MV dedup).Ask sentinel:CONNECTOR_NETBOX_DIRECT_KEEPALIVE_INTERVAL) or the next NetBox change,
whichever comes first. A “mirror healed” conclusion within minutes of recovery is only true for the
projection and link tables. Sustained failures while the dev cluster is healthy mean auth
(the mirror uses the ingest-only azure-kusto Service Principal, granted on the dev cluster only) or
a schema problem — Fleet Manager / Azure.Escalate. Only if failures persist after the dev cluster is confirmed healthy, or the customer
reports dev-db staleness. Live-row mirror failures are never a prod-SLA incident by themselves;
tombstone retry only applies to V2-schema mirrors (the dev-DB mirror skips tombstones), where a
long mirror outage shows as dead-lettered delete jobs (error_code=kusto_ingest_failed) that
recover on redelivery once the cluster returns.6. Devices skipped (transform errors)
6. Devices skipped (transform errors)
MatrixConnectorDevicesSkippedTransformError firing.Ask sentinel:asset_tag, which maps to NodeName) is skipped for that table so the
rest of the site still delivers; the job marks done, so this never shows as a dead letter. The
skip is metered on devices_skipped_total with error_code set to the transform exception class
(ServerValidationError, NVSwitchValidationError, MatrixDeviceNotReadyError — a bounded set)
and logged at ERROR as Skipped device with transform error carrying device_id, table and
reason. MatrixDeviceNotReadyError is the one transient case (interfaces not projected yet) —
it logs at WARNING and a later sweep delivers the device, so the alert excludes it. Every other
error_code is bad or incomplete NetBox data and stays missing from Kusto until the record is
fixed in NetBox — the next backstop sweep then delivers it. It is not a connector bug. If every
device at the site is rejected the job still marks done with nothing ingested; only a site-level
DataCenterValidationError (a site missing its description) fails the job with transform_failed
and dead-letters — see the Dead-lettered jobs row above.Escalate. Only if the NetBox record is not yours to fix — route it to whoever owns it.7. Cross-datacenter link resolution
7. Cross-datacenter link resolution
*_not_in_store warnings are transient — the far side is retried next cycle, so
a few are normal.Escalate. Persistent requeues where far-end sites never resolve, or cable_projection_invalid
dead-letters (the cross-DC far-end resolution path) — include an example cable.8. Freshness lag / cold-start after a deploy
8. Freshness lag / cold-start after a deploy
1/1 and the heartbeat
is above 0, it has recovered.Escalate. A lag that does not clear within ~15 minutes.Disabling or rolling back one instance
Two different things share the word “rollback” here — do not confuse them:- Disabling one customer instance (e.g. pulling Ward out of service without touching sin1) is
a values change: set that instance’s
enabled: falsein the overlay and redeploy. The chart renders nothing for a disabled instance — no Deployment, no ConfigMap, no schema-apply Job — and every other enabled instance’s resources are untouched, because each instance’s rendered config only depends on its own values plus the shareddefaultsblock. This is the fast, safe, per-customer lever; it is an engineer action (values + redeploy), not something an operator does ad hoc. - Rolling back the whole connector to the legacy pipeline (
netbox-matrix-agent/kusto-copy/kusto-monitor) is the unrelated, chart-version-level rollback described in the intro above — reverting the cutover PR. It affects every instance at once and is a last resort, not the tool for isolating a single customer’s incident.
If NetBox-direct is switched on
During the prod soak, NetBox-direct mode is off, so the classes above are the complete picture. If Fleet Manager later switches it on,/readyz can also report netbox_direct_full_pending while
it warms, freshness metrics prefixed fleet_manager_matrix_connector_netbox_direct_ start being
exported, and the log SyncLoop cycle failed — supervisor restarting appears on a failing cycle.
Those are Fleet Manager signals — as an operator, treat netbox_direct_full_pending like any other
“still starting up” state and escalate if it does not clear.
Appendix
Where the connector logs live
Filter Loki byservice_name, always with cluster="fleet-mgr-glo1" and
namespace="fleet-manager":
Customer heads-up template (Fleet Manager / OBS only)
Use this only if Fleet Manager decides the customer should be notified — operators do not send it:Subject: Fleet Manager inventory pipeline — status update We are seeing [brief symptom, e.g. delayed inventory freshness] on the inventory data feed as of [time, UTC]. The Fleet Manager and Observability teams are engaged and investigating. We will update by [time]. No action is required on your side.