Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 08:29
· 180 commits to main since this release
9787248

[0.5.0] - 2026-10-15

Upgrade notes / operator action required

  • beep must be the cluster's only L4 load balancer. Disable k3s servicelb
    and do not run MetalLB, kube-vip or a cloud LB controller alongside; two
    controllers would write status.loadBalancer.ingress on the same Services.
    kube-proxy must run in iptables or nftables mode. See deploy/README.md
    ("Required per-cluster configuration & gotchas").

  • The loader refuses to start on kernels older than 6.6 (TCX attach).
    Upgrade nodes to Linux 6.6 or newer before rolling out 0.5.0.

  • New beep-config ConfigMap (deploy/configmap.yaml) holds the sizing
    caps.
    Apply it before deploy/daemonset.yaml, which now loads it with
    envFrom (a missing ConfigMap fails the pod). BEEP_MAX_FRONTS (1024) and
    BEEP_ENDPOINTS_PER_FRONT (4) take effect on kubectl rollout restart ds/servicelb-controller; the front maps are recreated with their entries
    carried over and established flows survive. Every option, its default and
    memory cost is in deploy/README.md ("Configuration").

  • Controllers now keep a per-node Lease and prune dead nodes from Service
    ingress.
    Apply the updated deploy/rbac.yaml (new beep-leases
    Namespace holding the Role for coordination.k8s.io leases, kept out of
    kube-system so beep cannot touch control-plane leader election; with a
    CSR kubeconfig also bind it in beep-leases, see deploy/README.md) and deploy/daemonset.yaml (NODE_NAME
    env). The controller requires --node-name (env NODE_NAME). A node whose
    beep pod stops renewing (40 s duration, 10 s renew) is removed from every
    Service's status.loadBalancer.ingress within about a minute.

  • Pinned maps are recreated on upgrade, flushing conntrack. The loader
    deletes and recreates any pinned map whose type, key size, value size or
    max_entries differ from the new definition (one log line per map). This
    release changes FLOW_TABLE values from 36 to 40 bytes and FRONT_META
    from 8 to 12 bytes. Established flows are reset once.

  • FRONT_META and FRONT_ENDPOINTS are recreated on upgrade. The loader
    now also compares a pinned map's BPF_F_NO_PREALLOC flag, and both front
    maps change from preallocated to no-prealloc (and FRONT_META from 4096 to
    1024 default entries). A pin whose type, key size and value size are
    unchanged (flag or max_entries change only, e.g. this upgrade or a changed
    --max-fronts/--endpoints-per-front) has its entries copied into the new
    map before any program is attached, so established flows see no gap; if a
    cap shrink does not fit, whole fronts are kept in key order, the loss is
    logged with counts, and the controller reconciles the rest. A layout change
    (key/value size) recreates the map empty, and established flows through
    fronts reset until the controller's first reconcile. Entries the previous
    loader's programs write after the snapshot (new flows) are not carried over.

  • Breaking flag change. --front-meta-max-entries and
    --front-endpoints-max-entries are removed from beep and
    beep-controller. Use --max-fronts (default 1024, env BEEP_MAX_FRONTS)
    and --endpoints-per-front (default 4, env BEEP_ENDPOINTS_PER_FRONT):
    FRONT_META holds max-fronts rows and FRONT_ENDPOINTS
    max-fronts x endpoints-per-front x 2 (the 2 is swap headroom).

Fixed

  • A Service that leaves type: LoadBalancer (e.g. patched to ClusterIP) now
    has beep's entries removed from status.loadBalancer.ingress and its fronts
    removed from the dataplane; previously the stale ingress stayed.
  • The controller's MapRestoreFailed Warning Event (a failed map restore at
    upgrade) is verified live on k3s, and posting has an overall deadline so a
    slow apiserver cannot stall the controller. BEEP_FAULT_FAIL_RESTORE_WRITE=1
    makes the first restore write fail with ENOMEM to exercise it; test hook,
    never set it in production.
  • New connections were refused (dropped at the backend node) once 16 closed
    connections had left stale remap state behind: two Services sharing a pod
    and a client reusing one source port across both remap the second flow's
    backend-facing source port, and the reverse/memo rows of finished flows were
    only ever freed by LRU eviction. A TCP flow's rows are now marked closed on
    RST, or once a FIN was seen in both directions (a half-closed connection
    stays live). A closed row keeps forwarding, so a retransmitted FIN or final
    ACK still finds it, and a new connection may reuse its remap slot; the short
    (UDP-interval) idle sweep then reclaims it with its port-remap memo, and the
    sweeps reclaim the rest.

