Running metallb in Layer 2 mode

MetalLB is a load-balancer implementation for bare metal Kubernetes clusters. I’ve used it succesfully to add support for LoadBalancer type service on bare-metal kubernetes clusters to expose my services outside the cluster.

Installation

First check if metallb is compatible with your CNI plugin at https://metallb.universe.tf/installation/network-addons/.

Installing metallb is as simple as running the following command.

kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.3/manifests/metallb.yaml.

This deploys two components - controller and speaker. controller is deployed as a Kubernetes Deployment and speaker is deployed as a Kubernetes Daemonset. Metallb deals with two primary tasks - Address allocation and external anouncement.

Once metallb is installed, you configure it by creating a ConfigMap object under the same namespace it is installed in(metallb-system). Metallb works in two modes - Layer2 and BGP. I’ve used it in Layer2 as it is simplest to configure.

Following is example of configuration for Layer2 mode configuration:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      # Change this address pool to your IP range
      - 192.168.1.240-192.168.1.250

How does it work?

The controller component of metallb watches for creation or updation of kubernetes Service objects of type LoadBalancer and assigns an IP from the address pool given to it from the configuration.

The job of speaker component is to announce this address. speaker is deployed as a Daemonset and so runs on every node. When controller assigns an IP to a speaker, it announces this event by sending out gratuitous ARP messages and by replying to ARP requests.

If a node that owns the IP goes down, another node takes over the IP and starts responding to the ARP requests.

One disadvantage with Layer 2 mode is that a single node attracts all the service IP’s traffic. From there, kube-proxy spreads the traffic across the service’s pods. So there is failover between nodes but there is no node level loadbalancing. So the service’s ingress bandwidth is limited to the bandwidth of single node.

What virtual IPs should I use?

In Layer 2 mode, the virtual IPs that you give to metallb should be routable to the cluster network. This means all the nodes IPs and the extra virtual IPs should be in same subnet.

How many virtual IPs do I need?

If you are running inside a private network, it should be easy to get as many IP addresses as you want. At minimum you would need as many IPs as the number of LoadBalancer services you have so that each service get’s it’s own IP.

Sharing IPs among different services

If for some reason, you do not have the luxury of getting the reuired number of IPs or if you want to expose all the services on same IP, metallb supports IP sharing mode where multiple services can use same IP given that they use different service ports. To enable IP sharing, you need add an annotation with key metallb.universe.tf/allow-shared-ip and use same value for this annotation in all the services you want to collocate on same IP. To guarantee that IP is shared among the services, you also need to set the spec.loadBalancerIP field to required IP. If only annotation is set and spec.loadBalancerIP is not set, metallb tried to collacate the services on same IP but it is not guranteed.

apiVersion: v1
kind: Service
metadata:
  name: gateway
  labels:
    app: gateway
  annotations:
    # Use `my-shared-ip` value in other services which
    # should be collocated with this service
    metallb.universe.tf/allow-shared-ip: my-shared-ip
spec:
  type: LoadBalancer
  # Set loadBalancerIP to guarantee services share the IP
  loadBalancerIP: 192.168.1.240
  selector:
    app: gateway
  - name: http
    port: 80
    targetPort: 80
Written on January 12, 2020