Deploying the MetalLB load balancer
MetalLB as the load balancer of choice
You have your K3s cluster up and running, but it is missing a crucial component: a load balancer.
The chapter about preparing the K3s cluster made you disable the default one because here you will deploy a more capable and popular alternative called MetalLB.
Considerations before deploying MetalLB
Before you can deploy MetalLB in your K3s cluster, there are certain points you must consider first.
Choosing the right mode of operation for MetalLB
MetalLB can work in one of these two modes:
The layer 2 option is the one that fits your K3s cluster, and is the most simple and straightforward mode to configure and run. BGP, on the other hand, requires a more complex setup (including network traffic routing) more appropriate for large Kubernetes clusters.
Reserve an IP range for services
You need to reserve a range, continuous if possible, of free IP addresses in your network. MetalLB, in layer 2 mode, will then assign IPs to each service you expose directly through it. This is to avoid collisions between services that happen to use the same ports, like the widely used 80 (for unencrypted HTTP communications) or 443 (for encrypted HTTPS communications).
On the other hand, remember that you have configured your cluster to use two networks, one for internal communications and other to face the external network. You only have to reserve an IP range in your external/public network (your LAN), since the internal communications will remain within your cluster. You have to ensure having enough IPs available for the services you want to make public in your network with their own static IP. This could be problematic in your LAN since it is also where your other devices are connecting to.
How to deal with IP conflicts in your LAN
Assuming that you have a regular consumer router handling your LAN, you have two ways for dealing with the issue of possible IP conflicts between your devices and the apps exposed to your LAN by MetalLB:
Assign static IPs to all devices in your LAN
Doable although cumbersome since this demands the manual handling of all IP assignments in your LAN, and also disabling the use of randomized MACs in all devices connected to the LAN. Still, this is the one that can almost guarantee that your devices and apps will not collide in their IP assignments (if you also leave the dynamic IP assignment enabled, conflicts may still happen). If you opt to this method, be sure of clearing a range of IPs in your router (meaning, do not assign any IP from that range to any device) that MetalLB can use freely.Making your private network assign IPs from the
10.0.0.0/8range
As it is explained in the chapter about deploying the K3s cluster, this guide’s LAN uses the biggest IPv4 range available for private networks:10.0.0.0/8. Still, this measure only mitigates the possibility of conflict between a device and a service exposed by MetalLB (or just with another device). This also depends on how capable your LAN’s router is handling IP assignments. The good news is that you do not have to manage manually the IPs assigned to your devices nor disable the use of randomized MACS in all of them.
In this guide’s scenario, the router is left with the dynamic IP assignment to allow devices to connect with randomized MACs, which is the default (and more secure) behavior nowadays.
Ports used by MetalLB
When using the L2 operation mode, MetalLB requires the 7946 port open both in TCP and UDP in all your cluster’s nodes, but only for internal communications among the MetalLB-related processes running on them. This 7946 port will be seen only in the internal network that runs through your isolated vmbr1 bridge. Therefore, you do not have to worry about adding specific firewall rules to open this port on your K3s cluster nodes.
Deploying from an external kubectl client
In the previous chapter you prepared an external kubectl client system for managing remotely your K3s cluster. Do not forget to have such client ready and always use it for handling your K3s cluster. This and following chapters will assume that you are using this kubectl client system.
Choosing the IP ranges for MetalLB
You have to choose an IP range on the external or public network your K3s cluster is connected to. This IP range should leave out the IPs already used by the K3s nodes themselves, helping you in keeping the nodes differentiated from the services deployed in them.
In this chapter, the chosen IP subrange “reserved” for MetalLB is 10.7.0.1-10.7.0.20. It only has twenty IPs, and you may be wondering why so few. The reason is that this guide will show you how to access the services you deploy not by assigning them a specific static IP, but through the Traefik ingress service already running in your K3s cluster. The main exception to this is the Traefik service itself, which needs its own public static IP to be reachable to do its job. In general, you either expose services directly through an external IP assigned by the load balancer or make them reachable through the ingress service, never in both ways at the same time.
Important
The bigger the reserved IP range, the greater the risk of having IP conflicts
In a private network where IPs are dynamically assigned to devices, you want to keep the MetalLB IP range as small as possible to reduce the chance of IP conflicts.
Also notice that the IP range starts with the 10.7.0.1 address rather than with the 10.7.0.0 one. Although 10.7.0.0 is a perfectly valid IP for a device within the 10.0.0.0/8 network, in testing this guide’s setup there were connectivity issues that went away when the next IP 10.7.0.1 was used instead. This may be an issue in the router used, which could be considering any IP ending in .0 only as an address identifying a network and not some device. Be aware of this issue if you face connectivity issues when using IPs ending in .0, your router or access point may not be able to handle them properly.
On the other hand, know that MetalLB links IPs to services. When a service moves from one node to another, MetalLB ensures that the IP sticks to the service. Any IP within the ranges managed by MetalLB can jump from node to node of your cluster as seen fit by the load balancer.
Deploying MetalLB on your K3s cluster
Next, see how to deploy MetalLB using kubectl and Kustomize. Kustomize is the official Kubernetes tool for customizing resource configuration without using templates or other techniques as is usually done with tools such as Helm. Kustomize is already integrated in the kubectl command, so you do not need to install anything else in your client system.
Preparing the Kustomize folder structure
It is better to treat each deployment as an independent project with its own folder structure. On this regard, there is the overlay model shown in the official introduction to Kustomize, but also there is another one in this “best practices” article meant for a repository-based organization of Kustomize projects. This guide bases the folder structures for the Kustomize projects shown in this and upcoming chapters on what is indicated in the “best practices” article.
Therefore, begin by creating a folder structure for your MetalLB deployment files as follows:
$ mkdir -p $HOME/k8sprjs/metallb/resourcesThe command creates a structure of three nested folders:
k8sprjs
Some sort of “root” folder where the MetalLB and all the other Kustomize projects of this guide will be kept.metallb
For the MetalLB deployment Kustomize project itself.resources
Holds MetalLB resources’ YAML declaration files.
You can use any other base path instead of $HOME within your kubectl client system.
Declaring the resources
There are certain resources you have to declare to make MetalLB work as specified previously in this chapter. This way, MetalLB will be deployed with the right configuration right away.
Operation mode L2Advertisement
First, prepare the resource that sets the operation mode of your MetalLB service to L2 and picks the pool of static IPs to use:
In the
resourcesfolder, create the filesl2-ip.l2advertisement.metallb.yaml:$ touch $HOME/k8sprjs/metallb/resources/l2-ip.l2advertisement.metallb.yamlIn
l2-ip.l2advertisement.metallb.yaml, specify the operation mode and pool to use:# Operation mode and pool list apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: l2-ip spec: ipAddressPools: - default-poolThis manifest sets the desired configuration to MetalLB:
The kind
L2Advertisementsets the mode used as L2. You need to use a specific MetalLB (not Kubernetes standard) kind of resource to set the operation mode of MetalLB rather than just adjusting some parameter.The
spec.ipAddressPoolparameter points to the pools of usable IPs. In this case, it is just one pool nameddefault-poolyou will declare right after thisL2Advertisementobject.
Static IP address pool
Like with the operation mode, MetalLB uses its own specific kind of resource for setting its static IP pools:
Create the file
default-pool.ipaddresspool.metallb.yamlin theresourcesfolder:$ touch $HOME/k8sprjs/metallb/resources/default-pool.ipaddresspool.metallb.yamlDeclare the IP address pool in
default-pool.ipaddresspool.metallb.yaml:# Default IP address pool setup apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: default-pool spec: addresses: - 10.7.0.1-10.7.0.20This manifest configures a simple pool of 20 static IP addresses:
The kind
IPAddressPoolindicates that this is a MetalLB pool of IP addresses.The name is the same
default-poolindicated in thel2-ip.l2advertisement.metallb.yaml.The
spec.addressesparameter is a list of IP ranges that can be expressed in different ways. This is useful when you do not have a big continuous range of IPs available in your network. For instance, you could have configured thedefault-poolIP range as:
... spec: addresses: - 10.7.0.1-10.7.0.10 - 10.7.0.11-10.7.0.20
Kustomize manifest
To put together the resources declared earlier with the official manifest describing MetalLB’s deployment, use a Kustomize manifest:
Create the
kustomization.yamlfile where to describe the deployment of MetalLB in Kustomize format:$ touch $HOME/k8sprjs/metallb/kustomization.yamlSpecify all the MetalLB resources declared in the previous steps in your new
kustomization.yamlfile:# MetalLB setup apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: metallb-system resources: - https://raw.githubusercontent.com/metallb/metallb/v0.15.2/config/manifests/metallb-native.yaml - resources/l2-ip.l2advertisement.metallb.yaml - resources/default-pool.ipaddresspool.metallb.yamlThings to notice in this manifest:
Kustomize manifests are identified in Kubernetes with the
Kustomizationkind.This declaration is based on the one offered in the official MetalLB documentation. Since the manifest URL indicated there does not work, here it has been replaced with the one indicated in the Installation by manifest section of the MetalLB documentation which does work.
The
namespacefor all the MetalLB resources deployed in your K3s cluster is going to bemetallb-system. The resources in this project that already have anamespacespecified will get it changed to this one, and those who do not have one will be set tometallb-systemtoo.The
resourcessection lists the files describing the resources used to deploy MetalLB:The first item points to the official
Kustomizationdeclaration of MetalLB. Notice how the URL also specifies which version of MetalLB to deploy:ref=v0.15.2.The other two items point to the local YAML files you have created previously to declare the alloted IP range for MetalLB.
Validating the Kustomize YAML output
You can check how the final deployment looks like as a YAML manifest with kubectl:
$ kubectl kustomize $HOME/k8sprjs/metallb/ | lessWith the kustomize option, kubectl builds the whole deployment YAML manifest resulting from processing the kustomization.yaml file. Since the output can be quite long, better to append a | less (or some other text editor of your choice) to the command for getting a paginated view of the YAML. The command takes a moment to finish because it has to download the MetalLB manifest first, then process and combine it with the other resource files found in your client system. When you finally see the result, you will get a quite long YAML output that embeds the resources you declared together with the others required in the MetalLB deployment. In particular, your L2Advertisement and IPAddressPool objects should look like this:
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- 10.7.0.1-10.7.0.20
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-ip
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
---Furthermore, you may also notice in the resulting YAML that MetalLB is prepared to look for L2Advertisement resources automatically. This implies that you do not have to explicitly tell MetalLB which one to use.
Deploying MetalLB
Now that you have your Kustomize project ready, you are just one command away from deploying MetalLB:
$ kubectl apply -k $HOME/k8sprjs/metallb/This command automatically looks for a kustomization.yaml file in the folder you specify. Then, kubectl builds the whole deployment output like with the kustomize option but, instead of displaying it, kubectl takes that YAML and directly applies it on your Kubernetes cluster. In this case, the kubectl command will return an output like the following:
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/servicebgpstatuses.metallb.io created
customresourcedefinition.apiextensions.k8s.io/servicel2statuses.metallb.io created
serviceaccount/controller created
serviceaccount/speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
rolebinding.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
configmap/metallb-excludel2 created
secret/metallb-webhook-cert created
service/metallb-webhook-service created
deployment.apps/controller created
daemonset.apps/speaker created
ipaddresspool.metallb.io/default-pool created
l2advertisement.metallb.io/l2-ip created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration createdThe lines inform about the resources created by your deployment. They could also show sporadic warnings about deprecated apis being used in the application’s deployment.
Important
The deployment in the cluster may be successful, but the deployed service may have issues
Even if you do not get a lot of warnings or, worse, errors, the deployment may not have been truly successful due to issues that go beyond what Kubernetes can detect, like configuration problems specific to the deployed service.
Give MetalLB a couple of minutes to get ready, then check with kubectl that it has been deployed in your cluster:
$ kubectl get -n metallb-system all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/controller-58fdf44d87-q6l7w 1/1 Running 1 (14m ago) 15m 10.42.1.5 k3sagent02 <none> <none>
pod/speaker-8rrkg 1/1 Running 0 15m 172.16.2.1 k3sagent01 <none> <none>
pod/speaker-grsdm 1/1 Running 0 15m 172.16.2.2 k3sagent02 <none> <none>
pod/speaker-z6dcg 1/1 Running 0 15m 172.16.1.1 k3sserver01 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/metallb-webhook-service ClusterIP 10.43.126.18 <none> 443/TCP 15m component=controller
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
daemonset.apps/speaker 3 3 3 3 3 kubernetes.io/os=linux 15m speaker quay.io/metallb/speaker:v0.15.2 app=metallb,component=speaker
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/controller 1/1 1 1 15m controller quay.io/metallb/controller:v0.15.2 app=metallb,component=controller
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/controller-58fdf44d87 1 1 1 15m controller quay.io/metallb/controller:v0.15.2 app=metallb,component=controller,pod-template-hash=58fdf44d87The MetalLB resources are all under the metallb-system namespace. On the other hand, you can already see the effect on your existing services of having this load balancer running in the K3s cluster:
$ kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 3d5h
kube-system kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 3d5h
kube-system traefik LoadBalancer 10.43.174.63 10.7.0.1 80:30512/TCP,443:32647/TCP 3d5h
metallb-system metallb-webhook-service ClusterIP 10.43.126.18 <none> 443/TCP 12mFrom all the services you have running at this point in your K3s cluster, the traefik service is the one set with the LoadBalancer type. Now it has an EXTERNAL-IP address assigned from MetalLB’s default-pool. In particular, it has got the very first available IP (10.7.0.1) from the default-pool.
Note
Traefik makes services available through its own external IP
All services configured to be accessed with a Traefik-based ingress, will be reachable only through the same Traefik external IP.
In other words, you will need to associate the DNS name or hostname of any service served through Traefik to the Traefik service’s IP. Do this in your client systems’ host file or in your LAN DNS system.
MetalLB’s Kustomize project attached to this guide
You can find the Kustomize project for this MetalLB deployment in the following attached folder:
Relevant system paths
Folders on remote kubectl client
$HOME/k8sprjs$HOME/k8sprjs/metallb$HOME/k8sprjs/metallb/resources
Files on remote kubectl client
$HOME/k8sprjs/metallb/kustomization.yaml$HOME/k8sprjs/metallb/resources/default-pool.ipaddresspool.metallb.yaml$HOME/k8sprjs/metallb/resources/l2-ip.l2advertisement.metallb.yaml