README
¶
atlasctl
Declarative management of RIPE Atlas measurements for Supabase external edge telemetry.
atlasctl applies the same workflow as Terraform or Pulumi to RIPE Atlas: write a config file describing the desired state, review a plan showing what will change, then apply the minimal set of operations. It selects probes, manages measurement lifecycles, detects drift, and maintains a state file mapping config identities to live measurement IDs.
Background
RIPE Atlas is a global network of roughly 12,000 hardware probes distributed across ISPs worldwide. Each probe runs active measurements (DNS, ping, TLS, traceroute) and reports results in real time. Supabase uses RIPE Atlas to detect failures that are invisible from our own infrastructure: DNS resolution problems at specific ISPs, TCP/TLS reachability issues from particular networks, and regional outages that Supabase's internal monitoring cannot see.
The challenge is operational. RIPE Atlas measurements are created individually, probe sets drift as probes connect and disconnect, and tracking which measurement IDs belong to which logical target requires bookkeeping. atlasctl handles all of that. The operator writes a config file and runs four commands. The tool does the rest.
Cohorts
A cohort is a named probe tier within a measurement. Each (measurement, cohort) pair becomes one RIPE Atlas measurement with its own permanent ID. This pairing is the core resource identity throughout the workflow, config, state file, and drift detection.
Cohorts within a measurement are filled in definition order. A probe selected for an earlier cohort is excluded from later cohorts within the same measurement. Different measurements select from the full probe pool independently, so the same physical probe can appear in cohorts across separate measurements.
Workflow
atlasctl.yaml probes/snapshot.json
| |
| .-----------------------'
v v
atlasctl refresh fetch & cache all connected probes
|
v
atlasctl select score, rank, and assign probes to cohorts
| (no API calls -- operates on local snapshot)
v
probe lists per cohort
|
v
atlasctl plan <---- state.yaml (measurement ID mappings)
| + RIPE Atlas API (live drift check)
v
changeset (create / update probes / stop / noop)
|
v
atlasctl apply ----> RIPE Atlas API (create, update, stop)
|
v
state.yaml (updated with new measurement IDs)
|
v
state.yaml (measurement IDs) ---> downstream consumers
select and plan are read-only. Only apply touches the RIPE Atlas API in a way that costs credits or modifies live measurements.
Subcommands
refresh
Fetches the full probe snapshot from the RIPE Atlas API and writes it to probes/snapshot.json. Run periodically (weekly or on demand) to pick up changes in the probe pool.
$ atlasctl refresh
Fetching probes... 12,847 connected probes across 24 pages
Snapshot written to probes/snapshot.json (18.2 MB)
Previous snapshot was 6 days old.
refresh has no side effects beyond writing the snapshot file. It does not touch measurements or state.
select
Runs the probe selection algorithm against the local snapshot and prints a coverage report. Optionally writes per-cohort GeoJSON files for visual review in geojson.io or kepler.gl.
$ atlasctl select
Cohort high-freq: 30 probes across 30 H3 cells, 14 ASNs, 8 countries
Cohort mid-freq: 60 probes across 52 H3 cells, 22 ASNs, 12 countries
Cohort low-freq: 100 probes across 71 H3 cells, 38 ASNs, 18 countries
Total: 190 unique probes
select makes no API calls. It is cheap to run repeatedly while iterating on scoring weights or city density overrides.
plan
Compares the desired state (config measurements with selected probes) against the current state (state file plus live RIPE Atlas API). Prints a human-readable diff and flags drift. Does not mutate anything.
$ atlasctl plan
KIND NAME cohort DETAILS
add dns-canary high-freq id=12345678 +2 probes
remove dns-canary high-freq id=12345678 -1 probes
noop dns-canary mid-freq id=12345679
noop dns-canary low-freq id=12345680
create tls-canary high-freq target=canary.supabase.co type=tls interval=60 probes=30
stop ping-old low-freq id=12345600
If the state file is absent (first run), plan treats all desired measurements as new creates.
plan also prints a projected credit burn based on the desired state: probe count, measurement type, and interval per (measurement, cohort) pair, rolled up to daily and weekly totals.
CREDIT BURN (projected)
NAME ROUND TYPE PROBES INTERVAL PER DAY
dns-canary high-freq dns 30 60s 432000
tls-canary high-freq tls 30 60s 432000
dns-canary mid-freq dns 60 300s 172800
dns-canary low-freq dns 100 900s 96000
tls-canary mid-freq tls 60 300s 172800
ping-edge low-freq ping 100 900s 28800
Total: 1334400/day 9340800/week
Credit costs are fixed by the RIPE Atlas platform: DNS and TLS cost 10 credits per result, ping costs 3, and traceroute costs 30. The projected total reflects the desired state after selection, not what is currently running.
apply
Executes the plan: creates new measurements, adds and removes probes on existing ones, and stops measurements no longer in config. Writes the updated state file after all changes complete. Prompts for confirmation before executing unless --yes is passed.
$ atlasctl apply
Creating tls-canary/high-freq...
msm 12345681 created (30 probes, 60s interval)
Updating dns-canary/high-freq (msm 12345678)...
added 2 probes, removed 1
Stopping ping-old/low-freq (msm 12345600)...
stopped
State written to state.yaml
Applied: 1 created, 1 updated, 1 stopped.
--dry-run logs what would happen without making any API calls.
Config
A single YAML file describes measurements and probe selection criteria.
Minimal cohort configuration
The only required fields per cohort are probe_count, max_probes_per_cell, and interval_seconds.
measurements:
- name: dns-canary
type: dns
target: canary.supabase.co
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
With no scoring config, every probe scores equally. The selection algorithm cycles through six continental zones (NA, EU, APAC, LATAM, MENA, SSA) in round-robin order and caps each H3 geographic cell at max_probes_per_cell probes. No tuning required.
Multiple cohorts per measurement
A measurement can have multiple cohorts. Each cohort becomes one RIPE Atlas measurement with its own ID. Cohorts are filled in definition order: a probe selected for an earlier cohort is excluded from later cohorts within the same measurement.
measurements:
- name: dns-canary
type: dns
target: canary.supabase.co
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
- name: low-freq
probe_count: 100
max_probes_per_cell: 3
interval_seconds: 900
high-freq gets the 30 best-available probes. low-freq gets the next 100 best remaining.
Pinning and excluding specific probes
Use include_probe_ids to force specific probes into a cohort regardless of scoring or H3 cell limits. Use exclude_probe_ids to prevent specific probes from appearing in a cohort. Both fields are per cohort.
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
include_probe_ids: [1001, 1002] # always selected; bypass H3 cap
exclude_probe_ids: [9999] # never selected in this cohort
Adding scoring
A cfg block on a cohort controls probe scoring. Higher-scoring probes are preferred within each continental zone. All scoring criteria are additive.
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
cfg:
asn:
7018: 10 # AT&T
7922: 8 # Comcast
tags:
office: 5
fibre: 2
countries:
BR: 5
US: 1
stability:
system-ipv4-stable-90d: 5
A probe matching ASN 7018, tagged office, in Brazil, with 90-day stability scores 1 (base) + 10 + 5 + 5 + 5 = 26.
Named presets
When multiple cohorts or measurements share the same scoring config, define named presets under cohort_configs and reference them with cfg_preset.
cohort_configs:
standard:
asn:
7018: 10
7922: 8
tags:
office: 5
fibre: 2
stability:
system-ipv4-stable-90d: 5
measurements:
- name: dns-canary
type: dns
target: canary.supabase.co
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
cfg_preset: standard
- name: tls-canary
type: tls
target: canary.supabase.co
cohorts:
- name: high-freq
probe_count: 30
max_probes_per_cell: 1
interval_seconds: 60
cfg_preset: standard
If both cfg_preset and cfg appear on the same cohort, the inline cfg wins as a complete replacement. There is no field-level merging.
Global settings
# Hard exclusions: probes with any of these tags are never candidates.
exclude_tags:
- broken
- system-flakey-connection
- system-flakey-power
- system-ipv4-doesnt-work
# H3 hexagonal grid resolution for geographic diversity (1-15, default 3).
# Resolution 3 gives cells roughly the size of a state or province (~12,000 km²).
geo_diversity:
h3_resolution: 3
For a complete reference of all configuration fields and the selection algorithm, see docs/selection-reference.md.
Probe selection
See docs/selection-reference.md for the complete config reference and algorithm walkthrough. See docs/bands-cohorts-explainer.md for a detailed explanation of how scoring bands, continental interleaving, and cohorts interact.
Every probe starts with a base score of 1. Per-cohort scoring config adds to that score. Scores are bucketed into four stability bands (A through D). Within each band, probes sort by a deterministic FNV-1a hash of the probe ID, making assignments stable across snapshot refreshes. Before slots are filled, probes are interleaved across six continental zones in round-robin order within each band, preventing the US and Europe-heavy pool from dominating small cohorts. The selection loop then walks the interleaved list and enforces the per-cell H3 limit.
The primary lever for coverage is probe_count, not scoring weights. A cohort of 6 or more probes will cover all six continental zones. Scoring controls which probe from each zone is preferred, not whether a zone is represented at all.
Resource model
A managed measurement is identified by (measurement_name, cohort_name). This pair maps to exactly one RIPE Atlas measurement ID.
config identity RIPE Atlas
-------------- ----------
dns-canary / high-freq --> msm 12345678
dns-canary / mid-freq --> msm 12345679
dns-canary / low-freq --> msm 12345680
tls-canary / high-freq --> msm 12345681
Structural attributes (target, measurement type, interval, address family) are immutable. Changing any of them stops the old measurement and creates a new one. The probe set is mutable: probes can be added and removed on a running measurement without recreating it. This is the common change path.
| Situation | Action | Credits |
|---|---|---|
New (name, cohort) in config, no existing msm |
Create measurement | Yes |
| Existing measurement, probe list changed | Add/remove probes | No |
| Existing measurement, structural attribute changed | Stop old, create new | Yes |
| Existing measurement, no changes | No-op | No |
| Running measurement not in config | Stop measurement | No |
Credit costs per result: DNS/TLS = 10, Ping = 3, Traceroute = 30. One-off measurements cost 2x periodic.
State tracking
state.yaml maps config identities to live measurement IDs. It is written by apply and read by plan.
measurements:
dns-canary:
high-freq:
msm_id: 12345678
target: canary.supabase.co
type: dns
interval: 60
probe_ids: [1001, 2002, 3003]
tls-canary:
high-freq:
msm_id: 12345681
target: canary.supabase.co
type: tls
interval: 60
probe_ids: [4004, 5005]
last_applied: 2026-07-07T14:30:00Z
probe_snapshot: probes/snapshot.json
Every measurement atlasctl creates includes a structured description tag:
[atlasctl:<measurement_name>:<cohort_name>]
This makes managed measurements discoverable via the RIPE Atlas API even if the state file is lost:
goat fm -my -status ong -descstarts "[atlasctl:"
Drift detection
plan compares the state file against the live API and flags discrepancies as warnings:
Orphan. A measurement on the API with our description tag that is not in the state file. Cause: manual creation, state file lost, or an apply that wrote to the API but crashed before saving state.
Ghost. A measurement ID in the state file that no longer exists on the API. Cause: manually stopped, credits exhausted, or deleted via the RIPE Atlas UI.
Drift is reported as warnings, not errors. The operator decides how to resolve.
Downstream pipeline
state.yaml records the measurement IDs of all active measurements. Those IDs are the hand-off point to whatever consumes results — see the RIPE Atlas measurements API for what is available per measurement ID.
The authors use atlasctl together with atlas_exporter to expose measurement results as Prometheus metrics.
In addition to managed measurements, RIPE Atlas has thousands of ongoing public measurements created by researchers and network operators. These can be consumed at no credit cost and are useful for incident correlation. If a managed canary fails from AT&T probes at the same time a public google.com measurement fails from the same probes, the failure is almost certainly network-level.
Constraints
- No HTTP measurements. RIPE Atlas supports ping, DNS, TLS, traceroute, and NTP only.
- Minimum measurement interval: 60 seconds per probe.
- All RIPE Atlas measurements are publicly queryable by design.
Package architecture
Domain logic lives in importable packages with no dependency on the CLI layer. This makes the packages usable by an external adapter (Terraform provider, Pulumi provider, or direct import) without pulling in cobra or any CLI concerns.
pkg/
config/ config loading and validation
snapshot/ probe cache: fetch, persist, load
selection/ probe scoring, band assignment, H3 diversity, cohort selection
atlasapi/ RIPE Atlas API wrapping
plan/ state file, desired-vs-current diff, drift detection, apply
cmd/
atlasctl/ CLI; thin wrappers over pkg/
The goat library (github.com/robert-kisteleki/goat) is the only dependency on the RIPE Atlas API. It is confined to internal/goatadapter. All pkg/ packages are tested with fakes and have no goat import.
Required API key permissions
Generate an API key at atlas.ripe.net/keys/ and enable the following permissions:
| Permission | Purpose |
|---|---|
| List your measurements | Read measurement state during plan and refresh |
| Show information about your probes | Resolve probe metadata during probe selection |
| Schedule a new measurement | Create measurements on apply |
Additional permissions may be required as provider capabilities expand.
Requirements
- Go 1.22 or later
RIPE_ATLAS_API_KEYenvironment variable (or--api-keyflag) forrefresh,plan, andapply- A RIPE Atlas account with sufficient credits for the measurements you intend to create
Getting started
# Build the binary.
go build -o atlasctl ./cmd/atlasctl
# Fetch the probe snapshot (run weekly or on demand).
atlasctl refresh --snapshot probes/snapshot.json
# Review the probe selection against your config.
atlasctl select --config atlasctl.yaml --snapshot probes/snapshot.json
# See what would change (no API mutations).
atlasctl plan --config atlasctl.yaml --snapshot probes/snapshot.json --state state.yaml
# Apply the changes.
atlasctl apply --config atlasctl.yaml --snapshot probes/snapshot.json --state state.yaml --yes
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
atlasctl
command
|
|
|
pkg
|
|
|
atlasapi
Package atlasapi implements the snapshot.Client and plan.ApplyClient interfaces against the live RIPE Atlas API.
|
Package atlasapi implements the snapshot.Client and plan.ApplyClient interfaces against the live RIPE Atlas API. |
|
plan
Package plan contains the declarative core of atlasctl: state file management, desired-vs-current diffing, and the apply logic that executes a changeset.
|
Package plan contains the declarative core of atlasctl: state file management, desired-vs-current diffing, and the apply logic that executes a changeset. |
|
selection
Package selection implements probe scoring, band assignment, and multi-round probe selection with H3 geographic diversity.
|
Package selection implements probe scoring, band assignment, and multi-round probe selection with H3 geographic diversity. |