Skip to main content

Kubernetes: MetalLB Load Balancing

Kubernetes - This article is part of a series.
Part 5: This Article

Using MetalLB to expose services on the local network
#

MetalLB provides bare-metal load balancing which we can use to expose services on our cluster to IP addresses on our local network. It is of course important that the addresses used by MetalLB are not already in use by your

Deploying MetalLB
#

Helm chart
#

First, we add modules to import the chart and set the namespace:

Note

The namespace must be set to metallb-system for the deployment to work

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.metallb-charts =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      metallbChart = {
        name = "metallb";
        repo = "https://metallb.github.io/metallb";
        version = "0.15.2";
        hash = "sha256-Tw/DE82XgZoceP/wo4nf4cn5i8SQ8z9SExdHXfHXuHM=";
      };
    in
    {
      config = lib.mkIf config.metallb.enable {
        services.k3s.autoDeployCharts = {
          metallb = metallbChart // {
            targetNamespace = "metallb-system";
            createNamespace = true;
          };
        };
      };
    };
}

Images
#

Then we preload each image used by the Helm chart:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.metallb-images =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      controllerImage = pkgs.dockerTools.pullImage {
        imageName = "quay.io/metallb/controller";
        imageDigest = "sha256:417cdb6d6f9f2c410cceb84047d3a4da3bfb78b5ddfa30f4cf35ea5c667e8c2e";
        sha256 = "sha256-AzOCFyOAeLsFw7ESAg8iYygzH4ygxgNQcJ5rpajbnio=";
        finalImageTag = "v0.15.2";
        arch = "amd64";
      };
      speakerImage = pkgs.dockerTools.pullImage {
        imageName = "quay.io/metallb/speaker";
        imageDigest = "sha256:260c9406f957c0830d4e6cd2e9ac8c05e51ac959dd2462c4c2269ac43076665a";
        sha256 = "sha256-gdy9zFJjY9wTYKuF7j5NW16V6oPWdFwEji+Nvt5Qr7Y=";
        finalImageTag = "v0.15.2";
        arch = "amd64";
      };
    in
    {
      config = lib.mkIf config.metallb.enable {
        services.k3s = {
          images = [
            controllerImage
            speakerImage
          ];
          autoDeployCharts.metallb.values = {
            controller.image = {
              repository = controllerImage.imageName;
              tag = controllerImage.imageTag;
            };
            speaker.image = {
              repository = speakerImage.imageName;
              tag = speakerImage.imageTag;
            };
          };
        };
      };
    };
}

Adding a default address pool and L2 advertisement
#

For MetalLB to work we must configure a pool of available IP Addresses and an L2 advertisement:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.metallb-settings =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      config = lib.mkIf config.metallb.enable {
        services.k3s.autoDeployCharts.metallb.extraDeploy = [
          {
            apiVersion = "metallb.io/v1beta1";
            kind = "IPAddressPool";
            metadata = {
              name = "default";
              namespace = "metallb-system";
            };
            spec = {
              addresses = [ "192.168.1.200-192.168.1.210" ];
              autoAssign = true;
            };
          }
          {
            apiVersion = "metallb.io/v1beta1";
            kind = "L2Advertisement";
            metadata = {
              name = "default";
              namespace = "metallb-system";
            };
            spec = {
              ipAddressPools = [ "default" ];
            };
          }
        ];
      };
    };
}

Exposing Services
#

Once deployed, services can be exposed using The LoadBalancer service type in Kubernetes manifests:

{
  self,
  inputs,
  ...
}:
{
  flake.modules.nixos.flaresolverr-services =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    {
      config = lib.mkIf (config.media-server.enable && config.media-server.flaresolverr.enable) {
        services.k3s.manifests.flaresolverr.content = [
          {
            apiVersion = "v1";
            kind = "Service";
            metadata = {
              name = "flaresolverr-lb";
              namespace = "media";
              annotations = {
                "metallb.io/address-pool" = "default";
                "metallb.io/allow-shared-ip" = "media";
              };
            };
            spec = {
              type = "LoadBalancer";
              loadBalancerIP = "192.168.1.202";
              selector = {
                "app" = "flaresolverr";
              };
              ports = [
                {
                  name = "http";
                  port = 8191;
                  targetPort = 8191;
                  protocol = "TCP";
                }
              ];
            };
          }
        ];
      };
    };
}

Kubernetes - This article is part of a series.
Part 5: This Article