LoadBalancer Configuration
LoadBalancer services expose applications to external traffic. KSail supports LoadBalancer across all distributions with distribution-specific implementations for each platform.
Overview
Section titled “Overview”LoadBalancer support varies by Kubernetes distribution and infrastructure provider:
| Distribution | Provider | Implementation | Default Behavior | Configuration Required | | --------------- | -------- | --------------------------------- | ---------------- | ---------------------- | | Vanilla (Kind) | Docker | Cloud Provider KIND | Disabled | Yes | | K3s (K3d) | Docker | ServiceLB (Klipper) | Enabled | No | | Talos | Docker | MetalLB | Disabled | Yes | | Talos | Hetzner | hcloud-cloud-controller-manager | Enabled | No | | VCluster (Vind) | Docker | Delegated to host cluster | N/A | N/A |
VCluster delegates LoadBalancer to the host cluster — spec.cluster.loadBalancer has no effect and never triggers a cluster update. Use the --load-balancer flag or spec.cluster.loadBalancer in ksail.yaml with values Default, Enabled, or Disabled.
Platform-Specific Configuration
Section titled “Platform-Specific Configuration”Vanilla (Kind) on Docker
Section titled “Vanilla (Kind) on Docker”Vanilla clusters use the Cloud Provider KIND controller, which runs as an external Docker container and allocates LoadBalancer IPs from the Docker bridge network.
Enable LoadBalancer Support
Section titled “Enable LoadBalancer Support”CLI:
ksail cluster init \ --name my-cluster \ --distribution Vanilla \ --load-balancer Enabledksail.yaml:
spec: cluster: distribution: Vanilla loadBalancer: EnabledHow It Works
Section titled “How It Works”Cloud Provider KIND runs as a single Docker container named ksail-cloud-provider-kind on the kind network, mounting the Docker socket to watch for type: LoadBalancer services. For each service it creates a dedicated cpk-<namespace>-<name> container that routes traffic from an IP on the kind network bridge subnet (typically 172.18.0.0/16) to the service's pods. ksail cluster delete removes the controller and all cpk-* containers.
K3s on Docker
Section titled “K3s on Docker”K3s includes ServiceLB by default, assigning the cluster node's IP as the external IP and forwarding traffic via iptables. No configuration needed:
ksail cluster init --name my-cluster --distribution K3sksail cluster createTo disable: use --load-balancer Disabled (CLI) or set loadBalancer: Disabled in ksail.yaml.
Talos on Docker
Section titled “Talos on Docker”Talos on Docker uses MetalLB to provide LoadBalancer services. MetalLB operates in Layer 2 mode and allocates IPs from a pre-configured pool.
Enable LoadBalancer Support
Section titled “Enable LoadBalancer Support”CLI:
ksail cluster init \ --name my-cluster \ --distribution Talos \ --load-balancer Enabledksail.yaml:
spec: cluster: distribution: Talos provider: Docker loadBalancer: EnabledKSail configures MetalLB with a default IP pool of 172.18.255.200–172.18.255.250 in Layer 2 (ARP/NDP) mode on the Docker bridge network, chosen to avoid conflicts with typical Docker allocations.
How It Works
Section titled “How It Works”KSail installs MetalLB via Helm, configures an IPAddressPool and L2Advertisement automatically, and MetalLB assigns IPs from the pool via ARP on the Docker network.
Custom IP Pool
Section titled “Custom IP Pool”To use a custom IP range, you'll need to create custom MetalLB resources after cluster creation:
apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: custom-pool namespace: metallb-systemspec: addresses: - 172.18.100.1-172.18.100.254---apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata: name: custom-l2 namespace: metallb-systemspec: ipAddressPools: - custom-poolTalos on Hetzner
Section titled “Talos on Hetzner”Talos on Hetzner Cloud uses the Hetzner Cloud Controller Manager to provision real cloud load balancers. LoadBalancer is enabled by default — KSail automatically installs hcloud-ccm when loadBalancer is Default or Enabled.
Prerequisites: Set HCLOUD_TOKEN to a Hetzner API token with read/write permissions for Load Balancers.
export HCLOUD_TOKEN=your-token-hereksail cluster init \ --name my-cluster \ --distribution Talos \ --provider Hetznerksail cluster createThe Hetzner CCM provisions a real cloud load balancer with a public IP in 30–60 seconds (subject to Hetzner billing).
kubectl get svc my-app -w # EXTERNAL-IP appears after 30–60 secondsAnnotations
Section titled “Annotations”You can customize Hetzner Load Balancer behavior using annotations:
apiVersion: v1kind: Servicemetadata: name: my-app annotations: load-balancer.hetzner.cloud/location: nbg1 load-balancer.hetzner.cloud/use-private-ip: "true" load-balancer.hetzner.cloud/health-check-interval: "15s"spec: type: LoadBalancer selector: app: my-app ports: - port: 80 targetPort: 8080See the Hetzner CCM documentation for all available annotations.
Troubleshooting
Section titled “Troubleshooting”LoadBalancer Service Stuck in Pending
Section titled “LoadBalancer Service Stuck in Pending”Symptom: EXTERNAL-IP shows <pending>.
Diagnosis:
# Check loadBalancer setting (absent field = Default; Default auto-enables on K3s and Talos×Hetzner):grep 'loadBalancer:' ksail.yaml || echo "(not set — effective value: Default)"# Vanilla: docker logs ksail-cloud-provider-kind# Talos: kubectl logs -n metallb-system -l app.kubernetes.io/component=controller# Hetzner: kubectl logs -n kube-system -l app=hcloud-cloud-controller-managerCommon fixes: LoadBalancer disabled → re-init with --load-balancer Enabled. Cloud Provider KIND not running → delete and recreate cluster. MetalLB IP pool exhausted → expand the pool (see below). Hetzner → ensure HCLOUD_TOKEN is set during cluster creation.
Cannot Access LoadBalancer IP
Section titled “Cannot Access LoadBalancer IP”Symptom: Service has an external IP but connections fail.
Diagnosis:
kubectl get pods -l app=my-app # verify pods are Runningkubectl get endpoints my-app # check endpoint IPs are populatedkubectl logs -l app=my-app # review pod logs# Test from within the cluster:kubectl run test --rm -it --restart=Never --image=curlimages/curl -- curl http://my-app.default.svc.cluster.localCommon fixes: Wrong targetPort → match the container port. Network policy blocking traffic → inspect NetworkPolicy resources. Application not listening on 0.0.0.0 → fix app bind address.
MetalLB IP Pool Exhausted
Section titled “MetalLB IP Pool Exhausted”Symptom: New LoadBalancer services remain <pending> after many allocations (default pool has 51 IPs: 172.18.255.200–172.18.255.250).
Fix: Create an additional IPAddressPool in metallb-system:
apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: expanded-pool namespace: metallb-systemspec: addresses: - 172.18.255.200-172.18.255.254 # Expanded from .250 to .254