Platform: Datastore (rqlite)

Datastore (rqlite)

Nexus GSLB stores all data-plane objects — pools, members, services, health checks, geo rules, redirects, TSIG keys, TXT records, users, tenants, roles, settings — in rqlite, a Raft-replicated SQLite cluster. rqlite handles cross-node replication; there is no NATS DB sync and no file-copy replication.

This is distinct from the other two config layers:

  • Configuration (YAML) is owned by git, applied per-node via GitOps self-restart.

  • Ephemeral cluster state (health, heartbeats, RTTs, sessions) flows over NATS with TTLs.

Two processes per node

Each node runs two services:

Process

Role

rqlited

The rqlite datastore. Holds the SQLite store + Raft log; replicates across nodes. A third-party binary, not built from this repo.

gslbd

The GSLB daemon. Connects to the localrqlited over its HTTP API (localhost:4001).

systemd ties them together: gslbd.service has Requires= + After=rqlited.service, and gslbd waits for a Raft leader on startup before serving.

gslbd talks to rqlite via the gorqlite client through a custom database/sql driver (internal/storage/rqlite_driver.go). The driver normalises rqlite's JSON number encoding (float64) back to int64 so timestamp/integer scans are exact. The whole stack is CGO-free (CGO_ENABLED=0).

Foreign keys are required

rqlited must run with the -fk flag. The schema relies on ON DELETE CASCADE (members/health_checks/geo_rules → pools, users → tenants); without -fk those cascades silently no-op. The shipped deploy/rqlited.service sets -fk.

Configuration

gslbd's rqlite config block tells it how to reach the local rqlited:

rqlite: httpAddr: "localhost:4001" # matches rqlited -http-addr connectTimeout: "10s" # startup connect + leader-wait budget dataDir: "/var/lib/gslbd/rqlite" joinAddrs: [] # peers for a joining node (empty = standalone) # tls: # HTTPS to rqlited (optional) # enabled: true # caFile: /etc/gslb/rqlite-ca.pem # certFile: /etc/gslb/rqlite-client.pem # keyFile: /etc/gslb/rqlite-client-key.pem # auth: # username: gslbd # password: change-me

Cluster membership (node id, bind/advertised addresses, join targets) is configured on rqlited, not here — see deploy/rqlited.env.

Cluster bootstrap and join

rqlited node settings live in /etc/gslb/rqlited.env (read by rqlited.service). Nodes form one Raft cluster over the private network (e.g. WireGuard).

First node (bootstrap) — no join target; bind/advertise on the address peers reach it on:

RQLITE_NODE_ID=lon-01RQLITE_HTTP_ADDR=10.8.0.1:4001RQLITE_RAFT_ADDR=10.8.0.1:4002

Additional nodes (join) — point at an existing peer's Raft address (port 4002, bare host:port; v8+/v10 reject http:// URLs and the HTTP port for -join). A joining node wipes its own local data and adopts the leader's, so bootstrap the node holding the data you want to keep and join the rest to it.

RQLITE_NODE_ID=eu-01RQLITE_HTTP_ADDR=10.100.0.2:4001RQLITE_RAFT_ADDR=10.100.0.2:4002RQLITE_EXTRA_FLAGS=-join 10.100.0.1:4002 # peer RAFT addr, not http://, not :4001

Firewall: ports 4001 (HTTP) and 4002 (Raft) must be reachable between peers on the cluster interface. With ufw active, allow them from the mesh subnet only — ICMP reachability is not sufficient, the Raft TCP port must be open both ways or the leader loses quorum:

ufw allow from 10.100.0.0/24 to any port 4001 proto tcpufw allow from 10.100.0.0/24 to any port 4002 proto tcp

Apply with systemctl restart rqlited. Verify membership (expect all nodes reachable: true, one leader: true):

curl -s localhost:4001/nodes | jq .curl -s localhost:4001/status | jq .store.raft

A single-node install needs nothing beyond the defaults — rqlited bootstraps a one-node Raft cluster on localhost.

Backups

gslbd backs up the database by calling rqlite's /db/backup endpoint, which streams the full SQLite database from the cluster leader to the local gslbd host (written atomically: temp + rename). This replaces the old file-copy/VACUUM INTO backup, which is meaningless in a clustered deployment. Configure under backup: (see Configuration).

Local development / testing

rqlite ships no macOS binaries — use Docker for local/CI work:

docker run -d -p 4001:4001 -p 4002:4002 rqlite/rqlite \ -fk -http-adv-addr localhost:4001 -raft-adv-addr localhost:4002

The -*-adv-addr localhost flags are required for a container: without them rqlite advertises its internal container hostname, which the gorqlite client (following leader redirects) cannot resolve from the host. Storage/API/DNS integration tests connect to RQLITE_TEST_ADDR (default localhost:4001) via internal/storage/storagetest and skip when no server is reachable.

Code references

  • internal/storage/rqlite.go — DB, Open(Options), schema migration, leader-wait.

  • internal/storage/rqlite_driver.go — the database/sql driver over gorqlite.

  • internal/storage/testsupport.go, internal/storage/storagetest/ — test helpers.

  • deploy/rqlited.service, deploy/rqlited.env — the datastore unit + node config.


Was this article helpful?