A k8s LB using ARP

Metallb now supports ARM LBs. This is great, because it allows you to create an working LB in your (shitty) home network with off-the-shelve, cheap, router hardware. Running k8s at home (on ARM), became a whole lot more interesting!

Note: this will all be different in metallb 0.3.0, but if you want to play with this now you can follow this little guide.

We follow some of steps in the tutorial.

This guide assumes a 192.168.1/24 (home) network. The load balanced IP pool is set to 192.168.1.240/28, so you’ve got 14 IP addresses to play LB with.

This assumes a cluster is up and running, i.e. minikube might work, but I haven’t used that.

Install metallb

Install metallb with ARP support and get some logs from the daemonset.

% kubectl apply -f https://raw.githubusercontent.com/google/metallb/master/manifests/metallb-arp.yaml
% kubectl logs -l app=arp-speaker -n metallb-system

A log line like:

1216 09:16:17.048864       1 arp.go:199] Address found 192.168.1.109/24 for interface: {2 1500 eth0 b8:27:eb:06:2a:72 up|broadcast|multicast}

Shows the arp-speaker found the interface of the node and will use that to send and receive ARP packets.

Apply the config map with the config for ARP:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      cidr:
      - 192.168.1.240/28    

The only thing we need is the address-pools where we tell it assign from 192.168.1.240/28. Apply this configuration:

% kubectl apply -f https://raw.githubusercontent.com/google/metallb/master/manifests/arp-config.yaml

Create a load-balanced service

% kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.2.0/manifests/tutorial-2.yaml
deployment "nginx" created
service "nginx" created

The arp-speaker logs should have something like this:

I1216 09:22:33.433858       1 main.go:93] default/nginx: announcable, making advertisement
I1216 09:22:33.434890       1 main.go:103] default/nginx: end update
I1216 09:22:33.775673       1 arp.go:117] Request: who-has 192.168.1.240?  tell 192.168.1.1 (b4:75:0e:63:b2:20). reply: 192.168.1.240 is-at b8:27:eb:66:21:54
I1216 09:22:33.846087       1 arp.go:117] Request: who-has 192.168.1.240?  tell 192.168.1.1 (b4:75:0e:63:b2:20). reply: 192.168.1.240 is-at b8:27:eb:66:21:54
I1216 09:22:33.985725       1 arp.go:117] Request: who-has 192.168.1.240?  tell 192.168.1.1 (b4:75:0e:63:b2:20). reply: 192.168.1.240 is-at b8:27:eb:66:21:54
I1216 09:22:34.095799       1 arp.go:117] Request: who-has 192.168.1.240?  tell 192.168.1.1 (b4:75:0e:63:b2:20). reply: 192.168.1.240 is-at b8:27:eb:66:21:54
I1216 09:22:34.215941       1 arp.go:117] Request: who-has 192.168.1.240?  tell 192.168.1.1 (b4:75:0e:63:b2:20). reply: 192.168.1.240 is-at b8:27:eb:66:21:54

And looking at the service, we indeed see an EXTERNAL-IP:

% kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>          443/TCP        2d
nginx        LoadBalancer   10.102.30.250   192.168.1.240   80:31517/TCP   1m

From my laptop I can now curl that IP!

% curl http://192.168.1.240
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

This whole thing just feels like magic.