> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siderolabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MetalLB BGP

> Advertise MetalLB Service VIPs through Talos native BGP with FRR-K8s.

export const VersionWarningBanner = () => {
  const latestVersion = "v1.13";
  const [latestUrl, setLatestUrl] = useState(null);
  const [currentVersion, setCurrentVersion] = useState(null);
  const [isBeta, setIsBeta] = useState(false);
  const parseVersion = v => v.replace("v", "").split(".").map(Number);
  const isGreaterVersion = (a, b) => {
    const [aMajor, aMinor] = parseVersion(a);
    const [bMajor, bMinor] = parseVersion(b);
    if (aMajor > bMajor) return true;
    if (aMajor === bMajor && aMinor > bMinor) return true;
    return false;
  };
  useEffect(() => {
    if (typeof window === "undefined") return;
    const {pathname, hash, search} = window.location;
    const match = pathname.match(/\/talos\/(v\d+\.\d+)\//);
    if (!match) return;
    const detectedVersion = match[1];
    if (detectedVersion === latestVersion) return;
    setCurrentVersion(detectedVersion);
    if (isGreaterVersion(detectedVersion, latestVersion)) {
      setIsBeta(true);
    }
    const newPath = pathname.replace(`/talos/${detectedVersion}/`, `/talos/${latestVersion}/`);
    setLatestUrl(`${newPath}${search}${hash}`);
  }, []);
  if (!latestUrl || !currentVersion) return null;
  return <div className="not-prose sticky top-6 z-50 my-6">
      <div className="border border-yellow-500/30 bg-yellow-500/10 px-4 py-3 rounded-xl">
        <div className="text-sm">
          {isBeta ? <>
              ⚠️ You are viewing a <strong>beta version</strong> of Talos ({currentVersion}).
              This version may be unstable.
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View latest stable version {latestVersion} →
              </a>
            </> : <>
              ⚠️ You are viewing an older version of Talos ({currentVersion}).
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View the latest version {latestVersion} →
              </a>
            </>}
        </div>
      </div>
    </div>;
};

<VersionWarningBanner />

MetalLB can advertise Kubernetes Service virtual IPs (VIPs) to a VRF-bound Talos BGP instance.
Talos can selectively import those routes into its fabric instance and advertise them to the physical network.

This guide uses MetalLB in FRR-K8s mode.
FRR-K8s provides the extended next-hop capability required to exchange IPv4 VIP routes over an IPv6 BGP session on the veth connection.

## Prerequisites

Complete the following prerequisites before configuring the BGP peers:

1. Configure the [Talos workload-speaker topology](./overview#connect-a-kubernetes-workload-speaker-to-the-fabric) on each node that runs the MetalLB speaker.
2. Install MetalLB with FRR-K8s enabled and the in-pod FRR container disabled.
3. Ensure MetalLB speaker pods can use the host network and required privileges under your Pod Security configuration.

The following Helm values select FRR-K8s mode:

```yaml theme={null}
frrk8s:
  enabled: true
speaker:
  frr:
    enabled: false
```

Install MetalLB with those values:

```bash theme={null}
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm upgrade --install metallb metallb/metallb \
  --create-namespace \
  --namespace metallb-system \
  --values values.yaml
```

## Plan the peer values

The examples use these values:

| Purpose                 | Example value         |
| ----------------------- | --------------------- |
| Kubernetes node         | `worker-1`            |
| MetalLB namespace       | `metallb-system`      |
| Talos workload instance | `workload`            |
| Talos workload ASN      | `4200000002`          |
| MetalLB ASN             | `4200000003`          |
| MetalLB source address  | `fda1:b2c3:d4e5:1::`  |
| Talos VRF peer address  | `fda1:b2c3:d4e5:1::1` |
| Service VIP pool        | `203.0.113.0/24`      |

Replace these values with addresses, ASNs, and node names from your network design.
The peer addresses must be assigned to the corresponding veth endpoints described in the native BGP overview.

## Configure the address pool

Create the pool from which MetalLB assigns Service VIPs:

```yaml theme={null}
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: workload-vips
  namespace: metallb-system
spec:
  addresses:
    - 203.0.113.0/24
```

The Talos fabric instance must have an `importRoutes` selector that contains this pool.

## Configure the MetalLB peer

Create a MetalLB `BGPPeer` that initiates a session from the workload-facing veth address to the Talos VRF address:

```yaml theme={null}
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
  name: talos-workload
  namespace: metallb-system
spec:
  myASN: 4200000003
  peerASN: 4200000002
  peerAddress: "fda1:b2c3:d4e5:1::1"
  sourceAddress: "fda1:b2c3:d4e5:1::"
  dualStackAddressFamily: true
  nodeSelectors:
    - matchLabels:
        kubernetes.io/hostname: worker-1
```

`myASN` must match the Talos neighbor `peerASN`.
`peerASN` must match the Talos workload instance `localASN`.
`sourceAddress` is assigned to `veth-workload`, and `peerAddress` is assigned to the veth endpoint inside `vrf-workload`.

The Talos example configures the workload neighbor as passive, so MetalLB initiates this session.
If you change the session direction, update both configurations consistently.

`dualStackAddressFamily: true` allows the IPv6 transport session to carry IPv4 Network Layer Reachability Information (NLRI).

## Enable extended next hop

Add an `FRRConfiguration` for the selected node so FRR enables extended next-hop capability for the Talos neighbor:

```yaml theme={null}
apiVersion: frrk8s.metallb.io/v1beta1
kind: FRRConfiguration
metadata:
  name: talos-extended-next-hop
  namespace: metallb-system
spec:
  nodeSelector:
    matchLabels:
      kubernetes.io/hostname: worker-1
  raw:
    priority: 100
    rawConfig: |
      router bgp 4200000003
       neighbor fda1:b2c3:d4e5:1::1 capability extended-nexthop
      exit
```

Without extended next hop, an IPv6 BGP session cannot advertise the IPv4 Service VIPs used by this topology.
Keep the ASN and neighbor address synchronized with the `BGPPeer` resource.

## Configure the advertisement

Select the address pool and peer explicitly:

```yaml theme={null}
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
  name: workload-vips
  namespace: metallb-system
spec:
  ipAddressPools:
    - workload-vips
  peers:
    - talos-workload
  nodeSelectors:
    - matchLabels:
        kubernetes.io/hostname: worker-1
```

Use node selectors that match the Talos nodes where the veth, VRF, and workload BGP instance exist.
For high availability, configure the topology on each selected node and ensure the external fabric handles the resulting paths as intended.

## Advertise a service

Create a LoadBalancer Service and select the MetalLB pool:

```yaml theme={null}
apiVersion: v1
kind: Service
metadata:
  name: example-web
  annotations:
    metallb.io/address-pool: workload-vips
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
  selector:
    app: example-web
  ports:
    - name: http
      port: 80
      targetPort: 8080
```

`externalTrafficPolicy: Local` keeps traffic on a node with a local backend.
Ensure that MetalLB advertises the VIP only from nodes where your traffic policy can deliver it.

## Apply and verify the configuration

Apply the MetalLB resources:

```bash theme={null}
kubectl apply --filename metallb-bgp.yaml
```

Verify that the resources were accepted and inspect FRR-K8s status:

```bash theme={null}
kubectl --namespace metallb-system get ipaddresspools,bgppeers,bgpadvertisements,frrconfigurations
kubectl --namespace metallb-system get pods --output wide
```

On the selected Talos node, inspect the workload peer and imported fabric route:

```bash theme={null}
talosctl get bgppeerstatus --nodes <TALOS_NODE_IP> --output yaml
talosctl get routes --nodes <TALOS_NODE_IP> --output yaml
```

Because the workload instance uses `installRoutes: false`, the MetalLB VIP should not appear in the workload VRF or host main table on that node.
The imported path also remains out of the Linux FIB on the importing node; the fabric peer's advertised count should increase when the route is imported.

Use the [native BGP troubleshooting workflow](./overview#inspect-bgp-state) when the peer or route does not appear.
Inspect MetalLB speaker and FRR-K8s logs on the selected node:

```bash theme={null}
kubectl --namespace metallb-system logs \
  --selector app.kubernetes.io/component=speaker \
  --all-containers
kubectl --namespace metallb-system logs \
  --selector app.kubernetes.io/name=frr-k8s,app.kubernetes.io/component=frr-k8s \
  --all-containers
```

Check for a source-address mismatch, node-selector mismatch, peer ASN mismatch, missing extended-next-hop capability, or a Talos import prefix that does not contain the advertised VIP.
