Bare-metal Kubernetes load balancer (MetalLB alternative) — IPv4/IPv6 dual-stack, multiple LB algorithms, ARP layer-2, and Prometheus metrics.
Helios Load Balancer is a Kubernetes controller that provides load balancing functionality for bare metal Kubernetes clusters, similar to MetalLB. It automatically assigns IP addresses to LoadBalancer services and manages the network configuration.
- Automatic IP address allocation for LoadBalancer services
- Support for IP ranges in CIDR, range, or single IP format
- IPv4 and IPv6 dual-stack support
- Multiple load balancing methods (RoundRobin, LeastConnection, WeightedRoundRobin, IPHash, Random)
- Per-service backend weights for WeightedRoundRobin
- Multiple HeliosConfig resources per cluster with independent IP ranges
- Namespace isolation via
namespaceSelector - Per-config IP allocation quota via
maxAllocations - Configurable health checks (TCP/HTTP, custom timeout and interval)
- CRD schema validation (CEL) for port/weight uniqueness, method-weight consistency, and health check config
- Optional validating webhook for IP range format and cross-config overlap validation
- ARP-based layer 2 mode
- Pluggable algorithm interface for custom load balancing strategies
- Prometheus metrics support
- Status monitoring and reporting
The controller:
- Watches for services of type LoadBalancer
- Allocates IP addresses from configured IP ranges
- Configures network interfaces with virtual IPs
- Manages ARP announcements for layer 2 connectivity
- Updates service status with allocated external IPs
Helios Load Balancer can coexist with MetalLB in the same cluster if properly configured:
-
Configure different IP ranges for each load balancer:
# MetalLB IPAddressPool apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: first-pool namespace: metallb-system spec: addresses: - 192.168.1.100-192.168.1.200 # MetalLB range
# Helios-LB HeliosConfig apiVersion: balancer.helios.dev/v1 kind: HeliosConfig metadata: name: heliosconfig-sample spec: ipRange: "192.0.2.65" # Helios-LB range
-
When creating a LoadBalancer service, specify which load balancer should handle it by using the appropriate IP range:
# Service using MetalLB apiVersion: v1 kind: Service metadata: name: metallb-service spec: type: LoadBalancer loadBalancerIP: "192.168.1.100" # MetalLB range
# Service using Helios-LB apiVersion: v1 kind: Service metadata: name: helios-service spec: type: LoadBalancer loadBalancerIP: "192.0.2.65" # Helios-LB range
This way, you can leverage both load balancers in your cluster, each managing its own IP range.
spec.loadBalancerIP does double duty here. It selects which load balancer picks
up the Service, and Helios-LB then assigns exactly that address — the request is
honored, never silently swapped for another one. If the address is already taken
by another Service, allocation fails and the reason is recorded on the
HeliosConfig status rather than a different IP being handed out. Omit the field
to take the next free address from the pool.
The address must be one the pool can actually hand out. For a CIDR range that
excludes the network and broadcast addresses, so 192.0.2.0 and 192.0.2.255
are not valid requests against 192.0.2.0/24.
- Kubernetes v1.25+ (the CRD ships CEL validation rules, which require
x-kubernetes-validations) - kubectl v1.11.3+
Recommended: OCI registry (Helm 3.8+)
# Single-command install — no helm repo add needed
helm install helios-lb oci://ghcr.io/somaz94/charts/helios-lb \
--version 0.8.0 \
--namespace helios-lb-system --create-namespaceAlternative: classic Helm repo
# Add the Helm repository
helm repo add helios-lb https://somaz94.github.io/helios-lb/helm-repo
helm repo update
# Install with default values
helm install helios-lb helios-lb/helios-lb
# Or install with custom values
helm install helios-lb helios-lb/helios-lb \
--set image.tag=v0.8.0 \
--namespace helios-lb-system --create-namespaceFor full Helm chart options, see Helm README.
kubectl apply -f https://raw.githubusercontent.com/somaz94/helios-lb/main/dist/install.yaml# Clone the repository
git clone https://github.com/somaz94/helios-lb.git
cd helios-lb
# Install CRDs
make install
# Deploy the controller
make deploy IMG=somaz940/helios-lb:v0.8.0apiVersion: balancer.helios.dev/v1
kind: HeliosConfig
metadata:
name: heliosconfig-sample
spec:
# IP range for virtual IP allocation (CIDR or range format) default port: 80 & default protocol: tcp
# Supports: single IP, range (192.168.1.100-200), CIDR (192.168.1.0/24), IPv6 (fd00::1, fd00::/120)
ipRange: "192.0.2.65"
ipv6Range: "fd00::1" # Optional: enables dual-stack (IPv4 + IPv6)
method: RoundRobin # RoundRobin, LeastConnection, WeightedRoundRobin, IPHash, Random
ports: # Optional: default is 80
- port: 80
- port: 443
protocol: TCP # Optional: default is TCP
namespaceSelector: # Optional: restrict to specific namespaces
- default
- production
maxAllocations: 10 # Optional: limit IP allocations (0 = unlimited)
# Download the sample yaml file
# default port: 80 & default protocol: tcp
curl -o heliosconfig.yaml https://raw.githubusercontent.com/somaz94/helios-lb/main/release/examples/balancer_v1_heliosconfig.yaml
# port: 80,443 & protocol: tcp
curl -o heliosconfig-port.yaml https://raw.githubusercontent.com/somaz94/helios-lb/main/release/examples/balancer_v1_heliosconfig_port.yaml
# dual-stack (IPv4 + IPv6)
curl -o heliosconfig-dualstack.yaml https://raw.githubusercontent.com/somaz94/helios-lb/main/release/examples/balancer_v1_heliosconfig_dualstack.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
labels:
app: nginx-test
spec:
replicas: 3
selector:
matchLabels:
app: nginx-test
template:
metadata:
labels:
app: nginx-test
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: nginx-test
spec:
type: LoadBalancer
loadBalancerClass: helios-lb
loadBalancerIP: "192.0.2.65"
ports:
- port: 80
targetPort: 80
protocol: TCP
selector:
app: nginx-test
# Download the sample yaml file
curl -o nginx-test.yaml https://raw.githubusercontent.com/somaz94/helios-lb/main/release/examples/nginx-test.yamlCheck the service status:
kubectl get svc nginx-test
...
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-test LoadBalancer 10.96.0.189 192.0.2.65 80:30396/TCP 2s
k get heliosconfigs
NAME PHASE MESSAGE AGE
heliosconfig-sample-port Active IP allocated successfully 26sYou should see an external IP assigned from your configured IP range.
ipRange: IP range for allocation (required) - supports single IP (192.168.1.100), range format (192.168.1.100-192.168.1.200), CIDR format (192.168.1.0/24), and IPv6 (fd00::1,fd00::1-fd00::ff,fd00::/120)ipv6Range: IPv6 address range for dual-stack allocation (optional) - when set alongsideipRange, enables dual-stack mode where both IPv4 and IPv6 addresses are allocated to each servicemethod: Load balancing method (RoundRobin,LeastConnection,WeightedRoundRobin,IPHash,Random)ports: Port configuration for the service (default: 80)protocol: Protocol type (default: TCP)weights: Per-service backend weights for WeightedRoundRobin (optional)serviceName: Name of the Kubernetes serviceweight: Relative weight (1-100, default: 1)
namespaceSelector: List of namespaces this config manages (optional, empty = all namespaces)maxAllocations: Maximum number of IP allocations for this config (optional, 0 = unlimited)healthCheck: Health check configuration (optional)enabled: Enable/disable health checking (default: true)intervalSeconds: Interval between health checks in seconds (default: 5, range: 1-300)timeoutMs: Health check timeout in milliseconds (default: 1000, range: 1-30000)protocol: Health check protocol -TCPorHTTP(default: TCP)httpPath: HTTP path for HTTP health checks (e.g.,/healthz)
allocatedIPs: Map of service names to their allocated IPv4 addressesallocatedIPv6s: Map of service names to their allocated IPv6 addresses (dual-stack only)phase: Current phase of the HeliosConfig (Pending,Active,Failed)state: Current state (same as phase, for backward compatibility)message: Human-readable status messageconditions: Standard Kubernetes conditions:Ready: Whether the HeliosConfig is successfully allocating IPsDegraded: Whether there are issues (e.g., IP conflicts)
When multiple HeliosConfig resources exist in a cluster, Helios-LB automatically detects IP range overlaps:
- If an IP already allocated by another HeliosConfig falls within the current config's range, a conflict is reported
- Conflicting configs are marked with
Degraded=Truecondition andIPConflictreason - A Kubernetes warning event
IPConflictis emitted - The controller requeues with a 30-second delay to allow resolution
The controller emits the following events on HeliosConfig resources:
| Event | Type | Description |
|---|---|---|
IPAllocated |
Normal | IP successfully allocated to a service |
IPConflict |
Warning | IP range overlaps with another HeliosConfig |
QuotaExceeded |
Warning | Max allocations limit reached |
AllocationFailed |
Warning | Failed to allocate IP for a service |
CleanupStarted |
Normal | Releasing allocated IPs during deletion |
CleanupComplete |
Normal | All IPs released and finalizer removed |
These rules are part of the CRD schema (field constraints and CEL x-kubernetes-validations), so they are enforced on every cluster with no extra setup — including when the webhook is disabled:
| Rule | Message |
|---|---|
spec.weights may only be set when spec.method is WeightedRoundRobin |
weights can only be used with the WeightedRoundRobin method |
spec.ports[*].port must be unique |
duplicate port in spec.ports |
spec.weights[*].serviceName must be unique and non-empty |
duplicate serviceName in spec.weights |
spec.healthCheck.httpPath is required when protocol is HTTP |
httpPath is required when the health check protocol is HTTP |
The webhook adds the checks that the CRD schema cannot express — IP range format (single IP / range / CIDR, IPv4 and IPv6) and IP range overlap between different HeliosConfig resources, which requires reading other objects in the cluster.
The webhook is disabled by default and can be enabled in three ways:
-
Without webhook (default): IP range format and cross-config overlap are not checked at admission; a malformed range surfaces as a reconcile error instead. All schema rules in the table above still apply.
-
With webhook + cert-manager: Recommended for production. cert-manager automatically provisions and rotates TLS certificates.
# Helm helm install helios-lb helios-lb/helios-lb \ --set webhook.enabled=true \ --set webhook.certManager.enabled=true # Kustomize: uncomment [WEBHOOK] and [CERTMANAGER] sections in config/default/kustomization.yaml
-
With webhook, without cert-manager: You must manually create a TLS Secret named
webhook-server-certin the controller namespace containingtls.crtandtls.key.
The controller uses the following system ports:
- Health probe endpoint:
:9082 - Metrics endpoint (if enabled):
:8443
These ports should be available when running the controller with hostNetwork enabled.
To ensure proper handling of services between MetalLB and Helios-LB, use the loadBalancerClass field:
apiVersion: v1
kind: Service
metadata:
name: nginx-test
spec:
type: LoadBalancer
loadBalancerClass: helios-lb # Specifies that Helios-LB should handle this service
loadBalancerIP: "192.0.2.65"
ports:
- port: 80
targetPort: 80
selector:
app: nginx-testThe controller automatically adds the necessary annotations and specifications:
- Adds annotation:
balancer.helios.dev/load-balancer-class: helios-lb - Sets spec:
loadBalancerClass: helios-lb
This ensures that:
- MetalLB ignores services marked for Helios-LB
- Helios-LB only processes services specifically marked for it
- No conflicts occur between the two load balancers
Helios-LB supports the following load balancing algorithms:
-
Round Robin (Default)
spec: method: RoundRobin
- Distributes requests sequentially across all backends
- Ensures even distribution of traffic
-
Least Connection
spec: method: LeastConnection
- Directs traffic to backend with fewest active connections
- Ideal for long-lived connections
-
Weighted Round Robin
spec: method: WeightedRoundRobin
- Distributes traffic based on backend weights
- Higher-weight backends receive more traffic
-
IP Hash
spec: method: IPHash
- Routes traffic based on client IP hash
- Provides session persistence (same client → same backend)
-
Random
spec: method: Random
- Randomly selects a healthy backend
- Simple and effective for homogeneous backends
Common issues and solutions:
-
IP allocation fails:
- Verify IP range configuration
- Check network interface exists
- Review controller logs
-
Service external IP not assigned:
- Verify HeliosConfig exists and is valid
- Check controller logs for errors
- Verify network interface configuration
-
Network connectivity issues:
- Check ARP announcements
- Verify network interface configuration
- Ensure IP range is valid for your network
- Delete the LoadBalancer service:
kubectl delete -f https://raw.githubusercontent.com/somaz94/helios-lb/main/release/examples/nginx-test.yaml- Delete the HeliosConfig:
k get heliosconfig
kubectl delete heliosconfig <heliosconfig-name>- Remove the controller:
kubectl delete -f https://raw.githubusercontent.com/somaz94/helios-lb/main/dist/install.yamlAll required tools will be automatically downloaded to ./bin directory when running:
make install-toolsOr you can install individual tools:
# Install controller-gen
make controller-gen # v0.16.4
# Install kustomize
make kustomize # v5.5.0
# Install setup-envtest
make envtest # v0.19.0
# Install golangci-lint
make golangci-lint # v2.1.6Manual installation locations:
- All tools will be installed in
./bindirectory - Specific versions:
- controller-gen v0.16.4
- kustomize v5.5.0
- setup-envtest v0.19.0
- golangci-lint v2.1.6
Note: The binary directory (./bin) is git-ignored and will be created when needed.
| Document | Description |
|---|---|
| Helm Chart | Helm chart installation, configuration, and values reference |
| Troubleshooting | Common issues and solutions |
| Version Bump | Checklist for releasing a new version |
| Contributing | How to contribute to this project |
Issues and pull requests are welcome.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.