When you deploy Kubernetes Cluster in a Public Cloud environment, the network load balancers are available on-demand. The same is not true for clusters deployed in private cloud environment or any kind of on-prem Infrastructure. In this kind of setups it’s the responsibility of System Administrators / Network Engineers to integrate Kubernetes Cluster with any Load balancer(s) in place.

A Load balancer is responsible for the provision of a single IP address to route incoming requests to the application. For you to successfully create Kubernetes services of type LoadBalancer, a load balancer implementation available for Kubernetes is required.

Since Bare-metal environments lack Load Balancers by default, Services of type LoadBalancers will remain in the “pending” state indefinitely when created. In this article we’ll perform an installation of MetalLB Load Balancer on Kubernetes Cluster to offer the same kind of access to external consumers. Commonly used tools in Bare-metal Kubernetes clusters to bring user traffic in are “NodePort” and “externalIPs“.

What is MetalLB?

MetalLB is a pure software solution that provides a network load-balancer implementation for Kubernetes clusters that are not deployed in supported cloud provider using standard routing protocols. By installing MetalLB solution, you effectively get LoadBalancer Services within your Kubernetes cluster.

MetalLB have the following requirement to function:

  • A Kubernetes cluster on version 1.13.0 or later. The cluster should not have another network load-balancing functionality.
  • A cluster network configuration that can coexist with MetalLB.
  • Availability of IPv4 addresses that MetalLB will assign to LoadBalancer services when requested.
  • If using BGP operating mode, one or more routers capable of speaking BGP are required.
  • When using the L2 operating mode, traffic on port 7946 (TCP & UDP, other port can be configured) must be allowed between nodes, as required by memberlist.

Installation of MetalLB Load Balancer on Kubernetes Cluster

Confirm if your Kubernetes Cluster API is responsive and you’re able to use kubectl cluster administration command-line tool:

$ kubectl cluster-info
Kubernetes control plane is running at https://k8sapi.example.com:6443
CoreDNS is running at https://k8sapi.example.com:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Install curl and wget utilities if not available already in your workstation:

### Debian / Ubuntu ###
sudo apt update && sudo apt install wget curl -y

### CentOS / RHEL / Fedora ###
sudo yum -y install wget curl

1. Download MetalLB installation manifest

Get the latest MetalLB release tag:

