Перейти к содержимому

Deploy a standalone collector

This is the operator walkthrough for standing up a standalone collector — the dial-out container you deploy near the devices it collects. It exists for two reasons: it reaches a network the core cannot, and it does the work there, holding the hundreds of slow device sessions at the site and handing the core only the finished, scrubbed result. It keeps no database: its durable state is its own identity and an outbox of results not yet acknowledged.

If the core reaches your devices comfortably and is not spending its time waiting on them, you don’t need this — use the built-in embedded collector.

A standalone collector dials out to the core, so the site it lives in needs outbound reachability to the core and nothing inbound — no ports opened toward the collector or the site.

The whole deployment is one command, and the core writes it for you. Everything below is the two minutes of clicking that produces that command, and the proof that it worked.

  • A host at the site that can run the collector container (Docker), with outbound network access to the Taranac core’s API — or a Taranac appliance, which installs the collector from images already on its disk.
  • RBAC permissions on the core: collectors → create to add the collector, and ncm_devices → edit to issue its enrollment token (and to revoke its identity later).
  • Local reachability from that host to the devices you want to collect, over SSH/SCP.
  • If a proxy, load balancer or firewall sits between the site and the core, it must tolerate an idle HTTPS request of about half a minute. The collector’s job poll is a held long-poll — the core keeps the GET open for roughly 25 seconds, sending nothing, and answers the instant work appears — so a middlebox that cuts idle connections sooner will sever it. Nothing breaks if it does: the collector falls back to its slow poll interval on its own. You simply lose the near-instant pickup that makes Collect now feel immediate.

Step 1 — create the collector on the core

Section titled “Step 1 — create the collector on the core”

In the core UI, go to Settings → System → Collectors and choose New Collector.

The New Collector drawer, with a name and the Standalone mode badge

Give it a Name, and optionally a Site label — free text for humans, because the core never calls the collector and this is just where you note where it lives. Mode is fixed at Standalone: the local built-in collector already exists and cannot be created again.

It starts life with status Unknown, because it has not reported in yet:

The Collectors list with the new collector present and status Unknown

Step 2 — issue a reveal-once enrollment token

Section titled “Step 2 — issue a reveal-once enrollment token”

Open the new collector. Its detail page carries the identity controls, and the Details rail on the right is where you will watch this deployment land — right now it reads Not enrolled, Never seen, no fingerprint.

The collector's detail page before enrollment, showing Not enrolled

Choose Issue enrollment token. The dialog asks one thing — how long the token stays valid. The default of 60 minutes is long enough to walk to the other host and short enough to limit exposure; the maximum is 7 days.

The Enroll dialog asking for a token lifetime in minutes

Then it shows, once: the token, the core’s URL, and a ready-to-paste join command with both already filled in.

The enroll result showing the token, the core URL and the ready-to-paste join command

Two things about that command are worth knowing before you copy it:

  • The address is the one you are reaching the core at right now, derived from your own request — so it is demonstrably an address that works, rather than a placeholder or a PUBLIC_BASE_URL that drifted months ago. It is editable, because a collector on a segmented site may have to dial the core by a different name than the browser you issued the token from. Edit it here, before you copy.
  • --core must include the /api/v1 prefix. The request signature covers the full path, so a missing prefix means every signed poll fails authentication.

On the site host, run the command from the dialog in the unpacked bundle directory:

Terminal window
cd /opt/taranac
./collector-join.sh \
--core https://10.20.0.40/api/v1 \
--enrollment-token v_yj0s3BGEugY_WHjuxaa…

On a Taranac appliance the same install is driven by the module installer instead — sudo taranac-module collector, run over SSH. The first-boot console wizard deliberately hands over rather than asking a 32-character token to be typed at a hypervisor console. It asks for the pair the dialog shows together, the core URL and the token, plus a poll interval (default 30 seconds), and it installs from an image already on the appliance’s disk — it never reaches for a registry.

The script writes the collector’s configuration beside itself, starts the container, and then waits until the collector has actually attached before reporting success:

