Service dependency map
| Service | Hard dependencies | Degraded behavior |
|---|---|---|
| API | PostgreSQL; valid port/JWT configuration | NATS publication/subscription is optional unless private-location authorization is enabled. Run-now checks and live status/alert updates degrade without NATS. |
| Scheduler | PostgreSQL and NATS | No useful degraded mode. It schedules checks, ingests results, builds rollups, purges deleted monitors, enforces retention, and schedules mesh probes. |
| Worker | NATS | Can execute checks without PostgreSQL. Database-free location workers intentionally omit DB; AI-RCA and notification consumers require DB. |
| Alerter | PostgreSQL | NATS is optional for synchronous evaluation/dispatch, but required for async notification publication and alert-event fan-out. |
| Status page | PostgreSQL | NATS subscriber is optional; without it, cache invalidation waits for STATUS_PAGE_CACHE_TTL. |
| Frontend | Reachable API proxy target | Static shell may load, but authenticated product workflows fail when API is unreachable. |
Collector agent (probara-collector) | Reachable HTTPS API and valid API key | Scrapes host metrics locally but cannot export while disconnected; there is no remote disable — uninstall is operator-run. |
Start, stop, inspect, and restart
make start-all-local
make restart-all-local
make stop-all-local
tail -f /tmp/probara-*.log
tail -f /tmp/probara-ui.logmake start-all
make ps
make logs
make logs-api
make logs-scheduler
make logs-worker
make logs-status
make restart-all
make stop-allkubectl -n probara get pods,deployments,statefulsets,jobs
kubectl -n probara get events --sort-by=.lastTimestamp
kubectl -n probara logs deployment/probara-api --all-containers --tail=200
kubectl -n probara logs deployment/probara-scheduler --all-containers --tail=200
kubectl -n probara rollout status deployment/probara-api
kubectl -n probara rollout status deployment/probara-workerHealth, readiness, and listener truth
/healthz answers liveness, /readyz checks required dependencies, and /metrics exposes Prometheus data. The port carrying those routes differs by service.
| Service | Local/Compose URL | Liveness semantics | Readiness semantics |
|---|---|---|---|
| API | http://localhost:8080 | Process HTTP handler is alive. | PostgreSQL responds within five seconds. NATS is not part of normal API readiness. |
| Scheduler | http://localhost:9091 | Process operational listener is alive. | Both PostgreSQL and NATS respond within five seconds. |
| Worker | http://localhost:9092 | Healthy while NATS is connected or reconnecting; unhealthy after permanent closure. | NATS responds, plus PostgreSQL when the worker was configured with DB access. |
| Alerter | http://localhost:9094 | Alive; nil/optional NATS is accepted, but a permanently closed configured connection is unhealthy. | PostgreSQL responds; NATS also responds when configured. |
| Status page | http://localhost:9093 | Operational listener is alive. | PostgreSQL responds within five seconds. |
| NATS | http://localhost:8222/healthz | Broker monitor endpoint. | Use JetStream/consumer checks in addition to HTTP liveness. |
curl -fsS http://localhost:8080/healthz
curl -fsS http://localhost:8080/readyz
curl -fsS http://localhost:9091/readyz
curl -fsS http://localhost:9092/readyz
curl -fsS http://localhost:9093/readyz
curl -fsS http://localhost:9094/readyz
make healthcheckWorker also serves /mesh/echo on the metrics listener. When its HTTP_PORT differs, it starts a second listener containing only mesh echo and /healthz, allowing mesh exposure without publishing /metrics.
Prometheus metrics
Custom metrics use namespace probara and a sanitized service subsystem, for example probara_scheduler_loops_total. Go runtime and process collectors are also registered.
| Area | Key metrics | What to alert on |
|---|---|---|
| Scheduler loop | probara_scheduler_loops_total, monitors_scheduled_total, loop_duration_seconds, monitors_in_batch | No loop increments, duration approaching schedule interval, or sustained full batches. |
| Scheduler failures | jobs_publish_errors_total, db_errors_total, mesh_publish_errors_total | Any sustained nonzero rate. |
| Result ingest | results_ingested_total, result_ingest_errors_total, result_ingest_duplicates_total, mesh_results_ingested_total | Publish success without ingest growth, ingest errors, or unexpectedly high duplicates. |
| Retention and rollups | retention_cleanup_runs_total, retention_cleanup_rows_total, rollup_runs_total, rollup_rows_total, rollup_errors_total, rollup_duration_seconds, rollup_cursor_unix | Stale completeness watermark, error growth, or runs exceeding the maintenance window. |
| Monitor purge | monitor_purge_runs_total, monitor_purge_rows_total, monitor_purge_monitors_total, monitor_purge_errors_total, monitor_purge_run_duration_seconds | Growing errors or deleted monitors never becoming fully purged. |
| Worker execution | probara_worker_jobs_total{status=…}, job_duration_seconds, http_request_duration_seconds, http_errors_total{reason=…} | Failure-rate changes, high latency, timeouts, and policy blocks. |
| Worker NATS/results | nats_ack_total, nats_nak_total, result_publish_errors_total | NAK or publish-error growth and falling ACK throughput. |
| Status live updates | probara_status_page_sse_subscriber_connected | Zero when low-latency status invalidation is expected. |
curl -fsS http://localhost:8080/metrics | grep '^probara_'
curl -fsS http://localhost:9091/metrics | grep '^probara_scheduler_'
curl -fsS http://localhost:9092/metrics | grep '^probara_worker_'
curl -fsS http://localhost:9093/metrics | grep '^probara_status_page_'Logs and diagnostic context
- Use
LOG_LEVEL=debugtemporarily for connection, scheduling, and readiness diagnosis; restoreinfoafter the incident. - Correlate monitor ID, tenant ID, location ID, alert ID, NATS stream/subject, and timestamps across services.
- Never paste location-authenticated NATS URLs, API keys, SMTP passwords, webhook secrets, OIDC secrets, JWT secrets, or encrypted configuration keys into tickets or chat.
- A warning that status-update publishing failed means core CRUD may work while public live invalidation is degraded.
- A scheduler warning that
RESULT_INGEST_ENABLED=falsemeans workers can run checks but results will not enter PostgreSQL. - A plaintext/NoOp encryption warning requires immediate review before storing or updating sensitive configurations.
docker compose logs --since=15m api scheduler worker alerter status-page
docker compose logs -f --tail=200 scheduler workerDatabase migrations and administrator bootstrap
make migrate
# Direct local migration command
POSTGRES_URL='postgres://probara:probara@localhost:5432/probara?sslmode=disable' \
MIGRATIONS_PATH='./shared/db/migrations' \
go run ./cmd/migrateThe migration CLI requires POSTGRES_URL, defaults MIGRATIONS_PATH to ./migrations, and retries the initial database connection up to 30 times at one-second intervals. API startup also runs the shared migration set. The Helm migration Job is a post-install/post-upgrade hook.
POSTGRES_URL='postgres://probara:probara@localhost:5432/probara?sslmode=disable' \
ADMIN_USERNAME='admin' \
ADMIN_PASSWORD='use-a-password-manager-generated-value' \
ADMIN_BCRYPT_COST='12' \
go run ./cmd/adminBackup and restore
make db-backup # writes ./backup.sql
# Inspect and copy the backup away from the workstation before maintenance.
ls -lh backup.sql
# Restore into the configured Compose database.
make db-restore- Quiesce writes or capture a database-consistent snapshot appropriate to your PostgreSQL topology.
- Back up PostgreSQL, including schema migration state, tenants, monitor configuration, alert state, users, API keys, audit data, and encrypted ciphertext.
- Back up the complete encryption keyring separately. A database backup without the historical keys may be unrecoverable.
- Protect JetStream state or accept that queued checks/results/notifications may be replayed or lost after recovery.
- Preserve browser artifacts separately if they are part of your incident evidence policy.
- Restore into an isolated environment, run readiness and data-integrity checks, then perform a documented cutover.
The bundled PostgreSQL and NATS StatefulSets use PVCs but provide no scheduled backup controller, point-in-time recovery, replication, or cross-zone failover. Production recovery objectives require managed services, an operator, or external backup automation.
Retention, rollups, and deletion
- Tenant telemetry retention
- Each tenant’s
data_retention_dayscontrols raw check results and mesh results, and can tighten (never extend) raw agent metric-sample retention belowMETRIC_RAW_RETENTION_DAYS. A value of0preserves check/mesh results; the public validator otherwise accepts 30–3650 days. It is configured in tenant settings. - Audit retention
AUDIT_RETENTION_DAYSis a separate compliance control, defaults to 365 days, and uses0to retain forever.- Monitor purge
- Soft-deleted monitors are later purged with dependent rows in bounded batches.
- Rollups
- Result ingestion marks each affected hour in a dirty-bucket ledger within the same transaction, and scheduler maintenance rebuilds marked buckets wholesale from raw results — late or redelivered rows can never be permanently skipped. A failed batch leaves its marks for the next run.
Scheduler checks once per minute whether the configured UTC cleanup hour is due, runs at most once per UTC date, uses a PostgreSQL advisory lock, and applies a 30-minute cleanup timeout. Batch size and maximum rows per run bound database pressure.
| Control | Default | Operator effect |
|---|---|---|
RETENTION_CLEANUP_ENABLED | true | Enable daily tenant telemetry cleanup. |
RETENTION_CLEANUP_HOUR_UTC | 2 | Daily target hour, 0–23 UTC. |
RETENTION_CLEANUP_BATCH_SIZE | 5000 | Delete batch size. |
RETENTION_CLEANUP_MAX_ROWS_PER_RUN | 200000 | Per-run cap; backlog can require multiple days/runs. |
AUDIT_RETENTION_DAYS | 365 | Separate hourly API audit pruner; 0 disables pruning. |
METRIC_RAW_RETENTION_DAYS | 30 | Raw OTLP metric samples (daily partitions); a tenant's data_retention_days can tighten it. Hourly metric rollups are kept 400 days. |
OTLP_MAX_SERIES_PER_MONITOR | 2000 | Metric-series cardinality cap per agent monitor; overflow points are rejected via OTLP partial_success. |
OTLP_MONITOR_RATE_PER_MIN | 60 | OTLP ingest requests per minute per monitor; excess returns 429 with Retry-After. |
MONITOR_PURGE_INTERVAL_SECONDS | 30 | Soft-delete purge cadence. |
MONITOR_PURGE_BATCH_SIZE | 5000 | Child-row purge batch size. |
MONITOR_PURGE_MAX_ROWS_PER_RUN | 200000 | Purge safety cap. |
NATS and JetStream operations
| Contract | Code default | Purpose |
|---|---|---|
CHECK_JOBS / check.jobs | Work-queue stream/subject | Scheduler and API publish; workers consume. |
CHECK_RESULTS / check.results | Work-queue stream/subject | Workers publish; scheduler persists. |
ALERTS / alerts | Alert event stream/subject | Alerter publishes; API live subscriber consumes. |
NOTIFICATIONS / alerts.dispatch.> | Optional work queue | Async alerter-to-worker dispatch. |
AI_RCA / ai.rca.jobs | AI work queue | API publishes tenant RCA work; DB-connected workers consume. |
Core NATS statuspage.updates | Non-JetStream fan-out | Cache/status update invalidation. |
nats --server "$NATS_URL" server check connection
nats --server "$NATS_URL" stream ls
nats --server "$NATS_URL" stream info CHECK_JOBS
nats --server "$NATS_URL" consumer ls CHECK_JOBS
nats --server "$NATS_URL" stream info CHECK_RESULTS
nats --server "$NATS_URL" consumer info CHECK_RESULTS result-ingest- Compare the actual stream subjects with every publisher and consumer environment before deleting or recreating streams.
- Watch pending, redelivered, and unacknowledged counts; queue depth without scheduler/worker progress indicates a contract or connectivity failure.
CHECK_JOB_LEGACY_CONSUMERScauses scheduler startup to delete named filterless consumers. Review the list before renaming durable consumers.- JetStream PVC persistence does not replace a broker backup/replication strategy.
- Core NATS status updates are ephemeral by design; status cache TTL is the fallback.
Synthetic-browser artifacts
- Workers write failure screenshots beneath
SYNTHETIC_BROWSER_ARTIFACTS_DIR; API reads them for authenticated retrieval. Trace and HAR requests currently emit warnings and do not create artifacts. - Compose mounts a shared named volume into API and worker.
- Local-process services share the same host directory.
- The current Helm workloads do not mount shared artifact storage, so cross-pod and cross-node retrieval is unreliable.
- Set retention and access controls appropriate for screenshots because they can capture page content, tokens, personal data, or internal application state.
Verification and tests
make test
make lint
# make fmt is a check, not a formatter.
gofmt -w path/to/changed.go
make fmt
make vet(cd web && npm run lint && npm run build)
(cd website && npm run typecheck && npm run build)
helm lint ./helm/monitoring-platform \
--set secrets.adminJwtSecret='replace-with-32-plus-char-secret-value' \
--set api.publicBaseURL='https://probara.example.com'make testruns the Go module with race detection and writescoverage.out; it excludes script packages. There is no separate agent module or test suite — the collector is a prebuilt OpenTelemetry distribution (make build-collector-static).- Database integration tests use Testcontainers and require a working Docker daemon capable of starting PostgreSQL 16.
- Run
make test-coverto open the HTML coverage report. - Frontend changes require both lint and a production build; documentation site changes should at least run
npm run typecheckandnpm run buildfromwebsite/. - Helm lint requires the chart’s mandatory JWT and public base URL values.
- For a release candidate, verify scheduled checks, on-demand checks, all touched monitor types, result ingest, alerts, notification delivery, status pages, OIDC, imports, private locations, and backup restore.
Troubleshooting guide
| Symptom | Likely cause | Checks and resolution |
|---|---|---|
| Configuration exits immediately | Missing ports/JWT, short or placeholder JWT, invalid bool/int/CIDR, incomplete OIDC, or location ID/credential mismatch | Read the first fatal line; compare the process environment with the configuration reference. |
| Compose command fails before containers start | Required ${ADMIN_JWT_SECRET:?…} or ${PUBLIC_BASE_URL:?…} interpolation is missing | Populate .env before any Compose-backed Make target. |
| PostgreSQL authentication fails after old local experiments | Existing volume was initialized with different credentials | Back up first. If disposable, docker compose down -v reinitializes all repo-scoped volumes and destroys local data. |
| Scheduled checks work, Run now does not | API publishes on a different CHECK_JOB_SUBJECT than the workers consume (a custom override applied to only some services) | Align CHECK_JOB_SUBJECT and stream configuration on API, scheduler, and worker; the shipped Compose file and Helm chart set all three identically. Inspect actual JetStream subjects. |
| Checks execute but no history appears | Result-ingest disabled, scheduler unavailable, result subject mismatch, DB failure, or ingest backlog | Check scheduler readiness/logs and results_ingested_total / result_ingest_errors_total; inspect CHECK_RESULTS consumer state. |
| Private location does not start | Missing credential, invalid UUID, unsafe public NATS URL, broker authentication/TLS issue, or no external broker route | Regenerate deployment info; require both location ID and credential; test the TLS/WSS endpoint from that network. |
| Encrypted monitor fails only in scheduler | Scheduler lacks the current/historical encryption keyring | Provide the identical PROBARA_SECRETS_KEY* set to scheduler. The shipped Compose file and Helm chart wire the base key; add rotation keys (_V2+) through the chart's top-level extraEnv so every service receives the same keyring. |
| Browser screenshot is 404/missing | API and worker use different filesystems/pods or artifact expired | Verify identical artifact directory and shared storage. Helm does not currently provide it. |
| Email fails on port 587 | SMTP_USE_TLS=true selects implicit TLS rather than STARTTLS | Confirm provider protocol. Use the correct implicit-TLS port or a supported non-TLS/private relay setting. |
| OIDC redirects or callback fails | Issuer/client secret/callback mismatch, bad public base URL, or JIT tenant/role error | Compare the exact HTTPS callback with the IdP registration and inspect OIDC discovery. |
| SSO user loses roles (or gains none) after login | OIDC group mappings exist but the ID token carries no groups claim — groups scope not requested, wrong OIDC_GROUPS_CLAIM, or the IdP does not embed groups in the ID token | Check API logs for the missing-groups warning, add groups to OIDC_SCOPES, and verify the group-mapping requirements. Deleting all mappings restores manual role management. |
| Status page updates slowly | NATS status subscriber disconnected or update subject differs | Check sse_subscriber_connected, subscriber logs, shared subject, and cache TTL. |
| Frontend links point to old origin | NEXT_PUBLIC_* changed only at runtime | Rebuild the frontend client bundle with the desired public values or use stable relative routing. |
| Prometheus cannot scrape API on 9090 | API has no separate metrics listener | Scrape /metrics on API HTTP port 8080 or fix the workload/service topology. |
Incident response checklist
- Identify the affected tenant, monitor/location, time window, and whether the failure is control plane, check execution, result persistence, alerting, or presentation.
- Check liveness and readiness on the real service listeners; preserve logs and relevant metrics before restarting.
- Inspect PostgreSQL availability and NATS stream/consumer state without deleting consumers or messages.
- Compare deployed queue variables and encryption-key versions across all replicas.
- Reduce blast radius with reversible actions: pause a broken monitor/policy, scale a healthy worker fleet, or disable optional async paths.
- Restore service, verify end-to-end from schedule through status/alert delivery, then reconcile delayed or duplicate jobs.
- Record the operator action in incident notes and retain the audit/log/artifact evidence according to policy.