{"id":199,"date":"2026-01-02T23:34:38","date_gmt":"2026-01-02T23:34:38","guid":{"rendered":"https:\/\/blog.simulakrum.vpndns.org\/?p=199"},"modified":"2026-01-12T10:51:34","modified_gmt":"2026-01-12T10:51:34","slug":"metallb-in-a-raspberry-pi-cluster","status":"publish","type":"post","link":"https:\/\/blog.simulakrum.vpndns.org\/?p=199","title":{"rendered":"MetalLB in a Raspberry PI cluster"},"content":{"rendered":"\n<p>After a recent installation of a Multi-master Istio mesh on two clusters, I decided to add the third cluster in the mesh, one completely based on RaspberryPIs. All worked well until I added the MetalLB (L2 mode) for the LoadBalancers, that, unlike the first two clusters, wasn&#8217;t working properly: all was there &#8211; the IPs were issued straight from the assigned pool, Istio could see all of the endpoints, and LoadBalancing apparently worked, and the pods in the newest cluster could communicate with the services in other two clusters via their internal &#8220;east-west&#8221; Istio Gateways, while still letting nothing in through the &#8220;ingressgateway&#8221;. <\/p>\n\n\n\n<p><br>Nmap showed filtered ports of the Gateways and LoadBalancers. <br>I could only figure out using tcpdump that there were no ARP answers when Istio GW was queried.<br>Everything else remained cryptic to me, like messages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Error from server InternalError error when creating metallb-pool.yaml Internal error occurred failed calling webhook ipaddresspoolvalidationwebhook.metallb.io failed to call webhook Post https:\/\/metallb-webhook-service.metallb-system.svc:443\/validate-metallb-io-v1beta1-ipaddresspool?timeout=10s dial tcp 10.101.81.237:443: connect: no route to host<\/code><\/pre>\n\n\n\n<p>and<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Error from server InternalError metallb-system.svc:443\/validate-metallb-io-v1beta1-ipaddresspool connect: no route to host<\/code><\/pre>\n\n\n\n<p>After a long bug-hunt, changing to legacy iptables and arptables, changes of CNIs (Flannel, Calico, Canal&#8230;) attempts to see if there was a difference with cri-socket (docker, docker+containerd, cri-o&#8230;) installations via Helm, tryouts of FFR with BGP server, and what not, I finally tried 0.13.12 instead of MetalLB 0.15.3, and it worked!<br>Apparently, somewhere in between, MetalLB started strictly respecting the exclusion labels of the nodes, that at the moment held the dreadful label:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>node.kubernetes.io\/exclude-from-external-load-balancers<\/code><\/pre>\n\n\n\n<p>After the label removal from the nodes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>for i in $(kubectl get nodes --no-headers | awk '{print $1}') ; do kubectl label node $i node.kubernetes.io\/exclude-from-external-load-balancers=- ; done<\/code><\/pre>\n\n\n\n<p>the latest (at the moment of writing) MetalLB finally started working, and Multi-mesh automagically became fully operational:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"538\" height=\"694\" src=\"https:\/\/blog.simulakrum.vpndns.org\/wp-content\/uploads\/2026\/01\/Screenshot-2026-01-03-at-00.29.57.png\" alt=\"\" class=\"wp-image-200\" srcset=\"https:\/\/blog.simulakrum.vpndns.org\/wp-content\/uploads\/2026\/01\/Screenshot-2026-01-03-at-00.29.57.png 538w, https:\/\/blog.simulakrum.vpndns.org\/wp-content\/uploads\/2026\/01\/Screenshot-2026-01-03-at-00.29.57-233x300.png 233w\" sizes=\"auto, (max-width: 538px) 100vw, 538px\" \/><\/figure>\n\n\n\n<p>I hope this hint saves you a day or two, I know now that it would do so for me&#8230; <\/p>\n","protected":false},"excerpt":{"rendered":"<p>After a recent installation of a Multi-master Istio mesh on two clusters, I decided to add the third cluster in the mesh, one completely based on RaspberryPIs. All worked well until I added the MetalLB (L2 mode) for the LoadBalancers, that, unlike the first two clusters, wasn&#8217;t working properly: all was there &#8211; the IPs were issued straight from the &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-199","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/posts\/199","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=199"}],"version-history":[{"count":4,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/posts\/199\/revisions"}],"predecessor-version":[{"id":206,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=\/wp\/v2\/posts\/199\/revisions\/206"}],"wp:attachment":[{"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=199"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=199"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.simulakrum.vpndns.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=199"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}