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.
Workflow
atlasctl.yaml probes/snapshot.json
| |
| .-----------------------'
v v
atlasctl refresh fetch & cache all connected probes
|
v
atlasctl select score, rank, and assign probes to rounds
| (no API calls -- operates on local snapshot)
v
probe lists per round
|
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-round GeoJSON files for visual review in geojson.io or kepler.gl.
$ atlasctl select
Round high-freq: 30 probes across 30 H3 cells, 14 ASNs, 8 countries
Round mid-freq: 60 probes across 52 H3 cells, 22 ASNs, 12 countries
Round 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 ROUND 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, round) 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 rounds, measurements, and probe selection criteria.
# Rounds define frequency tiers. Each (measurement, round) pair becomes one
# RIPE Atlas measurement.
rounds:
- name: high-freq
count: 30
interval_seconds: 60
max_probes_per_cell: 1
- name: mid-freq
count: 60
interval_seconds: 300
max_probes_per_cell: 2
- name: low-freq
count: 100
interval_seconds: 900
max_probes_per_cell: 3
# Measurements define what to measure and which rounds to apply it to.
measurements:
- name: dns-canary
type: dns
target: canary.supabase.co
rounds: [high-freq, mid-freq, low-freq]
- name: tls-canary
type: tls
target: canary.supabase.co
rounds: [high-freq, mid-freq]
- name: ping-edge
type: ping
target: 162.159.36.1
rounds: [low-freq]
# Scoring controls which probes are preferred. All sections are optional.
scoring:
asn:
7018: 10 # AT&T
7922: 8 # Comcast
28573: 8 # Claro Brazil (incident history, sparse region)
5650: 6 # Frontier
tags:
office: 5
datacentre: 4
lte: 3
fibre: 2
cable: 2
home: 1
countries:
BR: 5 # sparse coverage, high incident relevance
HN: 8 # very sparse, boost hard
US: 1
DE: 2
stability:
system-ipv4-stable-90d: 5
system-ipv4-stable-30d: 3
# Hard exclusions. These probes 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 km2).
geo_diversity:
h3_resolution: 3
# City overrides relax the per-cell probe limit in specific areas.
# Coordinates are always explicit (no runtime geocoding).
cities:
- name: Ashburn
lat: 39.04
lon: -77.49
radius_km: 40
density_coefficient: 2.0
- name: Sao Paulo
lat: -23.55
lon: -46.63
radius_km: 60
density_coefficient: 3.0
- name: Frankfurt
lat: 50.11
lon: 8.68
density_coefficient: 0.7 # Frankfurt has too many probes
- name: Bellingham
lat: 48.7519
lon: -122.4787
radius_km: 40
score: 12 # raise score
- name: Berlin
lat: 52.520008
lon: 13.404954
radius_km: 70 # berlin is huge
score: -12 # downweight berlin
Probe selection
Probe selection is the central design problem. The RIPE Atlas probe pool has ~12,000 connected probes. atlasctl needs to pick a small subset that covers the right networks and geographies, assign them to frequency tiers, and keep those assignments stable as probes connect and disconnect over time.
Scoring
Every probe starts with a base score of 1. Matching scoring criteria add to the score. All criteria are additive. A probe matching multiple criteria accumulates the sum.
base: 1
asn (7018, AT&T): +10
tag (office): +5
tag (fibre): +2
stability (90d): +5
country (US): +1
---
total: 24
Bands and deterministic assignment
Pure score-sorted selection is fragile: one probe disconnecting can cascade across all three rounds. atlasctl avoids this by discretising scores into four bands, then using a deterministic hash of the probe ID as the tiebreaker within each band.
Sort key: (band DESC, FNV-1a(probe_id) ASC)
Band A score >= 15 High priority, multiple strong criteria
Band B score 8-14 Single strong criterion
Band C score 3-7 Moderate match
Band D score 1-2 Weak or base match only
A probe's band changes only when its score crosses a threshold (requiring a tag or ASN change on the probe itself, which is rare). Within a band, FNV-1a(probe_id) is perfectly stable because probe IDs are permanent integers. When a probe disconnects, only its slot opens. The next probe in hash order fills it. No cascade beyond that one slot.
Round assignment
Rounds are filled in order of decreasing frequency. A probe selected for high-freq is excluded from all subsequent rounds. Each round walks the sorted candidate list (after prior-round probes are removed) and fills slots until the round's count is reached.
high-freq 30 probes first responders, maximum geographic spread
mid-freq 60 probes regional coverage, fills ASN and tag diversity
low-freq 100 probes depth, denser coverage in priority areas
Geographic diversity
Two mechanisms work together to spread probes across the globe: continental interleaving and H3 cell limits.
Continental interleaving
The RIPE Atlas probe pool is heavily concentrated in the US and Europe. Left to pure score ordering, a round of 40 probes can easily fill all slots from those two regions, leaving Asia-Pacific, Latin America, and Africa unrepresented regardless of how scoring weights are tuned.
After scoring, probes are grouped into six continental zones:
| Zone | Countries |
|---|---|
| NA | United States, Canada |
| EU | Europe (including Russia and the Caucasus) |
| APAC | Asia-Pacific and Oceania |
| LATAM | Latin America and the Caribbean |
| MENA | Middle East and North Africa |
| SSA | Sub-Saharan Africa |
Within each band tier, the selection walk interleaves zones in round-robin order before any zone gets a second pick. The effect is easiest to see with an example. Suppose a round has these Band B candidates:
NA: [probe 1, probe 2, probe 3, probe 4, probe 5]
EU: [probe 6, probe 7, probe 8]
APAC: [probe 9]
LATAM: [probe 10, probe 11]
MENA: (none in Band B)
SSA: (none in Band B)
Interleaved order:
pass 1: NA-1, EU-6, APAC-9, LATAM-10
pass 2: NA-2, EU-7, LATAM-11
pass 3: NA-3, EU-8
pass 4: NA-4
pass 5: NA-5
The H3 cell filter then applies to this reordered list. Selecting 8 probes from the example above yields 3 from NA, 3 from EU, 1 from APAC, and 1 from LATAM, rather than 5 from NA and 3 from EU.
Band priority is fully preserved: Band A probes from any zone are exhausted before Band B probes from any zone enter the walk. Within a zone, probes are ordered by the same (band DESC, FNV-1a(probe_id) ASC) key as before, so within-zone assignment is stable across snapshot refreshes.
H3 cell limits
Each probe is mapped to an H3 hexagonal cell. The max_probes_per_cell limit per round prevents geographic clustering within a zone. City density overrides allow a higher limit in specific metros.
| Resolution | Avg cell area | Useful for |
|---|---|---|
| 2 | ~86,000 km2 | Large country region |
| 3 | ~12,000 km2 | State/province (default) |
| 4 | ~1,770 km2 | Metro area |
| 5 | ~253 km2 | City |
Detection cadence
| Event scope | Source | Effective cadence |
|---|---|---|
| Global outage | Round 1 (30 probes) | ~60s, multiple probes fail simultaneously |
| Regional (US, EU) | Rounds 1+2 (90 probes) | 60s from round 1 probes in region, 5m from round 2 |
| ISP-specific | Rounds 1+2+3 (190 probes) | Depends on probe count in that ASN |
| Single city | Mostly rounds 2+3 | 5-15 min |
Resource model
A managed measurement is identified by (measurement_name, round_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, round) 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>:<round_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. Any tool that can subscribe to the RIPE Atlas streaming WebSocket (wss://atlas-stream.ripe.net/stream/) can consume results using those IDs. atlas_exporter is one option: it exposes 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 subscribed to 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, round selection
plan/ state file, desired-vs-current diff, drift detection, apply
internal/
goatadapter/ adapts the goat library to pkg/ interfaces
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.
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. |