From 7c0a74cf515a29b414f973c1130a25322e4ff9b6 Mon Sep 17 00:00:00 2001 From: juvdiaz Date: Tue, 26 May 2026 23:09:09 -0600 Subject: [PATCH] Add optional MetalLB platform support --- README.md | 23 +++++++++++++ bootstrap/platform/main.tf | 58 +++++++++++++++++++++++++++++++++ bootstrap/platform/variables.tf | 22 +++++++++++++ 3 files changed, 103 insertions(+) diff --git a/README.md b/README.md index 7082b88..ee6df90 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ accidentally modify the cluster. 3. `bootstrap/platform` - installs a minimal Calico deployment through the Tigera operator - installs NodeLocal DNSCache for node-local DNS query caching + - can install MetalLB for LAN `LoadBalancer` services after an address pool + is chosen - installs OpenEBS - creates `openebs-hostpath-retain` - installs Argo CD @@ -240,6 +242,27 @@ rollout compatible with the current kube-proxy iptables path without rewriting kubelet DNS settings across the nodes. Override `nodelocal_dns` if the service CIDR or upstream DNS servers change. +## MetalLB + +MetalLB is present in `bootstrap/platform` but disabled by default. Enable it +only after reserving a LAN IP range outside DHCP and outside any future OpenWrt +LAN pool: + +```bash +export TF_VAR_metallb='{ + enabled = true + repository = "https://metallb.github.io/metallb" + version = "0.16.0" + namespace = "metallb-system" + address_pool = ["192.168.100.240-192.168.100.250"] + l2_advertisement_enabled = true + pool_name = "homelab-lan" +}' +``` + +The current website, demos, registry, and Gitea services remain `NodePort` +services until the LAN address pool and edge route are tested manually. + ## Secrets Use SOPS with age for secrets that need to live in Git. Start from diff --git a/bootstrap/platform/main.tf b/bootstrap/platform/main.tf index 168246a..80706de 100644 --- a/bootstrap/platform/main.tf +++ b/bootstrap/platform/main.tf @@ -514,6 +514,64 @@ resource "kubernetes_manifest" "nodelocal_dns_metrics_service" { } } +resource "helm_release" "metallb" { + for_each = var.metallb.enabled ? { enabled = true } : {} + + depends_on = [null_resource.calico_ready] + name = "metallb" + repository = var.metallb.repository + chart = "metallb" + version = var.metallb.version + namespace = var.metallb.namespace + create_namespace = true + timeout = 600 + wait = true + + values = [ + yamlencode({ + frrk8s = { + enabled = false + } + }) + ] +} + +resource "kubernetes_manifest" "metallb_ip_address_pool" { + for_each = var.metallb.enabled && length(var.metallb.address_pool) > 0 ? { enabled = true } : {} + + depends_on = [helm_release.metallb] + + manifest = { + apiVersion = "metallb.io/v1beta1" + kind = "IPAddressPool" + metadata = { + name = var.metallb.pool_name + namespace = var.metallb.namespace + } + spec = { + addresses = var.metallb.address_pool + } + } +} + +resource "kubernetes_manifest" "metallb_l2_advertisement" { + for_each = var.metallb.enabled && var.metallb.l2_advertisement_enabled && length(var.metallb.address_pool) > 0 ? { enabled = true } : {} + + depends_on = [kubernetes_manifest.metallb_ip_address_pool] + + manifest = { + apiVersion = "metallb.io/v1beta1" + kind = "L2Advertisement" + metadata = { + name = var.metallb.pool_name + namespace = var.metallb.namespace + } + spec = { + ipAddressPools = [var.metallb.pool_name] + } + } +} + resource "helm_release" "openebs" { depends_on = [null_resource.calico_ready] name = "openebs" diff --git a/bootstrap/platform/variables.tf b/bootstrap/platform/variables.tf index e5bc05d..1f7acbf 100644 --- a/bootstrap/platform/variables.tf +++ b/bootstrap/platform/variables.tf @@ -112,6 +112,28 @@ variable "nodelocal_dns" { } } +variable "metallb" { + type = object({ + enabled = bool + repository = string + version = string + namespace = string + address_pool = list(string) + l2_advertisement_enabled = bool + pool_name = string + }) + + default = { + enabled = false + repository = "https://metallb.github.io/metallb" + version = "0.16.0" + namespace = "metallb-system" + address_pool = [] + l2_advertisement_enabled = true + pool_name = "homelab-lan" + } +} + variable "observability" { type = object({ namespace = string