MetalLB, Calico, BGP, and LoadBalancer IPs

This post will cover exposing only your LoadBalancer service IP's to your BGP Peer.

Problem statement

Configure MetalLB and Calico to expose your LoadBalancer services to your upstream BGP peered router

Summary

Installing MetalLB is easy, so I'm not going to cover that, I will however, cover the configuration of it and Calico so that it will only expose only the IP's associated with your LoadBalancer services to your BGP peers.

According to the docs, in theory, setting disableBGPExport to true should do what you want, however, that has an unintended side effect. Traffic destined between your pods in your cluster will go out through your default gateway which makes it so pods can't communicate with one another nor the public internet. If you use Linux as your gateway, you may see kernel entries stating IPv4 martian source.

I'm going to show manifests using the 192.168.3.0/24 subnet for your LoadBalancer services, 64512 as your autonomous system ID, and 192.168.0.1 as your peer address. You'll want to update those values to fit your network accordingly. My examples also work for local as your externalTrafficPolicy.

I am not going to cover configuring your upstream router, that is very dependent on what you use and how your network is configured, you'll need to research that if you don't know how to do it.

Another thing you may notice, my manifests use crd.projectcalico.org/v1 and not what the docs from Tigera show, projectcalico.org/v3. The reason being, that API doesn't exist in the installed manifests, I don't know why they show that API in their docs, but, they do.

Solution

Install MetalLB

First step is installing MetalLB, we'll cover the configuration, so go ahead and install it.

You can get that information from the MetalLB site:

MetalLB, bare metal load-balancer for Kubernetes

Configure MetalLB's IP address pool

Next step is creating the IPAddressPool. Here's the manifest for that, it's simple and only contains the addresses option. We don't need, nor want, any BGP configuration for MetalLB. That is all handled by Calico.

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 192.168.3.0/24

Setup a Calico BGP filter

Next up is we need to create a filter for what Calico will send to the upstream BGP peer. Here we will allow everything in the 192.168.3.0/24 subnet.

apiVersion: crd.projectcalico.org/v1
kind: BGPFilter
metadata:
  name: default
spec:
  exportV4:
  - action: Reject
    matchOperator: NotIn
    cidr: 192.168.3.0/24

Setup a Calico BGP peer

Now that we have our filter blocking everything except 192.168.3.0/24 we'll create the BGPPeer, referencing the filter we just created.

apiVersion: crd.projectcalico.org/v1
kind: BGPPeer
metadata:
  name: default
spec:
  peerIP: 192.168.0.1
  asNumber: 64512
  keepOriginalNextHop: false
  filters:
  - default

Setup the Calico BGP Configuration

Now that we have a peer, create the BGPConfiguration. We add each individual IP in the subnet for a reason. If you do the entire subnet, 192.168.3.0/24 instead, it will push that entire subnet to all your nodes. If you want to use the local type in your service, that won't work since that requires the pod to be on the node that the traffic is coming in on. The externalTrafficPolicy set to local on the service is required to preserve the client IP address. Without that it will show the kubeproxy address running on the node.

For a /24 subnet you can use this handy little bash command to generate those entries, replace the 192.168.3 with your subnet.

for i in $(seq 1 255); do echo "  - cidr: 192.168.3.$i/32"; done
apiVersion: crd.projectcalico.org/v1
kind: BGPConfiguration
metadata:
  name: default