MetalLB_RTAG=$(curl -s https://api.github.com/repos/metallb/metallb/releases/latest|grep tag_name|cut -d '"' -f 4|sed 's/v//')

To check release tag use echo command:

echo $MetalLB_RTAG

Create directory where manifests will be downloaded to.

mkdir ~/metallb
cd ~/metallb

Download MetalLB installation manifest:

wget https://raw.githubusercontent.com/metallb/metallb/v$MetalLB_RTAG/config/manifests/metallb-native.yaml

Below are the components in the manifest file:

  • The metallb-system/controller deployment – Cluster-wide controller that handles IP address assignments.
  • The metallb-system/speaker daemonset – Component that speaks the protocol(s) of your choice to make the services reachable.
  • Service accounts for both controller and speaker, along with RBAC permissions that the needed by the components to function.

2. Install MetalLB Load Balancer on Kubernetes cluster

Install MetalLB in your Kubernetes cluster by apply the manifest:

$ kubectl apply -f metallb-native.yaml
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io 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
serviceaccount/controller created
serviceaccount/speaker created
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/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
secret/webhook-server-cert created
service/webhook-service created
deployment.apps/controller created
daemonset.apps/speaker created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created

The command we executed deploys MetalLB to your Kubernetes cluster, under the metallb-system namespace.

watch kubectl get all -n metallb-system
kubectl get pods -n metallb-system --watch

Waif for everything to be in running state, then you can list running Pods:

$ kubectl get pods  -n metallb-system
NAME                              READY   STATUS    RESTARTS   AGE
pod/controller-5bd9496b89-fkgwc   1/1     Running   0          7m25s
pod/speaker-58282                 1/1     Running   0          7m25s
pod/speaker-bwzfz                 1/1     Running   0          7m25s
pod/speaker-q78r9                 1/1     Running   0          7m25s
pod/speaker-vv6nr                 1/1     Running   0          7m25s

To list all services instead, use the commands:

$ kubectl get all  -n metallb-system
NAME                              READY   STATUS    RESTARTS   AGE
pod/controller-5bd9496b89-fkgwc   1/1     Running   0          7m25s
pod/speaker-58282                 1/1     Running   0          7m25s
pod/speaker-bwzfz                 1/1     Running   0          7m25s
pod/speaker-q78r9                 1/1     Running   0          7m25s
pod/speaker-vv6nr                 1/1     Running   0          7m25s

NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/webhook-service   ClusterIP   10.98.112.134   <none>        443/TCP   7m25s

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/speaker   4         4         4       4            4           kubernetes.io/os=linux   7m25s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/controller   1/1     1            1           7m25s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/controller-5bd9496b89   1         1         1       7m25s

The installation manifest does not include a configuration file required to use MetalLB. All MetalLB components are started, but will remain in idle state until you finish necessary configurations.

3. Create Load Balancer services Pool of IP Addresses

MetalLB needs a pool of IP addresses to assign to the services when it gets such request. We have to instruct MetalLB to do so via the IPAddressPool CR.

Let’s create a file with configurations for the IPs that MetalLB uses to assign IPs to services. In the configuration the pool has IPs range 192.168.1.30-192.168.1.50.

$ vim ~/metallb/ipaddress_pools.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: production
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.30-192.168.1.50

The IP addresses can be defined by CIDR, by range, and both IPV4 and IPV6 addresses can be assigned.

...
spec:
  addresses:
  - 192.168.1.0/24

You can define multiple instances of IPAddressPoolin single definition. See example below:

...
spec:
  addresses:
  - 192.168.1.0/24
  - 172.20.20.30-172.20.20.50
  - fc00:f853:0ccd:e799::/124

Announce service IPs after creation

This is a sample configuration used to advertise all IP address pools created in the cluster.

apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advert
  namespace: metallb-system

Advertisement can also be limited to a specific Pool. In the example the limit is to the production pool.

apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: pool1-advert
  namespace: metallb-system
spec:
  ipAddressPools:
  - production

A complete configuration with both IP Address Pool and L2 advertisement is shown here.

$ vim ~/metallb/ipaddress_pools.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: production
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.30-192.168.1.50
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advert
  namespace: metallb-system

Apply the configuration using kubectl command:

$ kubectl apply -f  ~/metallb/ipaddress_pools.yaml
ipaddresspool.metallb.io/production created
l2advertisement.metallb.io/l2-advert created

List created IP Address Pools and Advertisements:

$ kubectl get ipaddresspools.metallb.io -n metallb-system
NAME         AGE
production   23s

$ kubectl get l2advertisements.metallb.io -n metallb-system
NAME        AGE
l2-advert   49s

Get more details using describe kubectl command option:

$ kubectl describe ipaddresspools.metallb.io production -n metallb-system
Name:         production
Namespace:    metallb-system
Labels:       <none>
Annotations:  <none>
API Version:  metallb.io/v1beta1
Kind:         IPAddressPool
Metadata:
  Creation Timestamp:  2023-11-09T12:01:44Z
  Generation:          1
  Resource Version:    5640
  UID:                 20d8ae5f-31e9-4928-b617-1457cf72e79e
Spec:
  Addresses:
    192.168.1.30-192.168.1.50
  Auto Assign:       true
  Avoid Buggy I Ps:  false
Events:              <none>

4. Deploying services that use MetalLB LoadBalancer

With the MetalLB installed and configured, we can test by creating service with spec.type set to LoadBalancer, and MetalLB will do the rest. This exposes a service externally.

MetalLB attaches informational events to the services that it’s controlling. If your LoadBalancer is misbehaving, run kubectl describe service <service name> and check the event log.

We can test with this service:

vim web-app-demo.yaml

Paste below contents.

apiVersion: v1
kind: Namespace
metadata:
  name: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
  namespace: web
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: httpd
        image: httpd:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-server-service
  namespace: web
spec:
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

Apply configuration manifest:

$ kubectl apply -f web-app-demo.yaml
namespace/web created
deployment.apps/web-server created
service/web-server-service created

Let’s check the IP assigned by Load Balancer to the service:

$ kubectl get svc -n web
NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
web-server-service   LoadBalancer   10.104.209.218   192.168.1.30   80:30510/TCP   105s

Confirm the Pod is running.

$ kubectl get pod -n web
NAME                          READY   STATUS    RESTARTS   AGE
web-server-6c56696747-jpc5p   1/1     Running   0          2m11s

Test using telnet connectivity to the service.

$ telnet 192.168.1.30 80
Trying 192.168.1.30...
Connected to 192.168.1.30.
Escape character is '^]'.

We can also access the service from a web console via http://192.168.1.30

apache httpd metallb