collector-join: writing collector.env, enrollment-token…
collector-join: starting the collector…
[+] up 3/3
✔ Network taranac-collector_default Created
✔ Volume taranac-collector_collector_state Created
✔ Container taranac-collector Started
collector-join: waiting for the collector to attach to the core…
collector-join: the collector is attached to https://10.20.0.40/api/v1.
Trust: pinned on first contact (the fingerprint appears in the log below)
Core UI: NCM -> Collectors — it is there now.

That wait is the point: if the collector did not attach, the script prints the daemon’s own reason and exits non-zero, instead of leaving you with a container quietly restart-looping and a green “done” on your screen.

Under the hood, on first boot the collector:

  1. Generates its own Ed25519 identity key and persists it locally (private key, mode-restricted, never leaves the box).
  2. Redeems the one-time token against the core, which pins the collector’s public key as its identity.
  3. Deletes the token (it’s spent) and starts long-polling the core for work.

From here every request the collector makes to the core is signed with its Ed25519 key and timestamped — there’s no long-lived shared secret on the wire.

Two commands to keep for later:

Terminal window
docker compose --env-file .env.collector -f docker-compose.collector.yml logs -f collector
docker compose --env-file .env.collector -f docker-compose.collector.yml ps

The join writes two files beside the compose file and they do different jobs, so don’t be surprised by either. collector.env is the daemon’s own configuration — the core URL, the trust choice, the poll cadence — and is the one you may edit. .env.collector is compose interpolation only (the image tag, and the CA bind-mount source when you supply one); it exists so the install never writes the bundle’s own .env, which belongs to a different stack. That is why every compose command above carries --env-file .env.collector.

Everything else collector-join.sh accepts at install time:

FlagWhat it does
--ca-file <path>verify the core against a PEM CA bundle instead of pinning — see Trust modes
--trust-anyverify nothing. Lab only; this channel carries device credentials
--collector-id <label>a display label for this container. Cosmetic
--poll-interval <seconds>the loop’s floor between job polls — used when it is backing off, since a healthy poll is held open by the core. Default 30, minimum 5. Widening it also widens the container’s health window, so a slow site does not read unhealthy while collecting perfectly
COLLECTOR_IMAGE=<image> (environment)run an image already on this host instead of the published one for this version — the air-gapped case

--ca-file and --trust-any contradict each other and the script says so. Every remaining flag is an operation on an installed collector — --status, --update, --uninstall, --reset-trust — and they are covered below.

Step 4 — verify it’s online and assign work

Section titled “Step 4 — verify it’s online and assign work”

Back on the core, the collector’s page answers for the whole deployment:

The collector's detail page after enrollment: Online, connected node, versions compatible, identity active

What it saysWhat it means
Status: OnlineIt has reported in within the staleness window
Connected to nodeWhich cluster node this collector talks to. Under HA there is no shared client-facing address, so each collector talks to exactly one node — and when a collector goes quiet, this is what tells you whose outage explains it
Version · CompatibleThe core’s version and the collector’s, and whether they agree
Identity: Active + fingerprintThe public key the core pinned at enrollment, and when it was issued

Then put it to work:

  • Set the collector field on the sources for that site (or bulk-onboard the site’s devices with this collector selected).
  • Use Collect now on a config to confirm end-to-end reachability, then check the version rail for the first snapshot.

The collector verifies the core one of three ways. The default is the middle one, and the right answer for almost everyone.

ModeHow to get itWhat it means
Pin (default)nothing to doThe certificate presented on first contact is the only one accepted afterwards. A later change is refused and named, not silently accepted
CA--ca-file /path/ca.crtVerify against a PEM CA bundle instead. The strictest mode. Behind an nginx edge this is the edge’s CA, not the core’s
Any--trust-anyVerify nothing. A lab setting — this channel carries device credentials, so do not ship it

On an appliance, the CA mode is sudo env COLLECTOR_CA_FILE=/path/ca.crt taranac-module collector.

When the core’s certificate is legitimately replaced, the collector refuses the new one and prints both fingerprints — a pin that re-learns itself on every restart protects nothing. Re-pinning is a deliberate one-shot:

