
Networking
Bare-Metal Ingress, Owned: MetalLB, kube-vip, and Gateway API
Run a Kubernetes bare-metal load balancer without a cloud provider — MetalLB BGP, kube-vip control-plane VIP, and Envoy Gateway API, exiting Cloudflare's proxy.
On a managed cluster, type: LoadBalancer is a lie you are happy to believe. You write four lines of YAML, the cloud-controller-manager provisions an external load balancer, an IP appears, and traffic flows. The mechanism — health-checked anycast VIPs, the provider's edge network, DDoS scrubbing at their border — is invisible and, crucially, not yours. Move that same cluster onto bare metal in your own rack or colo and the illusion collapses: a LoadBalancer service sits in <pending> forever — no cloud controller answers the request. This is the moment most on-premises Kubernetes projects discover that ingress was never solved — it was rented.
The good news is that the sovereign replacement is not one tool but a clean separation of three concerns, each with a mature open-source implementation. This article wires them together for production: MetalLB for advertising service IPs to your network fabric, kube-vip for the control-plane virtual IP that bootstraps and protects the API server, and the Gateway API — with Envoy Gateway as the data plane — for L7 routing that finally replaces the sprawling annotations of the Ingress era. Then it does the part most guides skip: walking off Cloudflare's orange-cloud proxy honestly, naming exactly what you gain, what you now own, and what you must not pretend you can ignore.
This is a companion to the cloud repatriation playbook: repatriating compute is the easy half; replacing the cloud's load balancer is the half that stalls migrations. It is distinct from in-cluster networking — Cilium and eBPF govern east-west pod traffic; what follows is strictly the north-south problem of getting external traffic to the cluster edge.
Three Layers, Not One Tool: What Bare-Metal Ingress Actually Requires
The single most common mistake in on-premises ingress design is treating it as one problem with one answer. It is three, and conflating them produces the classic failure where two controllers fight over the same service IP and traffic flaps. The concerns are: (1) a control-plane VIP — a stable address for the Kubernetes API server that survives the loss of any one control-plane node; (2) service IP advertisement — making a type: LoadBalancer address reachable from the physical network, which on bare metal means answering ARP for it or announcing a route to it; and (3) L7 routing — terminating TLS and dispatching HTTP requests to the right backend by host and path. Cloud providers fuse these into one opaque product. On bare metal you assemble them, and the assembly is where the sovereignty lives.
The mapping is clean once the concerns are separated. kube-vip owns layer one. MetalLB owns layer two. A Gateway API implementation — Envoy Gateway or Contour — owns layer three. They are composable precisely because they operate at different points in the packet's life: kube-vip and MetalLB put an IP on your network; the Gateway decides what to do with the bytes that arrive at it. The diagram below shows the three planes inside a single autonomous system — the mental model for the rest of this article.
kube-vip: The Control-Plane VIP That Bootstraps the Cluster
The control-plane VIP is the chicken-and-egg problem of self-hosted Kubernetes. Your kubeconfig and every kubelet point at a single API-server address, but you have three control-plane nodes and no cloud load balancer to sit in front of them. kube-vip solves this by running as a static pod on each control-plane node and electing a leader through a Kubernetes lease; the leader owns the VIP. In ARP/Layer 2 mode, the leader emits gratuitous ARP for the VIP so the switch fabric routes control-plane traffic to it; when the leader dies, the next node wins the lease and takes the address within seconds. In BGP mode, each node peers with your top-of-rack routers and announces the VIP as a /32, so the routers ECMP across live nodes and reconverge on failure.
kube-vip is the default control-plane load balancer in k3s, RKE2, and Cluster API, and it is what Talos Linux ships for API-server HA — so if your nodes already run an immutable OS, you likely have kube-vip whether you configured it consciously or not. A minimal ARP-mode manifest for a VIP at 10.0.0.10 looks like this:
# kube-vip static pod (control-plane VIP, ARP/L2 mode).
# Runs on every control-plane node; the lease leader owns 10.0.0.10.
apiVersion: v1
kind: Pod
metadata:
name: kube-vip
namespace: kube-system
spec:
hostNetwork: true # must see the host interface to send gratuitous ARP
containers:
- name: kube-vip
image: ghcr.io/kube-vip/kube-vip:v0.8.9
args: ["manager"]
env:
- { name: vip_interface, value: "eth0" }
- { name: address, value: "10.0.0.10" } # the control-plane VIP
- { name: port, value: "6443" }
- { name: vip_arp, value: "true" } # ARP/L2 mode
- { name: vip_leaderelection, value: "true" } # HA via Kubernetes lease
- { name: cp_enable, value: "true" } # manage the control-plane VIP
securityContext:
capabilities:
add: ["NET_ADMIN", "NET_RAW"] # required to manage the interface + ARPMetalLB: Advertising Service IPs to Your Physical Network
MetalLB is the component that makes type: LoadBalancer stop returning <pending>. It watches LoadBalancer services, allocates each an address from a pool you define, and advertises it in one of two modes. Layer 2 mode needs no router configuration: one elected node answers ARP (IPv4) or NDP (IPv6) for the service IP, so all traffic funnels through a single node before kube-proxy or Cilium spreads it internally. That is why it dominates homelabs and small clusters — but the single-node funnel caps throughput and makes failover an ARP-cache-timing question.
BGP mode is the production answer. Every node runs a MetalLB speaker that peers with your top-of-rack routers and announces each service IP as a /32 route. The routers then ECMP-hash flows across all advertising nodes, so ingress bandwidth scales with node count and failover is a routing reconvergence rather than an ARP gamble. As of MetalLB 0.15, the recommended BGP backend is FRR-K8s — the legacy frr mode is deprecated and the built-in native speaker is feature-limited. FRR-K8s runs FRRouting as a managed backend and, critically, lets MetalLB *share* BGP sessions with other actors on the node: if Cilium or Calico is already peering with your routers for pod-network routes, FRR-K8s merges MetalLB's service advertisements into the same sessions instead of fighting over them.
# MetalLB BGP mode with the FRR-K8s backend (MetalLB 0.15+).
# 1) The pool of externally-routable IPs MetalLB may hand out.
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: prod-services
namespace: metallb-system
spec:
addresses:
- 198.51.100.16/28 # RFC 5737 documentation range; use your own routable block
autoAssign: true
---
# 2) The BGP peer — your top-of-rack router.
apiVersion: metallb.io/v1beta2 # v1beta1 BGPPeer is deprecated; use v1beta2
kind: BGPPeer
metadata:
name: tor-router
namespace: metallb-system
spec:
myASN: 64512 # the cluster's AS
peerASN: 64512 # iBGP here; use a different ASN for eBGP
peerAddress: 10.0.0.1 # ToR router address
# bfdProfile: sub-second failure detection, defined separately
---
# 3) Advertise the pool over BGP as /32 host routes (ECMP across nodes).
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: prod-advert
namespace: metallb-system
spec:
ipAddressPools: [prod-services]
aggregationLength: 32 # advertise each service IP as a distinct /32BGP mode also changes your security posture: your nodes are now BGP speakers, and a misconfigured or compromised node can advertise routes it should not. Constrain this with router-side prefix filters that accept only your allocated service block from the cluster's AS, BGP session authentication (TCP-MD5 or the newer TCP-AO), and — with FRR-K8s — the advertisement config kept in Git, so the announced prefixes are reviewable rather than discovered by tcpdump at 3 a.m. That is the difference between a network you operate and one that operates you.
kube-vip vs MetalLB: Why You Often Want Both
kube-vip can *also* provide service load balancing, not just the control-plane VIP — so why run two tools? Because they optimize for different things. kube-vip's service LB is lightweight and convenient — ideal when the control plane and services share one simple network. MetalLB's BGP mode is the more capable service-LB engine: mature address-pool management, BFD, node selectors, and the FRR-K8s session-sharing that matters when a BGP-speaking CNI is in play. The common production pattern: kube-vip for the control-plane VIP, MetalLB for the data-plane service IPs.
Gateway API vs Ingress: The Role Separation Worth Rewiring For
With a service IP now reachable, the last layer is L7 routing, and here the platform has genuinely moved. The Ingress API served for years but aged badly: every non-trivial capability — TLS options, path rewrites, header manipulation, canary weighting — lived in controller-specific annotations, so an Ingress manifest was portable in theory and locked to nginx-ingress or Traefik in practice. The Gateway API replaces it with typed, role-oriented resources. Its core resources — GatewayClass, Gateway, and HTTPRoute — reached GA in v1.0 back in October 2023; the 2026 milestone is v1.5 (released 27 February 2026), which promoted six previously-experimental features to the Standard channel and moved the project to a predictable release-train cadence. Gateway API is no longer the risky new thing; it is the stable default that Ingress is now legacy against.
The reason to rewire is the role separation, which maps onto how platform teams actually divide responsibility. The GatewayClass is infrastructure — chosen once, like a StorageClass. A Gateway is platform-owned: it declares listeners, ports, and TLS certificates, and it is the resource that binds to your MetalLB-advertised IP. HTTPRoute objects are app-owned: they attach to a Gateway and declare host/path matching and backends, with no privilege to change the listener or certificate. This is the golden-path model expressed in the API itself — application teams route their own traffic without a platform-team ticket, and cannot misconfigure the shared edge.
# Gateway (platform-owned): binds to the MetalLB service IP, terminates TLS.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-edge
namespace: gateway-system
spec:
gatewayClassName: envoy-gateway
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
certificateRefs:
- name: prod-tls # cert-manager-issued secret
allowedRoutes:
namespaces:
from: Selector # only namespaces the platform labels may attach
selector:
matchLabels: { gateway-access: "true" }
---
# HTTPRoute (app-owned): attaches to the Gateway, routes by host + path.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: checkout
namespace: shop
spec:
parentRefs:
- name: prod-edge
namespace: gateway-system
hostnames: ["shop.example.com"]
rules:
- matches:
- path: { type: PathPrefix, value: /checkout }
backendRefs:
- name: checkout-svc
port: 8080That HTTPRoute weighting is also what powers progressive delivery: shifting 5% of traffic to a canary is a backendRefs weight change the application team makes directly, with no annotation dialect and no controller-specific behavior. And because TLS terminates at the Gateway against a cert-manager-issued secret, your certificate authority stays yours — pair this with a sovereign internal PKI and no private key ever leaves infrastructure you operate.
Envoy Gateway vs Contour: Choosing the Data Plane
The Gateway API is a specification; you still choose an implementation to run the proxies. On bare metal the two mature CNCF-aligned Envoy-based choices are Envoy Gateway and Contour. Contour maps one controller to one fleet of Envoy instances, has a long track record as an ingress controller, and by v1.33 implements Gateway API v1.3 including TLSRoute, TCPRoute, GRPCRoute, and BackendTLSPolicy. It is the conservative, battle-tested pick, especially when migrating from an existing Contour Ingress deployment.
Envoy Gateway is the Envoy project's own Gateway API implementation, built on Envoy's xDS dynamic configuration, and the more forward-leaning choice. It is the only major implementation with native declarative rate limiting via CRD — which matters enormously for the Cloudflare-exit story below — and its v1.5 release cut memory roughly 25% by consolidating its xDS runners, and added an admin console, client-certificate validation in ClientTrafficPolicy, and zone-aware routing. For a greenfield sovereign edge where you want WAF-like controls and rate limiting inside the cluster rather than at a vendor's border, Envoy Gateway is the stronger default; Contour is the safer incremental migration. Either way the Gateway API resources you author are identical, so switching implementations is a gatewayClassName change, not a rewrite — the optionality is built into the standard.
Past Cloudflare: What You Gain, and What You Now Own
Exiting Cloudflare's proxy is where this stops being a wiring exercise and becomes an engineering-sovereignty decision. Understand what the orange cloud does when a record is proxied: resolvers get Cloudflare's anycast IPs, browsers connect to its edge, and Cloudflare terminates TLS, runs its WAF, absorbs volumetric DDoS, caches assets, and hides your origin IP — then forwards to you. Flip the record to DNS-only and every one of those functions reverts to you, along with the traffic. This is not a checkbox; it is a set of responsibilities you choose to repatriate — and pretending otherwise is how self-hosting projects earn a bad reputation.
Here is the honest reassignment. WAF and rate limiting move into Envoy Gateway: its declarative rate-limit CRD and SecurityPolicy cover the abuse controls Cloudflare's rules provided, and live in Git as reviewable YAML rather than a vendor dashboard. TLS moves to cert-manager at the Gateway, so you hold the private keys. Caching / CDN becomes an in-cluster concern — a Varnish or nginx layer, or a genuinely self-hosted CDN — right for dynamic and regional traffic, but no substitute for a global anycast edge serving a worldwide static audience. Volumetric DDoS is the one function you cannot fully self-host: absorbing hundreds of gigabits needs upstream capacity. The sovereign answer is transit-level scrubbing (Akamai Prolexic, or Cloudflare's BGP-based Magic Transit, which protects your prefixes without proxying HTTP) plus BGP remote-triggered blackholing with your upstreams — network engineering, not a toggle.
The Exit Ramp: Keeping Cloudflare Optional, Not Mandatory
The sovereign move is rarely to burn the bridge; it is to make the bridge optional. This architecture keeps every external dependency swappable. Cloudflare stays a DNS provider — a commodity you can move in an afternoon — rather than a proxy welded into your request path. The Gateway API resources are vendor-neutral by specification, so Envoy Gateway and Contour are interchangeable behind them. MetalLB and kube-vip announce over standard BGP and ARP, protocols your routers already speak, with no proprietary control plane. If you later want a scrubbing provider in front for a launch or an attack window, Magic Transit or Prolexic slot in at the BGP layer without touching a single Kubernetes manifest.
Contrast that with the position you started from, where type: LoadBalancer bound you to one cloud's controller and Cloudflare's proxy sat inline for every request. The difference is not that the sovereign stack has no dependencies — it is that each dependency is now a component you chose, at a layer you understand, replaceable in isolation. That is what optionality means in practice: not the absence of vendors, but the absence of vendors you cannot leave.
The Long Game: A Request Path You Can Read in Five Years
The deepest argument for this architecture is legibility over time. When an engineer joins in three years and asks how a request reaches the checkout service, the answer is a readable chain: a DNS record resolves to a service IP; MetalLB advertises that /32 over BGP and the ToR routers ECMP it to a node; the node hands it to an Envoy proxy; an HTTPRoute matches the host and path and forwards to the backend Service. Every hop is an object in Git or a route in a router config — inspectable, diffable, and owned. There is no vendor console to screenshot, no ticket to file to learn why traffic went somewhere, no opaque edge whose behavior changes when the provider ships a release you did not ask for.
That legibility is the compounding asset. The cloud repatriation case is usually argued on cost, and that case is real. But the durable return is an ingress path your team understands completely and can operate for a decade without renting the parts that matter. MetalLB, kube-vip, and the Gateway API are not a downgrade from the managed load balancer you left behind. They are the same capability, disaggregated into layers you can see — which is the whole point of doing it yourself.
§FAQ/Common questions
Frequently asked
Can I run a Kubernetes bare metal load balancer without any cloud provider at all?
Yes. MetalLB provides the type: LoadBalancer capability that the cloud-controller-manager gives you on managed clusters, advertising service IPs to your physical network over Layer 2 (ARP/NDP) or BGP. Combined with kube-vip for the control-plane VIP and a Gateway API implementation for L7 routing, you get a complete ingress stack with no cloud dependency. All three are open-source and run entirely inside your own infrastructure.
Should I use MetalLB in Layer 2 mode or BGP mode?
Layer 2 mode needs no router configuration and works on any flat network, but funnels all traffic for a given service through one elected node, capping throughput and making failover depend on ARP cache timing. BGP mode has every node advertise service IPs as /32 routes to your top-of-rack routers, which ECMP-balance across nodes — so bandwidth scales with node count and failover is routing reconvergence. Use Layer 2 for homelabs and small clusters; use BGP mode with the FRR-K8s backend for production.
Do I need both kube-vip and MetalLB, or can one do everything?
kube-vip can provide both the control-plane VIP and service load balancing, and for a simple single-network cluster that may be enough. But MetalLB's BGP mode is the more capable service-LB engine — mature address-pool management, BFD, and FRR-K8s session sharing with a BGP-speaking CNI. The common production pattern is kube-vip for the control-plane VIP and MetalLB for data-plane service IPs. If you run both, set spec.loadBalancerClass on your Services (Kubernetes 1.24+) so the two controllers do not fight over the same IP.
Is the Gateway API ready to replace Ingress on bare metal in 2026?
Yes. The core Gateway, GatewayClass, and HTTPRoute resources reached GA in v1.0 in October 2023, and v1.5 (February 2026) promoted six more features to the Standard channel and adopted a release-train cadence. Mature bare-metal implementations exist — Envoy Gateway and Contour are both Envoy-based and CNCF-aligned. The role separation (platform owns the Gateway, apps own HTTPRoutes) and vendor-neutral spec make it the stable default; Ingress is now the legacy path.
What do I lose by exiting Cloudflare's proxy, and can I get it back in-cluster?
Proxying gives you edge WAF, rate limiting, caching/CDN, TLS termination, DDoS absorption, and origin-IP privacy. Going DNS-only, most of these move into the cluster: WAF and rate limiting into Envoy Gateway's SecurityPolicy and rate-limit CRDs, TLS into cert-manager, caching into an in-cluster layer. The two you cannot fully self-host are a global anycast CDN edge and volumetric DDoS absorption — for those, use transit-level scrubbing (Magic Transit or Prolexic) at the BGP layer plus RTBH with your upstreams. Origin-IP privacy is gone, so harden the edge with default-deny firewalling first.
Further reading
- Cloud repatriation: the Kubernetes on-premises engineering playbook
- eBPF and Cilium: zero-trust in-cluster networking without sidecars
- Talos Linux: the immutable Kubernetes OS security case (ships kube-vip)
- Progressive delivery with Argo Rollouts: canary and blue-green via Gateway API
- Golden paths and policy enforcement: the platform-team model
- Your own ACME: a sovereign internal PKI with step-ca and cert-manager
- Bare-metal Kubernetes provisioning: Metal3, Tinkerbell, Sidero, Omni
- The sovereignty thesis
- Infrastructure and platform capabilities
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.