monotux.tech

Bird, BGP & Kubernetes

Kubernetes, BGP, Networking

I’m still fiddling around with Kubernetes in my homelab, and this time I wanted to use BGP to announce addresses in my router, but had a hard time finding a working configuration for BIRD. This entry describes a basic but working configuration for use in a homelab, with MetalLB & BIRD.

Table of Contents

Why? #

For homelab usage, this is not any better than just using layer 2 advertisements with MetalLB. In theory, you can use BGP to loadbalance between nodes, but that comes with it’s own set of limitations, and since I don’t run my lab with any real high-availability ambitions I’m pretty sure I don’t really gain anything from this.

I just wanted to test BGP. ¯\_(ツ)_/¯

Overview #

I’m running BIRD2 on my home router1, which will establish BGP sessions with my Kubernetes nodes. Kubernetes will then announce IPs using said sessions, and BIRD will export these routes to the routers routing tables, allowing us to reach said IPs.

BIRD #

This configuration is typically located at /etc/bird/bird.conf, but check your distributions documentation for the correct location.

This configuration is also problematic, as it accepts any IP announced from our Kubernetes cluster. You can implement IP filters in BIRD2, I’ve just been too lazy.

Make sure to change:

  • router id
  • local as, use a number from the private range, and change:
    • your router (one unique number), 65001 in the example below
    • your nodes (one number shared for all k8s nodes), 65010 in the example below
  • neighbor IP addresses for your k8s nodes
# I use my routers kubernetes subnet IP for id
router id 192.0.8.1;

protocol direct {
}

# Export all routes from BGP to the kernel routing table, but don't export
# kernel routes back to BGP/kubernetes
protocol kernel {
    ipv4 {
        import none;
        export all;
    };
}

template bgp k8s {
    local as 65001;
    source address 192.0.8.1;
    ipv4 {
        # Import all routes from BGP, don't export any of our routes back
        import all;
        export none;
    };
}

# Change peer addresses
protocol bgp node01 from k8s {
    neighbor 192.0.8.10 as 65010;
}

protocol bgp node02 from k8s {
    neighbor 192.0.8.11 as 65010;
}

protocol bgp node03 from k8s {
    neighbor 192.0.8.12 as 65010;
}

You can print out the bird route table using ths following command, and once it’s working properly announced IPs should look something like this:

# birdc 'show route'
BIRD 2.17.1 ready.
Table master4:
192.0.8.102/32  unicast [node01 2025-07-10] * (100) [AS65010i]
        via 192.0.8.10 on k8s
                unicast [node03 2025-07-10] (100) [AS65010i]
        via 192.0.8.12 on k8s
                unicast [node02 2025-07-10] (100) [AS65010i]
        via 192.0.8.11 on k8s
192.0.8.101/32  unicast [node01 2025-07-10] * (100) [AS65010i]
        via 192.0.8.10 on k8s
                unicast [node03 2025-07-10] (100) [AS65010i]
        via 192.0.8.12 on k8s
                unicast [node02 2025-07-10] (100) [AS65010i]
        via 192.0.8.11 on k8s
192.0.8.100/32  unicast [node01 2025-07-10] * (100) [AS65010i]
        via 192.0.8.10 on k8s
                unicast [node03 2025-07-10] (100) [AS65010i]
        via 192.0.8.12 on k8s
                unicast [node02 2025-07-10] (100) [AS65010i]
        via 192.0.8.11 on k8s
192.0.8.103/32  unicast [node01 2025-07-10] * (100) [AS65010i]
        via 192.0.8.10 on k8s
                unicast [node03 2025-07-10] (100) [AS65010i]
        via 192.0.8.12 on k8s
                unicast [node02 2025-07-10] (100) [AS65010i]
        via 192.0.8.11 on k8s

MetalLB #

Below is a example configuration for announcing address pools using MetalLB and BGP. For me (using Talos) it wouldn’t work unless I added the namespace labels to make the namespace privileged, this is why it’s included here.

apiVersion: v1
kind: Namespace
metadata:
  name: metallb-system
  # These can be omitted if you don't follow security recommendations :)
  labels:
    kubernetes.io/metadata.name: metallb-system
    pod-security.kubernetes.io/audit: privileged
    pod-security.kubernetes.io/enforce: privileged
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/warn: privileged
---
apiVersion: metallb.io/v1beta2
kind: BGPPeer
metadata:
  name: example-bgp
  namespace: metallb-system
spec:
  # CHANGE ME
  myASN: 65010
  peerASN: 65001
  peerAddress: 192.0.8.1
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: k8s-subnet
  namespace: metallb-system
spec:
  # CHANGEME
  addresses:
  - 192.0.8.100-192.0.8.200
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
  name: metallb-bgp-advert
  namespace: metallb-system
spec:
  ipAddressPools:
    - k8s-subnet

Conclusion #

This works on my cluster(tm).


  1. Which runs NixOSbtw, BIRD2 & nftables ↩︎