Terminal window
./collector-join.sh --reset-trust # on a Docker host
sudo taranac-module collector --reset-trust # on an appliance

Persistence — what must survive a restart

Section titled “Persistence — what must survive a restart”

The collector keeps a small amount of durable state on a named volume that must persist across container restarts and image updates:

  • its identity (the Ed25519 private key, public cert and fingerprint), and
  • its outbox — the local, durable store-and-forward buffer of results not yet acknowledged by the core.

The outbox is what makes a brief link outage or a restart safe: buffered captures are re-delivered idempotently, so nothing is lost and nothing is double-written. It is bounded, though — past 10,000 buffered results the collector stops claiming new work and says so, while the uploader keeps draining to relieve the pressure. A very long outage therefore pauses collection rather than growing a queue without limit. If you destroy that volume, the collector loses its identity and must be re-enrolled.

Note the collector stores no device credentials at rest — those are handed to it just-in-time per collection and used only in memory.

  • Rotate / re-enroll — issue a fresh token from the collector’s page and re-run the join on the host. Supplying a new token to a box that is already enrolled re-attaches it: the daemon rotates its keypair and enrolls again.
  • Revoke — from the collector’s detail page, Revoke identity invalidates its key immediately; the next signed request it makes is rejected with a 401, and after five consecutive rejections the daemon halts into a re-enroll required state rather than inventing a new identity for itself. Buffered outbox results are kept and re-ingest once it is re-enrolled. Use this if a site host is decommissioned or compromised.

Both answers come from local state only, deliberately: this is what you run when the core is the thing you suspect, and a status command that hangs on the connection under investigation is worse than none. Neither makes a network call.

On a Docker host, in the bundle directory:

Terminal window
./collector-join.sh --status # where it points and what trust mode it uses,
# the container's state and health, whether it
# is enrolled (key-id), the pinned core-cert
# fingerprint, how long ago it last polled, how
# many results are waiting in the outbox, and
# the last error SINCE IT LAST STARTED
./collector-join.sh --uninstall # container, state volume and config files
./collector-join.sh --uninstall --force # …discarding a non-empty outbox

On an appliance:

Terminal window
sudo taranac-module --list # what is installed on this box
sudo taranac-module collector --status # the same local answer, minus the
# outbox count and the trust line
sudo taranac-module collector --uninstall # containers and volumes, cleanly
sudo taranac-module collector --uninstall --force

“Since it last started” is not a detail: a collector that failed twice and then attached is healthy, and reporting its last historical error as the current state is exactly the stale answer these commands exist to stop giving.

Two things about removal:

  • The outbox is checked first. If it still holds results the core has not accepted, the uninstall refuses and says how many. Those results exist nowhere else — the devices were read here and the configurations went no further — so let it drain (it retries on its own) or accept the loss explicitly with --force.
  • It is local. Neither command deletes the collector on the core. A box being decommissioned frequently cannot reach the core any more, and an uninstall that fails exactly then is not an uninstall — so the two halves are independent, and each reminds you of the other. Delete the row yourself under Settings → System → Collectors, or it sits in the list for ever as offline.

The collector image and the core must agree on the snapshot contract — how a config is scrubbed, hashed and fingerprinted. If they diverge, the core fail-closes that collector: it dispatches nothing and ingests nothing until the two match again. The Version row on the collector’s page is where you see the two side by side; the mechanism, and why idling is the correct outcome, is on the Collectors page.

Three consequences worth carrying into the field:

  • It is not scoped to whatever changed. A skewed collector stops collecting for every tracked config it serves, not only the ones touched by the change that moved the contract.
  • A refused poll is not a heartbeat. The contract check runs before the core gets far enough to stamp last_seen, so a fail-closed collector also stops reporting in: within 15 minutes it reads Offline and raises the ordinary ncm_collector_offline alert. Locally the same thing surfaces as an unhealthy container, because the daemon only touches its liveness file on a poll that actually succeeded.
  • So a collector that “went offline” right after an upgrade is usually a contract skew, not a dead host or a broken link. Check its log for version mismatch (409) before you go hunting a network fault.