spec:
  logSeverityScreen: Info
  asNumber: 64512
  listenPort: 178
  serviceLoadBalancerIPs:
  - cidr: 192.168.3.0/32
  - cidr: 192.168.3.1/32
  - cidr: 192.168.3.2/32
  - cidr: 192.168.3.3/32
  - cidr: 192.168.3.4/32
  - cidr: 192.168.3.5/32
  - cidr: 192.168.3.6/32
  - cidr: 192.168.3.7/32
  - cidr: 192.168.3.8/32
  - cidr: 192.168.3.9/32
  - cidr: 192.168.3.10/32
  - cidr: 192.168.3.11/32
  - cidr: 192.168.3.12/32
  - cidr: 192.168.3.13/32
  - cidr: 192.168.3.14/32
  - cidr: 192.168.3.15/32
  - cidr: 192.168.3.16/32
  - cidr: 192.168.3.17/32
  - cidr: 192.168.3.18/32
  - cidr: 192.168.3.19/32
  - cidr: 192.168.3.20/32
  - cidr: 192.168.3.21/32
  - cidr: 192.168.3.22/32
  - cidr: 192.168.3.23/32
  - cidr: 192.168.3.24/32
  - cidr: 192.168.3.25/32
  - cidr: 192.168.3.26/32
  - cidr: 192.168.3.27/32
  - cidr: 192.168.3.28/32
  - cidr: 192.168.3.29/32
  - cidr: 192.168.3.30/32
  - cidr: 192.168.3.31/32
  - cidr: 192.168.3.32/32
  - cidr: 192.168.3.33/32
  - cidr: 192.168.3.34/32
  - cidr: 192.168.3.35/32
  - cidr: 192.168.3.36/32
  - cidr: 192.168.3.37/32
  - cidr: 192.168.3.38/32
  - cidr: 192.168.3.39/32
  - cidr: 192.168.3.40/32
  - cidr: 192.168.3.41/32
  - cidr: 192.168.3.42/32
  - cidr: 192.168.3.43/32
  - cidr: 192.168.3.44/32
  - cidr: 192.168.3.45/32
  - cidr: 192.168.3.46/32
  - cidr: 192.168.3.47/32
  - cidr: 192.168.3.48/32
  - cidr: 192.168.3.49/32
  - cidr: 192.168.3.50/32
  - cidr: 192.168.3.51/32
  - cidr: 192.168.3.52/32
  - cidr: 192.168.3.53/32
  - cidr: 192.168.3.54/32
  - cidr: 192.168.3.55/32
  - cidr: 192.168.3.56/32
  - cidr: 192.168.3.57/32
  - cidr: 192.168.3.58/32
  - cidr: 192.168.3.59/32
  - cidr: 192.168.3.60/32
  - cidr: 192.168.3.61/32
  - cidr: 192.168.3.62/32
  - cidr: 192.168.3.63/32
  - cidr: 192.168.3.64/32
  - cidr: 192.168.3.65/32
  - cidr: 192.168.3.66/32
  - cidr: 192.168.3.67/32
  - cidr: 192.168.3.68/32
  - cidr: 192.168.3.69/32
  - cidr: 192.168.3.70/32
  - cidr: 192.168.3.71/32
  - cidr: 192.168.3.72/32
  - cidr: 192.168.3.73/32
  - cidr: 192.168.3.74/32
  - cidr: 192.168.3.75/32
  - cidr: 192.168.3.76/32
  - cidr: 192.168.3.77/32
  - cidr: 192.168.3.78/32
  - cidr: 192.168.3.79/32
  - cidr: 192.168.3.80/32
  - cidr: 192.168.3.81/32
  - cidr: 192.168.3.82/32
  - cidr: 192.168.3.83/32
  - cidr: 192.168.3.84/32
  - cidr: 192.168.3.85/32
  - cidr: 192.168.3.86/32
  - cidr: 192.168.3.87/32
  - cidr: 192.168.3.88/32
  - cidr: 192.168.3.89/32
  - cidr: 192.168.3.90/32
  - cidr: 192.168.3.91/32
  - cidr: 192.168.3.92/32
  - cidr: 192.168.3.93/32
  - cidr: 192.168.3.94/32
  - cidr: 192.168.3.95/32
  - cidr: 192.168.3.96/32
  - cidr: 192.168.3.97/32
  - cidr: 192.168.3.98/32
  - cidr: 192.168.3.99/32
  - cidr: 192.168.3.100/32
  - cidr: 192.168.3.101/32
  - cidr: 192.168.3.102/32
  - cidr: 192.168.3.103/32
  - cidr: 192.168.3.104/32
  - cidr: 192.168.3.105/32
  - cidr: 192.168.3.106/32
  - cidr: 192.168.3.107/32
  - cidr: 192.168.3.108/32
  - cidr: 192.168.3.109/32
  - cidr: 192.168.3.110/32
  - cidr: 192.168.3.111/32
  - cidr: 192.168.3.112/32
  - cidr: 192.168.3.113/32
  - cidr: 192.168.3.114/32
  - cidr: 192.168.3.115/32
  - cidr: 192.168.3.116/32
  - cidr: 192.168.3.117/32
  - cidr: 192.168.3.118/32
  - cidr: 192.168.3.119/32
  - cidr: 192.168.3.120/32
  - cidr: 192.168.3.121/32
  - cidr: 192.168.3.122/32
  - cidr: 192.168.3.123/32
  - cidr: 192.168.3.124/32
  - cidr: 192.168.3.125/32
  - cidr: 192.168.3.126/32
  - cidr: 192.168.3.127/32
  - cidr: 192.168.3.128/32
  - cidr: 192.168.3.129/32
  - cidr: 192.168.3.130/32
  - cidr: 192.168.3.131/32
  - cidr: 192.168.3.132/32
  - cidr: 192.168.3.133/32
  - cidr: 192.168.3.134/32
  - cidr: 192.168.3.135/32
  - cidr: 192.168.3.136/32
  - cidr: 192.168.3.137/32
  - cidr: 192.168.3.138/32
  - cidr: 192.168.3.139/32
  - cidr: 192.168.3.140/32
  - cidr: 192.168.3.141/32
  - cidr: 192.168.3.142/32
  - cidr: 192.168.3.143/32
  - cidr: 192.168.3.144/32
  - cidr: 192.168.3.145/32
  - cidr: 192.168.3.146/32
  - cidr: 192.168.3.147/32
  - cidr: 192.168.3.148/32
  - cidr: 192.168.3.149/32
  - cidr: 192.168.3.150/32
  - cidr: 192.168.3.151/32
  - cidr: 192.168.3.152/32
  - cidr: 192.168.3.153/32
  - cidr: 192.168.3.154/32
  - cidr: 192.168.3.155/32
  - cidr: 192.168.3.156/32
  - cidr: 192.168.3.157/32
  - cidr: 192.168.3.158/32
  - cidr: 192.168.3.159/32
  - cidr: 192.168.3.160/32
  - cidr: 192.168.3.161/32
  - cidr: 192.168.3.162/32
  - cidr: 192.168.3.163/32
  - cidr: 192.168.3.164/32
  - cidr: 192.168.3.165/32
  - cidr: 192.168.3.166/32
  - cidr: 192.168.3.167/32
  - cidr: 192.168.3.168/32
  - cidr: 192.168.3.169/32
  - cidr: 192.168.3.170/32
  - cidr: 192.168.3.171/32
  - cidr: 192.168.3.172/32
  - cidr: 192.168.3.173/32
  - cidr: 192.168.3.174/32
  - cidr: 192.168.3.175/32
  - cidr: 192.168.3.176/32
  - cidr: 192.168.3.177/32
  - cidr: 192.168.3.178/32
  - cidr: 192.168.3.179/32
  - cidr: 192.168.3.180/32
  - cidr: 192.168.3.181/32
  - cidr: 192.168.3.182/32
  - cidr: 192.168.3.183/32
  - cidr: 192.168.3.184/32
  - cidr: 192.168.3.185/32
  - cidr: 192.168.3.186/32
  - cidr: 192.168.3.187/32
  - cidr: 192.168.3.188/32
  - cidr: 192.168.3.189/32
  - cidr: 192.168.3.190/32
  - cidr: 192.168.3.191/32
  - cidr: 192.168.3.192/32
  - cidr: 192.168.3.193/32
  - cidr: 192.168.3.194/32
  - cidr: 192.168.3.195/32
  - cidr: 192.168.3.196/32
  - cidr: 192.168.3.197/32
  - cidr: 192.168.3.198/32
  - cidr: 192.168.3.199/32
  - cidr: 192.168.3.200/32
  - cidr: 192.168.3.201/32
  - cidr: 192.168.3.202/32
  - cidr: 192.168.3.203/32
  - cidr: 192.168.3.204/32
  - cidr: 192.168.3.205/32
  - cidr: 192.168.3.206/32
  - cidr: 192.168.3.207/32
  - cidr: 192.168.3.208/32
  - cidr: 192.168.3.209/32
  - cidr: 192.168.3.210/32
  - cidr: 192.168.3.211/32
  - cidr: 192.168.3.212/32
  - cidr: 192.168.3.213/32
  - cidr: 192.168.3.214/32
  - cidr: 192.168.3.215/32
  - cidr: 192.168.3.216/32
  - cidr: 192.168.3.217/32
  - cidr: 192.168.3.218/32
  - cidr: 192.168.3.219/32
  - cidr: 192.168.3.220/32
  - cidr: 192.168.3.221/32
  - cidr: 192.168.3.222/32
  - cidr: 192.168.3.223/32
  - cidr: 192.168.3.224/32
  - cidr: 192.168.3.225/32
  - cidr: 192.168.3.226/32
  - cidr: 192.168.3.227/32
  - cidr: 192.168.3.228/32
  - cidr: 192.168.3.229/32
  - cidr: 192.168.3.230/32
  - cidr: 192.168.3.231/32
  - cidr: 192.168.3.232/32
  - cidr: 192.168.3.233/32
  - cidr: 192.168.3.234/32
  - cidr: 192.168.3.235/32
  - cidr: 192.168.3.236/32
  - cidr: 192.168.3.237/32
  - cidr: 192.168.3.238/32
  - cidr: 192.168.3.239/32
  - cidr: 192.168.3.240/32
  - cidr: 192.168.3.241/32
  - cidr: 192.168.3.242/32
  - cidr: 192.168.3.243/32
  - cidr: 192.168.3.244/32
  - cidr: 192.168.3.245/32
  - cidr: 192.168.3.246/32
  - cidr: 192.168.3.247/32
  - cidr: 192.168.3.248/32
  - cidr: 192.168.3.249/32
  - cidr: 192.168.3.250/32
  - cidr: 192.168.3.251/32
  - cidr: 192.168.3.252/32
  - cidr: 192.168.3.253/32
  - cidr: 192.168.3.254/32

Configure a service with a local external traffic policy

Here is an example of a local external traffic policy service, this will get assigned an IP address from the allowed pool and that IP will then go up to your BGP peer.

apiVersion: v1
kind: Service
metadata:
  name: lb
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 2368
  selector:
    my: selector
  type: LoadBalancer
  externalTrafficPolicy: Local

Conclusion

That should be all there is to exposing your local LoadBalancer services using MetalLB and Calico with BGP configuring your upstream router.

Setting this up was fun and interesting. I learned a lot about BGP and FRR, that's what I used on my Linux router for this project. Figuring out some of the nuances and inconsistencies in the documentation was a bit challenging, but, that's what these posts are for right?

MetalLB, bare metal load-balancer for Kubernetes
Configure BGP peering | Calico Documentation
Configure BGP peering with full mesh, node-specific peering, ToR, and/or Calico route reflectors.