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 |
K3s and Talos Ă Hetzner enable LoadBalancer by default; Vanilla and Talos Ă Docker require explicit enablement. 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âAll platforms accept the same LoadBalancer service manifest:
apiVersion: v1kind: Servicemetadata: name: my-appspec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: my-appVanilla (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: EnabledArchitecture
Section titled âArchitectureâgraph TB
subgraph "Docker Host"
CPK["ksail-cloud-provider-kind container"]
SOCK["/var/run/docker.sock"]
NET["kind Docker network"]
subgraph "Kind Cluster"
CP["Control Plane"]
W1["Worker 1"]
end
subgraph "Per-Service Containers"
SVC1["cpk-default-my-app"]
SVC2["cpk-default-nginx-lb"]
end
CPK -->|"watches LoadBalancer services"| CP
CPK -->|"creates/removes"| SVC1
CPK -->|"creates/removes"| SVC2
CPK -.->|"mounts"| SOCK
SVC1 -.->|"routes traffic"| W1
SVC2 -.->|"routes traffic"| W1
NET -.->|"IP allocation"| SVC1
NET -.->|"IP allocation"| SVC2
end
USER["Host / curl"]
USER -->|"http://172.18.0.x"| SVC1
How It Works
Section titled âHow It WorksâCloud Provider KIND runs as an external Docker container named ksail-cloud-provider-kind on the kind Docker network. It mounts the Docker socket (/var/run/docker.sock) to manage container lifecycles:
- Controller container â KSail creates a single
ksail-cloud-provider-kindcontainer with restart policyunless-stopped. This container watches all Kind clusters fortype: LoadBalancerservices. - Per-service containers â For each LoadBalancer service, Cloud Provider KIND creates a dedicated container prefixed
cpk-(e.g.,cpk-default-my-app). These containers handle traffic routing from an external IP to the serviceâs pods. - IP allocation â External IPs are allocated from the
kindDocker bridge network subnet (typically172.18.0.0/16), making them accessible from your host machine. - Cleanup â When you run
ksail cluster delete, KSail stops and removes theksail-cloud-provider-kindcontainer and allcpk-*containers.
Verification
Section titled âVerificationâ# Verify the controller is runningdocker ps --filter name=ksail-cloud-provider-kind# CONTAINER ID IMAGE STATUS# abc123 registry.k8s.io/cloud-provider-kind/... Up 5 minutes
# Check service external IPkubectl 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 10s
# Test connectivity from hostcurl http://172.18.0.100K3s on Docker
Section titled âK3s on DockerâImplementation: ServiceLB (Klipper LoadBalancer)
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 createkubectl 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âTo disable LoadBalancer support:
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: 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.
Example
Section titled âExampleâ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 8sCustom 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: HetznerPrerequisite: Set HCLOUD_TOKEN to a Hetzner API token with read/write permissions for Load Balancers.
The Hetzner CCM provisions a real cloud load balancer with a public IP in 30â60 seconds (subject to Hetzner billing).
# Watch for EXTERNAL-IP assignment (takes 30â60 seconds)kubectl get svc my-app -w# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# my-app LoadBalancer 10.32.45.100 135.181.10.50 80:32100/TCP 45sAnnotations
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âChange type: NodePort to type: LoadBalancer, remove nodePort fields, enable LoadBalancer if needed (see above), then wait for an external IP: kubectl get svc my-app -w.
Before (NodePort):
apiVersion: v1kind: Servicemetadata: name: my-appspec: type: NodePort ports: - port: 80 targetPort: 8080 nodePort: 30080 # Static node port selector: app: my-appAfter (LoadBalancer): Use the shared manifest above, accessible via http://<external-ip>:80.
Testing LoadBalancer Services
Section titled âTesting LoadBalancer ServicesâDeploy nginx to verify LoadBalancer functionality:
kubectl create deployment nginx-test --image=nginx:1.25 --replicas=2apiVersion: v1kind: Servicemetadata: name: nginx-lbspec: type: LoadBalancer ports: - port: 80 targetPort: 80 selector: app: nginx-testkubectl apply -f nginx-lb.yamlEXTERNAL_IP=$(kubectl get svc nginx-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}')curl http://$EXTERNAL_IPTroubleshooting
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 cat ksail.yaml | grep -A 5 "loadBalancer" -
Verify the controller is running:
- Vanilla:
docker ps | grep ksail-cloud-provider-kind - Talos:
kubectl get pods -n metallb-system - Hetzner:
kubectl get pods -n kube-system | grep hcloud
- Vanilla:
-
Check controller logs:
- 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-manager
- Vanilla:
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:
kubectl get pods -l app=my-appâ verify pods are runningkubectl get endpoints my-appâ check endpoints have pod IPskubectl logs -l app=my-appâ review pod logskubectl run test --rm --image=curlimages/curl -- sh -c 'curl http://my-app.default.svc.cluster.local'â test from within cluster
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-52 LoadBalancer <pending> 80:30052/TCPDiagnosis:
Check IPAddressPool status:
kubectl get ipaddresspool -n metallb-system default-pool -o yamlFix:
Expand the pool range or 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 .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