1.2.6 moved the contract, because the two masking switches on a scrub set and a recipe changed what a snapshot is identified by. Every standalone collector still on an older image fail-closes the moment its core passes that line, whether or not any of its configs use those switches — so plan the collector upgrades into the same maintenance window as the core upgrade. Recovery needs no re-enrollment: the collector keeps slow-polling while refused and resumes on its own the moment the images agree again.

Terminal window
./collector-join.sh --update # move it to the version its CORE runs
./collector-join.sh --update --check # is it behind? report only, change nothing

--update asks the core, never a public release feed. A collector must match its own core, so “update to the newest release” is precisely wrong while you upgrade hosts one at a time — and a remote site can always reach its core (that is its whole job) while it often cannot reach the internet at all. The core reports its version on every authenticated answer and the daemon records it beside its identity, so this reads local state and opens no extra connection. It then pulls the matching image, swaps it in, waits for the collector to attach — and rolls back to the previous version if it does not come back, because a remote collector that fails to start is one nobody is standing next to.

Two more forms, for when the core cannot be consulted:

Terminal window
./collector-join.sh --update --version 1.2.8 # move to a version you name
./collector-join.sh --update --from ./image.tar # docker load an image tarball first

On an appliance there is no --update. The module installer never downloads anything — every image it can use is baked into the golden disk — so moving an appliance collector means having the matching image on that box and installing the module again with a fresh token, naming the image if it is not the appliance’s own version:

Terminal window
sudo env COLLECTOR_IMAGE=<image> taranac-module collector

The trap — ./taranac update does not move a collector

Section titled “The trap — ./taranac update does not move a collector”

On a collector host the bundle’s familiar ./taranac update refreshes the bundle files only — this script, the compose file, the docs — and leaves the container alone. That is deliberate, it says so as it finishes, and it points at collector-join.sh --update. A collector follows its core, not the newest release.

The rest of the wrapper follows the same rule. On a collector host ./taranac ps and ./taranac logs -f collector drive the collector’s stack, while the subcommands that need a database and an API — cluster …, unlock, reset-password, create-admin, installation-id, reset — refuse with an explanation and a list of what this host can do, instead of a wall of “variable is not set” warnings ending in “service api is not running”.

Symptom on the coreLikely causeFix
Status stuck UnknownCollector never enrolled / never startedRe-run the join; check the host’s outbound access to the core. If collector-join.sh exited non-zero, its message names the cause.
Join fails authenticating every poll--core missing the /api/v1 prefixThe signature covers the full path. Re-run with the prefix; the script warns when it looks absent.
Status Offline shortly after a core upgradeContract skew — the refused poll never reached the heartbeat./collector-join.sh --update. Its log will show version mismatch (409).
Status Offline after workingHost down, link down, or container stoppedRestart the collector; confirm outbound reachability. --status answers without the core.
Log says the core’s certificate changedThe core’s certificate was replaced — or something is impersonating itIf you replaced it, --reset-trust. If you did not, stop and find out why before re-pinning.
No work dispatched, nothing ingestedVersion mismatch (fail-closed)./collector-join.sh --update. It idles for every config it serves until the images agree.
Container reads unhealthy while the daemon looks aliveOnly a successful poll touches the liveness file — so a fail-closed or halted collector goes staleRead the log. version mismatch (409)--update; re-enroll required → a fresh token.
--update says already in step while nothing is collectedPre-1.2.7 core: it dropped its version header from the refusalRead the version on the core, then --update --version <ver>.
Uninstall refusesThe outbox still holds results the core never acceptedLet it drain, or --uninstall --force to accept the loss.
Collect now feels slow at one siteA middlebox is cutting the ~25 s held long-poll, so the collector fell back to its poll intervalAllow the idle connection through, or accept the slower cadence — collection itself is unaffected.
Collector reports re-enroll requiredIts identity was revoked, or sustained auth failuresIssue a fresh token and re-run the join; it won’t self-generate a new identity by design.
Collections auth-failWrong/again-locked device credentialCheck the source’s credential; auth failures aren’t retried (to avoid device lockouts).