A Kubernetes-native internal load balancer that dynamically discovers pods and configures Traefik to route TCP traffic. This project bridges Kubernetes service discovery with Traefik's flexible routing capabilities to provide automated, real-time load balancing.
The K8s Internal Load Balancer automatically:
- Discovers pods based on label selectors
- Updates Traefik configuration via REST API
- Maintains an up-to-date list of backend servers
- Provides dynamic TCP load balancing without manual intervention
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────┐ │
│ │ Pod with 2 Containers │ │
│ ├──────────────────────────────┤ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Updater Container │ │ ─┐ │
│ │ │ - Polls K8s API │ │ │ │
│ │ │ - Discovers pods │ │ │ Sidecar Pattern │
│ │ │ - Updates Traefik │ │ │ │
│ │ └──────────────────────────┘ │ │ │
│ │ ┌──────────────────────────┐ │ │ │
│ │ │ Traefik Container │ │ ─┘ │
│ │ │ - Routes TCP traffic │ │ │
│ │ │ - Listens on :3333 │ │ │
│ │ │ - Exposes metrics │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────────────▼──────────────────┐ │
│ │ Application Pods (discovered) │ │
│ │ - Pod 1: 10.0.0.1:3333 │ │
│ │ - Pod 2: 10.0.0.2:3333 │ │
│ │ - Pod N: 10.0.0.N:3333 │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
- Dynamic Pod Discovery: Continuously monitors Kubernetes API for pods matching label selectors
- Automatic Configuration: Updates Traefik backends automatically when pods are added/removed
- Smart Change Detection: Only updates Traefik when backend list actually changes
- Least Connections Load Balancing: Uses
leastconnalgorithm for optimal distribution - Kubernetes-Native: Deployed via Helm chart with full RBAC support
- Prometheus Metrics: Built-in metrics export from Traefik
- Production-Ready Security: Configurable security contexts and non-root execution
This load balancer is designed for a specific scenario that standard Kubernetes Ingress controllers don't handle well:
You need TCP load balancing for long-lived connections without modifying your existing Ingress setup.
Standard Kubernetes Ingress is built for HTTP/HTTPS traffic at Layer 7. But many applications rely on raw TCP connections that:
- Stay open for hours, days, or even weeks (database connections, message queues, game servers)
- Use custom protocols that aren't HTTP-based (MQTT, custom binary protocols, database wire protocols)
- Require connection-level load balancing, not request-level
When you have such a service and want to add load balancing:
- Option A: Modify your Ingress controller to handle TCP streams — complex, requires config changes, may affect other services
- Option B: Use a dedicated TCP load balancer — this project
This project provides a self-contained, single-service TCP load balancer that:
- Doesn't touch your Ingress — runs as a separate deployment
- Handles long-lived TCP connections — uses Traefik's TCP routing with least-connections algorithm
- Dynamically discovers backends — watches Kubernetes pods in real-time via Watch API
- Operates at Layer 4 — raw TCP, no protocol assumptions
| Scenario | Why This Load Balancer |
|---|---|
| Database connection pooling | PostgreSQL/MySQL connections that stay open for connection pools |
| Message broker clusters | RabbitMQ, Kafka, NATS with persistent consumer connections |
| Real-time services | WebSocket backends, game servers, chat systems |
| IoT gateways | MQTT brokers with thousands of long-lived device connections |
| Custom TCP protocols | Proprietary protocols that Ingress can't parse |
- For HTTP/HTTPS APIs — use standard Ingress
- When you need TLS termination with SNI routing — use Ingress with TLS
- For services that already work with your existing load balancing setup
- Kubernetes 1.20+
- Helm 3.0+
- Go 1.22+ (for building from source)
- Clone the repository:
git clone https://github.com/yourusername/k8s-internal-loadbalancer.git
cd k8s-internal-loadbalancer- Install the Helm chart:
helm install my-loadbalancer ./chart \
--set env.relay=app=my-app \
--set env.updateinterval=5s- Verify the deployment:
kubectl get pods
kubectl logs -f <pod-name> -c ilb# Build the binary
make build
# Build Docker image
make docker-build
# Run locally (requires kubeconfig)
./traefik-updaterThe load balancer is configured via environment variables:
| Variable | Description | Default | Required |
|---|---|---|---|
POD_LABELS |
Label selector for pods to discover | - | Yes |
TRAEFIK_API_URL |
Traefik REST API endpoint | http://localhost:8080/api/providers/rest |
Yes |
POD_NAMESPACE |
Kubernetes namespace to watch | Current namespace | Yes |
UPDATE_INTERVAL |
Poll interval for pod discovery | 1s |
No |
See chart/values.yaml for all available configuration options. Key settings:
env:
# Label selector for target pods
relay: "app=my-app"
# Update check interval
updateinterval: 5s
# Security context (enabled by default)
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: falseDeploy a load balancer for pods with label app=redis:
helm install redis-lb ./chart \
--set env.relay=app=redis \
--set env.updateinterval=10sUse multiple labels for pod selection:
helm install my-lb ./chart \
--set env.relay="app=myapp,tier=backend"Change the exposed service port:
# custom-values.yaml
service:
port: 8080
env:
relay: "app=my-app"helm install my-lb ./chart -f custom-values.yaml- Discovery Phase: The updater container polls the Kubernetes API at regular intervals (default 1s)
- Selection Phase: Filters pods matching the configured label selector in the specified namespace
- Comparison Phase: Compares discovered pods with the previous state
- Update Phase: If changes detected, sends new configuration to Traefik REST API
- Routing Phase: Traefik applies the new backend configuration and routes traffic using least connections algorithm
Traefik exposes Prometheus metrics on port 9090:
# Port-forward to access metrics
kubectl port-forward <pod-name> 9090:9090
# Access metrics
curl http://localhost:9090/metricsView structured JSON logs:
# Updater logs
kubectl logs -f <pod-name> -c ilb
# Traefik logs
kubectl logs -f <pod-name> -c traefik- Check label selector:
kubectl get pods -l "app=my-app"- Verify RBAC permissions:
kubectl auth can-i list pods --as=system:serviceaccount:default:my-loadbalancer- Check updater logs:
kubectl logs <pod-name> -c ilb | grep "Found pods"- Verify Traefik API is accessible:
kubectl exec -it <pod-name> -c ilb -- wget -O- http://localhost:8080/api/providers/rest- Check for API errors in logs:
kubectl logs <pod-name> -c ilb | grep "Failed to update"- Test direct connectivity to backend pods:
kubectl exec -it <pod-name> -- telnet <backend-pod-ip> 3333- Check Traefik routing configuration:
kubectl exec -it <pod-name> -c traefik -- cat /etc/traefik/traefik.yml# Set up environment variables
export POD_LABELS="app=test"
export TRAEFIK_API_URL="http://localhost:8080/api/providers/rest"
export POD_NAMESPACE="default"
export UPDATE_INTERVAL="5s"
# Run the application
go run main.gomake testmake lintFor production environments, consider:
- Enable TLS for Traefik API (modify
traefik.yml) - Implement authentication for Traefik dashboard
- Use NetworkPolicies to restrict access
- Review RBAC permissions and apply principle of least privilege
- Enable Pod Security Standards
The Helm chart includes secure defaults:
- Runs as non-root user (UID 65534)
- Read-only root filesystem
- All capabilities dropped
- Privilege escalation disabled
We welcome contributions! Please see CONTRIBUTING.md for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Replace polling with Kubernetes watch API
- Add comprehensive unit and integration tests
- Implement circuit breaker pattern for Traefik API calls
- Support for multiple Traefik instances
- Health and readiness probes
- Custom metrics export
- Helm chart repository publishing