Added

  • Idle FLOW_TABLE GC, swept by the controller: flow rows carry a parity bit
    that the dataplane copies from a global parity (CONFIG) when a packet
    matches; every sweep interval the controller deletes the rows whose bit
    differs (with their port-remap memos), then flips the parity. Idle UDP flows
    go after 1-2 --udp-idle-sweep-secs (env BEEP_UDP_IDLE_SWEEP_SECS, default
    120); TCP flows that vanish without a close go after 1-2
    --tcp-idle-sweep-secs (env BEEP_TCP_IDLE_SWEEP_SECS, default 43200 = 12 h).
    Both are in the beep-config ConfigMap, as is BEEP_FLOW_TABLE_MAX_ENTRIES
    (--flow-table-max-entries, default 16384); see deploy/README.md
    ("Configuration"). The sweep runs on wall-clock interval boundaries and the
    parity survives a controller restart.

  • Front-map write failures are reported by cause: E2BIG (capacity), ENOMEM
    (kernel out of memory allocating a map element, transient) and other, each
    with its errno text. A front update that would not fit under the pinned
    map's max_entries is refused whole before any row is written, with a
    capacity error naming the map; fronts are applied shrinking first, then in
    key order, so the front that starves under pressure is deterministic. The
    front is left on its previous endpoints in every case.

  • Node-local endpoint preference: FRONT_META carries a local_count, and
    selection prefers local endpoints when it is non-zero. Inert until the
    controller sets local_count.

  • The controller emits every ready, non-terminating, same-family endpoint of a
    Service (previously one), with its target port and an IS_LOCAL flag, and
    honours Service policy:

    • externalTrafficPolicy: Cluster with no spec.trafficDistribution
      spreads uniformly over all ready endpoints.
    • spec.trafficDistribution: PreferSameNode prefers local endpoints and
      falls back to all; PreferClose and PreferSameZone are treated the same
      (beep has no zone model); an unknown value is logged and spreads uniformly.
    • externalTrafficPolicy: Local uses only endpoints on the ingress node
      (ignoring trafficDistribution); with no ready local pod it uses
      serving-but-terminating local pods, and with none at all the front is
      owned at count 0.
    • Each node's controller writes only its own address to
      status.loadBalancer.ingress; under externalTrafficPolicy: Local it
      removes it while the node has no usable local pod. Switching policy does
      not evict pinned flows; they drain naturally.

Changed

  • Owned fronts with no ready endpoints (count 0: eTP=Local with no usable
    local pod, 0-backend Services) now silently drop new flows instead of
    answering with a TCP RST / ICMP(v6) port unreachable, matching kube-proxy's
    hardened edge posture. Established and pending pinned flows still forward.
    A front with no FRONT_META row still passes to the host. The reject path
    (beep_common::reject) and its REJECT_BUCKET rate limiter are removed;
    the loader deletes a leftover REJECT_BUCKET pin on start.
  • The forward Geneve option is now 24 bytes and carries the target port.
    Decap reads the port from the option and no longer consults the front maps.
  • FRONT_META and FRONT_ENDPOINTS are BPF_F_NO_PREALLOC: memory is a
    bucket array (~16 B per slot of max_entries) plus ~96 B per front and
    ~128 B per endpoint row in use, instead of the full cap. At the 11-front
    smoke scale the two maps drop from 1.54 MB to ~0.15 MB.
  • The previous endpoint generation is deleted about 2 s after a swap commits
    (it was kept until the next swap). Only a packet that loaded the old
    FRONT_META microseconds before the commit can still want it; established
    flows read their conntrack pin. Steady state holds one generation per front.
  • The memory-smoke tripwire is 1.15x of the expected usage (bucket floors +
    entries x per-entry + the preallocated maps) instead of a flat 6 MiB, which
    stays as the gross ceiling.
  • The controller reads its kubeconfig through the politeia crate (git
    dependency pinned to v0.1.1); the in-tree beep-kubeconfig crate is gone.
    It parses only an X.509 client-cert kubeconfig file; see deploy/README.md
    ("Kubeconfig").

Known limitations

  • kube-proxy IPVS mode is unsupported: kube-proxy binds the published ingress
    addresses (including node addresses) to kube-ipvs0, which breaks node
    connectivity. Use iptables or nftables mode.
  • Minimum kernel is Linux 6.6; the loader fails fast on older kernels.
  • Supported Kubernetes: the latest two minor versions (currently 1.36 and
    1.37), rolling forward with upstream. Older versions are unsupported.