Health module
Source: modules/health/.
At a glance
Section titled “At a glance”| CLI | shc status |
| HTTP endpoints | see the route reference |
| Cross-module deps | app, backup, docker, job, metrics, network, node, vault |
Source layout
Section titled “Source layout”modules/health/├── command.go├── commands/├── crons/│ ├── tick.go # 15s leader-only cron│ ├── poll.go # 1min leader-only cron│ ├── reconcile.go # 1min leader-only cron│ └── maintenance.go # DB-tracked cron jobs├── events.go├── poll_deployments.go├── poll_nodes.go├── reconcile.go├── router.go # /health, /ready endpoints├── state.go # module-level HealthState singleton├── tasks.go└── types.go # HealthState, NodeHealth, DeploymentHealthResponsibilities
Section titled “Responsibilities”Three concerns, all rooted in the health module:
- Runtime health endpoints — liveness, readiness, and a detailed
health report for load balancers,
shc status, and monitoring. - Cron tiers — the structured periodic work that polls the cluster, reconciles degraded deployments, and purges old rows.
- The
HealthStatesingleton — the module-level snapshot of cluster state that crons write to and the HUD / endpoints read from.
The module owns no subsystem lifecycle; HealthState is a plain
module-level object decoupled from subsystem start/stop.
Cron tier model
Section titled “Cron tier model”All periodic work runs through three leader-only cron tiers plus a set of DB-tracked maintenance cron jobs. Every cron uses skip-if-running — if the previous invocation has not completed, the next tick is silently skipped.
Tier summary
Section titled “Tier summary”| Cron | Type | Default | Config path |
|---|---|---|---|
| Tick | rpc cron | 15 s | timing.tick_interval |
| Poll | rpc cron | 1 min | timing.poll_interval |
| Reconcile | rpc cron | 1 min | timing.reconcile_interval |
| Cleanup | task cron | 1 h | maintenance.cleanup.interval |
| Disk scan | task cron | 1 h | maintenance.disk_scan.interval |
| Usage reconcile | task cron | 6 h | maintenance.usage_reconcile.interval |
| Disk auto-prune | task cron | 1 h | maintenance.disk_auto_prune.interval |
| Repo sync | task cron | 6 h | maintenance.repo_sync.interval |
| Retention cleanup | task cron | daily 02:00 | maintenance.retention_cleanup.schedule |
All intervals are config-driven via function references resolved at scheduler start — see cron in the communication-patterns doc.
Tick — 15 s, leader-only
Section titled “Tick — 15 s, leader-only”Fast, lightweight data gathering. Runs sequentially within one tick:
node_metrics— broadcast gather, each node returns system metrics; leader writes toHealthState.container_metrics— broadcast gather, each node returns docker stats.global_diagnostics— local leader query for task/worker/cron counts.quota_metrics— local leader collection of quota gauges.vault_check— broadcast fire-and-forget; if any node reports locked and the leader holds the password in memory, re-broadcast unlock (see Vault unlock flow).
Poll — 1 min, leader-only
Section titled “Poll — 1 min, leader-only”Moderate-weight state sync:
deployment_sync— broadcastdocker compose psto every node; leader writes container/service state to DB and toHealthState.retention_config_check— leader-local audit of retention policies.
Reconcile — 1 min, leader-only
Section titled “Reconcile — 1 min, leader-only”Self-healing for degraded deployments plus infrastructure enforcement. Broadcast fire-and-forget; each node acts on its own local state.
Per-node flow:
- Read in-memory
HealthStatefor this node’s deployments. - If any deployment is degraded, do a live
docker compose psre-check before acting — never reconcile on stale in-memory state alone. - If still degraded:
- Skip if already in the resolving set for this stack.
- Skip if cooldown has not elapsed since last attempt.
- Apply exponential backoff on repeated failures.
- Add to resolving set → run
docker compose up -d→ remove on completion.
- All cooldown / backoff state is in-memory only and resets on process restart.
Infrastructure enforcement (same cron): Traefik port-binding check and restart, OpenObserve health check, docker network creation.
Maintenance crons
Section titled “Maintenance crons”Run via task cron, so each invocation creates a TaskModel row
and is audit-tracked.
The Cleanup job also does task purging:
- Completed tasks older than
maintenance.completed_task_retention(default 24 h) are bulk-deleted. - Failed tasks older than
maintenance.failed_task_retention(default 7 d) are bulk-deleted.
This is a single bulk DELETE; the retention defaults
keep failed rows longer for debugging.
HealthState singleton
Section titled “HealthState singleton”Lives at modules/health/state.go as a module-level instance of
the HealthState struct:
type HealthState struct { Nodes map[string]*NodeHealth Deployments map[string]*DeploymentHealth LastPoll time.Time}Writers: the Tick and Poll crons (node_metrics, container_metrics,
deployment_sync). Readers: the HUD collector, shc status, and the
detailed shc.status.get method.
Alongside HealthState, the same module holds:
_cron_heartbeats: dict[str, datetime]— last successful completion per cron (read by the HUD for heartbeat display).- Reconcile backoff state — cooldown dict, resolving set — also in-memory only.
Vault unlock flow
Section titled “Vault unlock flow”Unlock — user POSTs to shc.vault.unlock with the password:
- Request is routed to the leader (existing behavior).
- Leader calls
unlock_vault(password)locally — password held in memory only. - Leader broadcasts
POST /internal/vault/unlockto all other nodes via mTLS internal API. - Each node unlocks locally.
Ongoing enforcement runs from the Tick cron’s vault_check step:
leader broadcasts a vault-status check; any node reporting locked
triggers a re-broadcast of the unlock if the leader still holds
the password. On leader restart, the leader first tries the
configured env var / password manager; if neither yields a password,
nodes stay locked until a human unlocks again.
Security constraints:
- There is no endpoint to read the vault password — only write (unlock).
- The password is only transmitted over the mTLS internal API (port 4003).
Performance at scale
Section titled “Performance at scale”| Metric | 1 node | 10 nodes | 50 nodes |
|---|---|---|---|
| Tick broadcast RPCs / min | 4 (self) | 40 | 200 |
| Poll broadcast RPCs / min | 1 | 10 | 50 |
| Reconcile broadcast RPCs / min | 1 | 10 | 50 |
| Internal-API calls / min (total) | ~6 | ~60 | ~300 |
Broadcasts run concurrently through goroutines and errgroup in
RpcTransport.FanOut; 50 nodes = 5 calls/s, comfortably within
mTLS connection capacity.
See also
Section titled “See also”- CLI reference —
shc status - Communication patterns — RPC and task systems
hudmodule — consumesHealthStatevia the internal API.vaultmodule