Why?

My motivation for using Kubernetes to manage Pi-hole and other services is to enable Pi-hole to run on any node within the cluster, which could result in IP changes. Additionally, I may want to create multiple instances of Pi-hole in the future to enhance redundancy. To achieve this, I need to place Pi-hole behind a load balancer.

By default, Kubernetes does not include load balancer provisioners. Therefore, when operating a Kubernetes cluster on my Raspberry Pi setup, I must provide a provisioner. Although k3s comes with klipper-lb pre-installed, its downside is that the load balancer IP is tied to the node.

Metallb offers a solution by allowing me to designate a reserved IP range for load balancers. It is simple and efficient, making it suitable for home use.

Prerequisite

Disable klipper-lb with --disable servicelb flag

1
2
k3sup install --ip <RPi-Server-IP> --user <RPi-user> \
     --k3s-extra-args '--disable servicelb'

Installation

1
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml

Reference: the official guide

IP Address Pool

At here I defined an IP address pool local-pool with address range 192.168.10.240 to 192.168.10.250.

1
2
3
4
5
6
7
8
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: local-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.10.240-192.168.10.250

Then advertise the pool with the simplest layer 2 mode (corresponding to layer 3 in OSI model).

1
2
3
4
5
6
7
8
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: local
  namespace: metallb-system
spec:
  ipAddressPools:
  - local-pool

That’s it.

Example

Here’s an example of creating a TCP and a UDP load balancers sharing the same IP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: Service
metadata:
  name: pihole-tcp
  namespace: pihole
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole
spec:
  type: LoadBalancer
  loadBalancerIP: 192.168.10.250
  ports:
    - name: piholetcp
      protocol: TCP
      port: 53
      targetPort: 53
  selector:
    app: pihole
---
apiVersion: v1
kind: Service
metadata:
  name: pihole-udp
  namespace: pihole
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole
spec:
  type: LoadBalancer
  loadBalancerIP: 192.168.10.250
  ports:
    - name: piholeudp
      protocol: UDP
      port: 53
      targetPort: 53
  selector:
    app: pihole