Load Balancing
Spread one hostname across several Nodes with a health-checked pool, at the edge or over the mesh.
Load Balancing
A load balancer takes one public hostname — say app.example.com — and spreads its traffic across several Nodes instead of pinning it to one box. If a Node is unhealthy or you want to take it out of rotation, traffic shifts to the others. You get this in one click: pick the Nodes, name the hostname, choose how traffic is spread, and the platform models a pool with those Nodes as origins.
This page explains the model and is honest about what is live today versus what is modeled and waiting on execution wiring. For the Node / Rack / Slot vocabulary this builds on, read Slots; for the private overlay used by one of the two backends, see Mesh.
The vocabulary
| Term | What it means |
|---|---|
| Pool | The load balancer. It fronts one public hostname and spreads traffic across its members by a strategy, using a backend, probing each member on a health path. |
| Member (origin) | A Node behind the pool. Each has a port it serves on, a weight (relative share of traffic, 1 = even), and a status. |
| Strategy | How traffic is spread: round robin (even rotation), least conn (send to the Node with the fewest active connections), or ip hash (a client always lands on the same Node — sticky). |
| Backend | Where the balancing actually happens: cloudflare (an edge load balancer) or mesh (a Traefik front distributing over the private overlay). |
A member's status is one of healthy, draining, or down. Draining means the Node stops taking new traffic but finishes what it has — the graceful way to pull a Node out before maintenance.
The two backends
You choose one backend per pool.
| Backend | What it means | When to use it |
|---|---|---|
cloudflare | An edge load balancer: a health-checked origin pool with DNS steering at Cloudflare, in front of your Nodes. Traffic is balanced before it reaches your fleet. | Public-facing hostnames where you want Cloudflare's global edge, health checks, and DNS/CNAME routing at the control plane. |
mesh | A Traefik front Node distributing traffic across the members over the private overlay. | Internal or fleet-private balancing that rides the Mesh rather than the public edge. |
The Load Balancing page
The page lives in the app at /balancing. It is gated on the fleet.view privilege — without it, the page shows a locked state. Creating and managing pools additionally requires fleet.manage; with only fleet.view, the board is read-only.
At the top, three stats summarize your pools:
| Stat | What it counts |
|---|---|
| Pools | How many load balancers you have. |
| Origins | Total members across all pools. |
| Mode | live or modeled — see below. Today this reads modeled. |
Below the stats, the board lists each pool as a card showing its name, hostname, strategy, and backend, with a row per member. Each member row shows the Node name, port, weight, and status. In a fresh environment the board starts empty — there are no demo pools; nothing appears until you create one.
Creating a load balancer
The one-click flow is Create a load balancer, at the bottom of the page (visible only with fleet.manage). You provide:
- A pool name and the hostname it fronts (must be a valid hostname such as
app.example.com). - A strategy (
round robin,least conn, orip hash) and a backend (cloudflareormesh). - One or more Nodes to put behind the pool, selected from your fleet.
The platform then creates the pool and adds each chosen Node as a member. New members default to port 443, weight 1, and healthy status, and each pool probes members on the health path /healthz by default. If none of the chosen Nodes can be added, the pool is torn down so you are never left with an empty, hostname-less pool. Only real fleet Nodes are offered in the picker — demo/seed Nodes are filtered out.
Failover: draining and marking members
With fleet.manage, each member row on the board gives you failover controls:
- Drain a
healthymember to stop new traffic to it while existing connections finish — use this before maintenance or a deploy. - Restore a drained (or down) member back to
healthyto return it to rotation. - Remove a member from the pool entirely, or Tear down the whole pool.
These edits are recorded to the audit log and reshape the modeled pool. Automatic health-check-driven failover (a member flipping to down on its own when it fails the health probe) is part of the not-yet-wired execution layer below — today status changes are the ones you make.
What's live today, and what's next
The page is honest about its own state, which is why the Mode stat reads modeled rather than live.
Live now:
- The pool model — hostname, strategy, backend, health path, and members with port/weight/status (
control-plane/web/lib/lb/). - The
lb_poolsandlb_membersdatabase schema (migration0005-lb). - The read/manage board at
/balancing: create a pool across chosen Nodes, add or remove members, drain/restore a member, and tear down a pool — all gated byfleet.view/fleet.manage. - A dual backend: the live Postgres tables when the control plane is connected to its database, and an in-memory backend otherwise. Unlike the Slot Board and the Mesh board, the in-memory backend seeds empty — honestly, you have no pools until you create one.
Not wired yet (modeled, not live):
- Provisioning the real load balancer. Creating a pool models it and assigns origins, but the platform does not yet call the Cloudflare Load Balancer API or stand up the Traefik front — so traffic is not actually steered by a freshly-created pool, and members do not health-check themselves. This is the meaning of
lbLive() === falsein the code, and of themodeledMode stat.
When the Cloudflare-API / Traefik provisioning lands, the same board and the same Create a load balancer action will bring up real edge or mesh balancing and flip the Mode stat to live, with no change to how you use the page.