watcher-service¶
For every rule with self_heal: true, the watcher-service asks the orchestrator on each poll whether the datasource still matches Git (CheckDatasource), and has it restore the ones that drifted (SyncDatasource). The orchestrator reads the sinks and builds the rules; the watcher holds no sink credentials and reads no rule. It needs only the orchestrator's address, and config.cue for leader election.
What it does¶
Every POLL_INTERVAL
├── ListWatchTargets (the orchestrator lists the rules to watch: self_heal on, not suspended)
└── for each rule
└── CheckDatasource (the orchestrator reads the sink and compares with Git, key by key)
├── in sync → OK
├── syncing → look again next poll
├── unknown → no heal
└── drifted → log + increment metric + SyncDatasource (heal)
Which rules it watches¶
The watcher does not read rules from config.cue, a tenant's bundle.json or a repository's vartrack.json. The orchestrator is the one service that layers those into the rule a sync runs with, so the watcher asks it which rules to watch, on every poll. A rule that gains self_heal, is suspended or is removed reaches the watcher at the next poll; nothing needs a restart.
- If the orchestrator cannot be asked, the watcher keeps watching the rules it has.
- If it cannot be asked at startup, the watcher asks again, waiting a little longer each time (up to one poll interval).
- A rule of two tenants is two targets: each tenant's is checked and healed on its own.
See Drift Detection & Self-Heal for the edge cases (unknown, a heal underway, a sync that died).
A heal already asked for¶
After the watcher asks for a heal, a "drifted" answer for the same environments within the next two polls (at least 30 s) is the drift that heal is about to fix, and it does not ask again. A different drift (another environment now differs too) is new and is healed at once.
Look now¶
POST /admin/check?platform=<platform>&datasource=<datasource>[&tenant=<tenant>] on the admin port makes the watcher check that rule and heal it if it drifted right away, whatever an earlier heal asked, and answers with what each watcher of the rule found:
{"results": [{"watcher": "gitea/mongo", "tenant": "demo", "state": "drifted", "detail": "differs from Git: gitea/mongo/default", "healed": true}]}
404 when the rule is not watched (no self_heal, or suspended). With INTERNAL_AUTH_SECRET set the call needs an admin grant signed with it (Authorization: Bearer <grant>); without a secret the watcher runs open, as it does towards the orchestrator, and accepts it unsigned. The end-to-end tests use it to wait for the watcher's answer instead of its next poll; a test stack also lowers POLL_INTERVAL (the demo uses 10s).
Running¶
ORCHESTRATOR_ADDR=localhost:50051 \
CONFIG_PATH=config.cue \
POLL_INTERVAL=60s \
go run ./watcher-service/cmd
With Docker¶
docker run \
-p 9091:9091 \
-v ./config.cue:/app/config.cue:ro \
-e APP_ENV=production \
-e ORCHESTRATOR_ADDR=orchestrator:50051 \
-e CONFIG_PATH=/app/config.cue \
-e POLL_INTERVAL=60s \
jarin-devoss/watcher-service:latest
Environment variables¶
| Variable | Default | Description |
|---|---|---|
APP_ENV |
development |
production requires TLS credentials |
CONFIG_PATH |
config.cue |
CUE bundle path; read only for watcher_leader_election_datasource |
ORCHESTRATOR_ADDR |
localhost:50051 |
gRPC address of the orchestrator |
POLL_INTERVAL |
60s |
How often the rules to watch are listed and each is checked |
HEAL_TIMEOUT |
30s |
Max time for a self-heal call |
ADMIN_ADDR |
:9091 |
Health + metrics listener |
OTEL_ENABLED |
false |
Enable OpenTelemetry tracing |
ELK_ENABLED |
false |
Enable Elasticsearch log shipping |
Every variable, with the observability ones, is in Environment variables.
Health + metrics¶
GET /health/liveness # 200 while alive
GET /health/readiness # 200 when orchestrator connection is ready
GET /metrics # Prometheus metrics (port 9091)
Key metrics:
| Metric | Description |
|---|---|
vartrack_watcher_drift_detected_total |
Drift found, by rule |
vartrack_watcher_heal_triggered_total |
Heals the orchestrator accepted |
vartrack_watcher_heal_failed_total |
Heals that could not be requested |
vartrack_watcher_poll_duration_seconds |
How long a CheckDatasource took |