Edge

The fleet layer for a Multisite: several servers (edges) share one set of instances, any edge accepts any request, and a request for an instance another edge runs is forwarded there over the private network. Works with either runtime provider, pocker or docker.

Install · Configure · Use · Integrations · Example

Install

Edge is built into pocketone. It turns on in host mode (a provider in pocketone.yaml) when the file has an edges section, or a mothership section (Mothership), which lists the edges too.

In your own Go build, give the host a router as its forward hook:

edges, err := edge.FromSection(log) // the `edges` section; nil when there is none
if edges != nil {
	opts.Forward = edge.NewRouter(edge.RouterConfig{Edges: edges, Logger: log}).Forward
}
h, err := multisite.FromConfig(opts)

The module is github.com/pockethost/pocketone/ext/multisite-edge.

Configure

The edges section names this edge and lists the others (keys below):

edges:
  self: lax-1
  list:
    - { id: sgp-1, internal_url: "http://10.88.0.4:8080", region: sgp }
server:
  plain: [":8080"]   # where the other edges reach this one (their internal_url for it)

Each entry needs id and internal_url. An instance another edge runs names its owner in remote:

instances:
  - id: shop
    hosts: [shop.example.com]
    remote: edge:@sgp-1     # also accepted: edge:sgp-1, or sgp-1

Without a mothership, instances come from the instances file. With one, instances and edges both come from it; a fixed edges.list entry wins over the mothership's for the same edge.

pocketone.yaml reference

edges:

The fleet: this edge's id and a fixed list of the other edges (id, internal_url, region).

Key Type Default Description
self string This edge's id, such as lax-1.
list list of object The other edges: id, internal_url (where this edge reaches it) and optional region.

Use

There's nothing to run by hand. A load balancer sends visitors to any edge, and each request ends up on the edge that runs its instance:

  • Owner healthy: the request is forwarded to its internal_url, with the original hostname. An edge is healthy when its source heard from it in the last 30 seconds, or else when it answers /_pocker/health (an edge the source marks offline never is).
  • Owner briefly unreachable but healthy: requests are held and retried every 250 ms until it answers. Only requests without a body (GET, HEAD, OPTIONS) are replayed after a failed connection.
  • Owner down or unknown: a 503 with Retry-After: 10, at once.
  • Never bounced: a request another edge already forwarded is never forwarded again; it gets a 421.
  • CORS: for an instance with access.cors_origins, the entry edge answers preflights and sets the only Access-Control-Allow-Origin the browser sees.
  • Headers: where both edges set the same header (such as the firewall's rate-limit headers), the owner's wins.

Integrations

Works with

Extension How they fit
Multisite Edge fills the host's forward hook for instances with remote set.
Pocker, Docker Either runs the instances this edge owns; an app already running here stops when its instance moves to another edge.
Firewall The entry edge checks the peer and the instance's rate limits before forwarding.
Mothership Its mirror is an edge source: it follows the mothership's list of edges and their heartbeats.

Building on Edge

  • Edge sources. Implement edge.EdgeSource (Self, Resolve, Edges) to say where the other edges are; add Run (edge.Runner) for background work. YAMLProvider is the edges.list one; MultiProvider combines several, the first to resolve an instance's remote winning.
  • Router. edge.NewRouter(edge.RouterConfig{...}) takes the health path, healthy window, retry interval and the 503 and 421 messages. Plug Router.Forward into multisite.Host.Forward.
  • Timing. edge.WithMark(ctx) records which edge a request was forwarded to and when, e.g. for Server-Timing.

Example

Edge has no example program of its own. The stock binary wires it up in examples/pocketone/multi.go, together with the mothership's edge list.

License

MIT