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

TermWhat it means
PoolThe 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.
StrategyHow 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).
BackendWhere 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.

BackendWhat it meansWhen to use it
cloudflareAn 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.
meshA 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:

StatWhat it counts
PoolsHow many load balancers you have.
OriginsTotal members across all pools.
Modelive 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, or ip hash) and a backend (cloudflare or mesh).
  • 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 healthy member 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 healthy to 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_pools and lb_members database schema (migration 0005-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 by fleet.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() === false in the code, and of the modeled Mode 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.

  • Slots — the Node / Rack / Slot vocabulary the pool spreads traffic across.
  • Mesh — the private overlay that the mesh backend balances over.