LoadBalancer Configuration
LoadBalancer services expose applications to external traffic by automatically provisioning and configuring load balancers. KSail provides comprehensive LoadBalancer support across all distributions with distribution-specific implementations optimized 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 |
Key Points:
- K3s and Talos Ă Hetzner include LoadBalancer support by defaultâno configuration needed
- Vanilla and Talos Ă Docker require explicit enablement during cluster initialization
- LoadBalancer type is configured via
--load-balancerflag orksail.yaml - VCluster delegates LoadBalancer to the host cluster. The
spec.cluster.loadBalancersetting has no effect on VCluster clustersâKSail does not install or uninstall any LoadBalancer controller. Changing this setting will not trigger a cluster update.
Configuration Options
Section titled âConfiguration OptionsâLoadBalancer support is controlled by the --load-balancer flag or spec.cluster.loadBalancer in ksail.yaml:
| Value | Behavior |
|---|---|
Default | Use distribution Ă provider default (enabled for K3s and Talos Ă Hetzner, disabled otherwise) |
Enabled | Force LoadBalancer support on (installs controller if not provided by default) |
Disabled | Force LoadBalancer support off (prevents LoadBalancer services from getting external IPs) |
Platform-Specific Configuration
Section titled âPlatform-Specific ConfigurationâVanilla (Kind) on Docker
Section titled âVanilla (Kind) on DockerâImplementation: Cloud Provider KIND
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:
apiVersion: ksail.io/v1alpha1kind: Clusterspec: cluster: distribution: Vanilla loadBalancer: EnabledHow It Works
Section titled âHow It Worksâ- KSail starts the Cloud Provider KIND controller as a Docker container named
ksail-cloud-provider-kind(Cloud Provider KIND then creates per-service LoadBalancer containers with acpk-prefix) - The controller watches for LoadBalancer services in the cluster
- When a LoadBalancer service is created, the controller allocates an IP from the Docker network
- The service becomes accessible on that IP from your host machine
Example Service
Section titled âExample ServiceâapiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appAfter applying this service, check the external IP:
kubectl get svc my-app# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.96.1.10 172.18.0.100 80:30123/TCP 10sAccess your application:
curl http://172.18.0.100K3s on Docker
Section titled âK3s on DockerâImplementation: ServiceLB (Klipper LoadBalancer)
K3s includes ServiceLB (formerly known as Klipper LB) by default. It uses host ports to expose LoadBalancer services.
Default Configuration
Section titled âDefault ConfigurationâLoadBalancer support is enabled by defaultâno configuration needed:
ksail cluster init --name my-cluster --distribution K3sksail cluster createHow It Works
Section titled âHow It Worksâ- When a LoadBalancer service is created, K3s automatically assigns an external IP
- The IP is typically the IP of one of the cluster nodes
- Traffic is forwarded to the service using
iptablesrules
Example Service
Section titled âExample ServiceâapiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appCheck the service:
kubectl get svc my-app# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.43.123.45 192.168.1.100 80:30456/TCP 5sDisabling LoadBalancer Support
Section titled âDisabling LoadBalancer SupportâIf you want to disable LoadBalancer support (for example, to use NodePort services instead):
CLI:
ksail cluster init \ --name my-cluster \ --distribution K3s \ --load-balancer Disabledksail.yaml:
spec: cluster: distribution: K3s loadBalancer: DisabledTalos on Docker
Section titled âTalos on DockerâImplementation: MetalLB
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:
apiVersion: ksail.io/v1alpha1kind: Clusterspec: cluster: distribution: Talos provider: Docker loadBalancer: EnabledDefault IP Pool
Section titled âDefault IP PoolâKSail configures MetalLB with a default IP pool:
- IP Range:
172.18.255.200-172.18.255.250 - Mode: Layer 2 (ARP/NDP)
- Network: Docker bridge network
This range is chosen to avoid conflicts with typical Docker network allocations.
How It Works
Section titled âHow It Worksâ- KSail installs MetalLB via Helm during cluster creation
- An
IPAddressPoolandL2Advertisementare created automatically - When a LoadBalancer service is created, MetalLB assigns an IP from the pool
- MetalLB announces the IP on the Docker network using ARP
Example Service
Section titled âExample ServiceâapiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appCheck the service:
kubectl get svc my-app# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.96.123.200 172.18.255.200 80:31234/TCP 8sAccess your application:
curl http://172.18.255.200Custom 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âImplementation: hcloud-cloud-controller-manager
Talos on Hetzner Cloud uses the Hetzner Cloud Controller Manager to provision real cloud load balancers.
LoadBalancer Support
Section titled âLoadBalancer SupportâLoadBalancer is enabled by default for Talos Ă Hetzner clusters. KSail automatically installs hcloud-ccm when loadBalancer is Default or Enabled.
CLI:
export HCLOUD_TOKEN=your-token-hereksail cluster init \ --name my-cluster \ --distribution Talos \ --provider Hetznerksail cluster createksail.yaml:
apiVersion: ksail.io/v1alpha1kind: Clusterspec: cluster: distribution: Talos provider: HetznerPrerequisites:
HCLOUD_TOKENenvironment variable must be set with a valid Hetzner Cloud API token- The token requires read/write permissions for Load Balancers
How It Works
Section titled âHow It Worksâ- The Hetzner CCM runs automatically in the cluster
- When a LoadBalancer service is created, the CCM provisions a real Hetzner Cloud Load Balancer
- The load balancer gets a public IP and forwards traffic to your cluster nodes
- You are billed for the Hetzner Load Balancer resource
Example Service
Section titled âExample ServiceâapiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appCheck the service (may take 30-60 seconds to provision):
kubectl get svc my-app# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.32.45.100 <pending> 80:32100/TCP 5s
# Wait for provisioning...kubectl get svc my-app# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.32.45.100 135.181.10.50 80:32100/TCP 45sThe EXTERNAL-IP is a real public IP accessible from the internet.
Annotations
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 ports: - port: 80 targetPort: 8080 selector: app: my-appSee the Hetzner CCM documentation for all available annotations.
Migration from NodePort
Section titled âMigration from NodePortâIf you have existing services using type: NodePort, migrating to LoadBalancer is straightforward:
Before (NodePort):
apiVersion: v1kind: Servicemetadata: name: my-appspec: type: NodePort ports: - port: 80 targetPort: 8080 nodePort: 30080 # Static node port selector: app: my-appAccess via: http://<node-ip>:30080
After (LoadBalancer):
apiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appAccess via: http://<external-ip>:80
Migration Steps:
- Enable LoadBalancer support if not already enabled (see platform-specific sections)
- Update your service manifests to change
type: NodePorttotype: LoadBalancer - Remove
nodePortspecifications (theyâre unnecessary with LoadBalancer) - Apply the updated manifests:
kubectl apply -f service.yaml - Wait for the external IP to be assigned:
kubectl get svc my-app -w - Update any clients or DNS records to use the new external IP
Testing LoadBalancer Services
Section titled âTesting LoadBalancer ServicesâHereâs a complete example to test LoadBalancer functionality:
-
Create a test deployment:
nginx-deployment.yaml apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-testspec:replicas: 2selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.25ports:- containerPort: 80 -
Create a LoadBalancer service:
nginx-service.yaml apiVersion: v1kind: Servicemetadata:name: nginx-lbspec:type: LoadBalancerports:- port: 80targetPort: 80selector:app: nginx -
Apply the manifests:
Terminal window kubectl apply -f nginx-deployment.yamlkubectl apply -f nginx-service.yaml -
Check the service:
Terminal window kubectl get svc nginx-lb -w# Wait for EXTERNAL-IP to be assigned -
Test connectivity:
Terminal window EXTERNAL_IP=$(kubectl get svc nginx-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}')curl http://$EXTERNAL_IP# Should show nginx welcome page
Troubleshooting
Section titled âTroubleshootingâLoadBalancer Service Stuck in Pending
Section titled âLoadBalancer Service Stuck in PendingâSymptom: Service shows <pending> for EXTERNAL-IP:
kubectl get svc# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.96.1.50 <pending> 80:30123/TCP 5mDiagnosis:
-
Check if LoadBalancer is enabled:
Terminal window # Check ksail.yamlcat ksail.yaml | grep -A 5 "loadBalancer" -
Verify LoadBalancer controller is running:
Vanilla (Cloud Provider KIND):
Terminal window docker ps | grep ksail-cloud-provider-kind# Should show a container named ksail-cloud-provider-kindTalos (MetalLB):
Terminal window kubectl get pods -n metallb-system# Should show controller and speaker pods in Running stateHetzner:
Terminal window kubectl get pods -n kube-system | grep hcloud# Should show hcloud-cloud-controller-manager pod -
Check controller logs:
Cloud Provider KIND:
Terminal window docker logs ksail-cloud-provider-kindMetalLB:
Terminal window kubectl logs -n metallb-system -l app.kubernetes.io/component=controllerHetzner:
Terminal window kubectl logs -n kube-system -l app=hcloud-cloud-controller-manager
Common Fixes:
- LoadBalancer disabled: Re-initialize cluster with
--load-balancer Enabled - Cloud Provider KIND not running: Delete and recreate cluster
- MetalLB IP pool exhausted: Check available IPs in the pool (default: 51 IPs)
- Hetzner token missing: Ensure
HCLOUD_TOKENis set during cluster creation
Cannot Access LoadBalancer IP
Section titled âCannot Access LoadBalancer IPâSymptom: Service has external IP but connection fails:
curl http://172.18.255.200# curl: (7) Failed to connect to 172.18.255.200 port 80: Connection refusedDiagnosis:
-
Verify pods are running:
Terminal window kubectl get pods -l app=my-app -
Check service endpoints:
Terminal window kubectl get endpoints my-app# Should show pod IPs -
Test from within cluster:
Terminal window kubectl run test --rm -it --image=curlimages/curl -- shcurl http://my-app.default.svc.cluster.local -
Check pod logs:
Terminal window kubectl logs -l app=my-app
Common Fixes:
- Pods not ready: Wait for pods to reach Running state
- Wrong target port: Verify
targetPortmatches container port - Network policy blocking traffic: Check for restrictive NetworkPolicies
- Application not listening: Verify app listens on correct port and
0.0.0.0
MetalLB IP Pool Exhausted
Section titled âMetalLB IP Pool ExhaustedâSymptom: New LoadBalancer services remain pending after several successful allocations:
kubectl get svc# NAME TYPE EXTERNAL-IP PORT(S)# app-1 LoadBalancer 172.18.255.200 80:30001/TCP# app-2 LoadBalancer 172.18.255.201 80:30002/TCP# ...# app-52 LoadBalancer <pending> 80:30052/TCPDiagnosis:
Check IPAddressPool status:
kubectl get ipaddresspool -n metallb-system default-pool -o yamlFix:
Expand the IP range by creating a new pool with additional addresses:
apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: expanded-pool namespace: metallb-systemspec: addresses: - 172.18.255.200-172.18.255.254 # Expanded from .250 to .254Or create multiple pools:
apiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: additional-pool namespace: metallb-systemspec: addresses: - 172.18.254.200-172.18.254.254Next Steps
Section titled âNext Stepsâ- Support Matrix â View detailed platform compatibility
- Features â Explore other KSail capabilities
- Troubleshooting â General troubleshooting guidance
- CLI Flags â Complete CLI reference