# K3s LoadBalancer and Load Balancing Guide ## Overview K3s includes **ServiceLB** (formerly known as Klipper LoadBalancer), a lightweight load balancer that automatically exposes services on all nodes. This guide explains how it works and how to configure round-robin load balancing. ## How K3s LoadBalancer Works ### Architecture K3s LoadBalancer is fundamentally different from cloud provider load balancers (AWS, GCP, Azure). Instead of a centralized load balancer, K3s uses **local load balancing** on each node: 1. When you create a `Service` with `type: LoadBalancer`, K3s creates a DaemonSet called `svclb-` 2. This DaemonSet runs a small load balancer pod on **every node** in the cluster 3. Each node's load balancer listens on the service's external port 4. Traffic hitting any node is distributed to the service's backend pods ### Service Types in K3s ``` ┌─────────────────────────────────────────────────────────────┐ │ Service Types │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ClusterIP (default) │ │ ├─ Only accessible within cluster │ │ ├─ Internal DNS: nginx-test.default.svc.cluster.local │ │ └─ No external access │ │ │ │ NodePort │ │ ├─ Exposes service on all nodes at a high port (30000+) │ │ ├─ Accessible via : │ │ └─ Used for external access without LoadBalancer │ │ │ │ LoadBalancer (uses ServiceLB) │ │ ├─ Exposes service on all nodes at standard ports (80, 443)│ │ ├─ Accessible via : │ │ ├─ Creates DaemonSet load balancer pods on all nodes │ │ └─ Best for production external access │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ## Current Setup Analysis In your `nginx-test-deployment.yaml`: ```yaml apiVersion: v1 kind: Service metadata: name: nginx-test spec: type: ClusterIP # ← Internal only, no external load balancing ports: - port: 80 targetPort: 80 selector: app: nginx-test ``` **Current flow:** 1. Request comes to Traefik (Ingress controller) 2. Traefik forwards to ClusterIP service `nginx-test` 3. Kubernetes distributes to backend pods using **round-robin by default** **Note:** You're already using round-robin! Kubernetes services default to round-robin load balancing at the service level. ## Load Balancing Methods in Kubernetes ### 1. Service-Level Load Balancing (Default: Round-Robin) **How it works:** - When a pod connects to a service, the endpoint controller maintains a list of all healthy pods - Kubernetes distributes connections **round-robin** across backend pods - This is handled by kube-proxy in iptables/IPVS mode **Verify current method:** ```bash # Check kube-proxy mode (usually iptables or ipvs) kubectl get daemonset -n kube-system kube-proxy -o yaml | grep -A 5 "mode:" # Check available endpoints kubectl get endpoints nginx-test ``` ### 2. Traefik-Level Load Balancing Since your traffic goes through Traefik (Ingress), Traefik also applies load balancing. **Traefik load balancing strategy:** - Traefik uses round-robin by default - Distributes across all service endpoints - Can be configured with Middleware for different strategies ## Changing Load Balancing Strategy ### Option 1: Configure Traefik Load Balancer (Recommended) Add a Middleware to explicitly configure round-robin with different settings: ```yaml apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: sticky-sessions namespace: default spec: sticky: cookie: name: sticky secure: true sameSite: lax --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-test annotations: traefik.ingress.kubernetes.io/router.middlewares: default-sticky-sessions@kubernetescrd spec: ingressClassName: traefik rules: - host: nginx-test.local http: paths: - path: / pathType: Prefix backend: service: name: nginx-test port: number: 80 ``` ### Option 2: Use LoadBalancer Service Type Replace ClusterIP with LoadBalancer: ```yaml apiVersion: v1 kind: Service metadata: name: nginx-test namespace: default spec: type: LoadBalancer # ← Changed from ClusterIP sessionAffinity: None # Round-robin (default) # sessionAffinity: ClientIP # Sticky sessions (same client → same pod) ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: nginx-test ``` **sessionAffinity options:** - `None` - Pure round-robin, each new request goes to next pod - `ClientIP` - Sticky: same client IP always routes to same pod - `ClientIP` with `sessionAffinityConfig` - Sticky with timeout ### Option 3: Verify Current Round-Robin Test that round-robin is working: ```bash # Get service endpoints kubectl get endpoints nginx-test # Shows all pod IPs that nginx-test routes to # Test round-robin with multiple requests for i in {1..10}; do curl -s -H "Host: nginx-test.local" http://192.168.30.101 | grep "pod-name" done # You should see different pods in the response (showing round-robin is active) ``` ## Practical Configuration for Round-Robin ### Change Current Service to Explicit Round-Robin Edit your deployment: ```bash kubectl patch service nginx-test -p '{"spec":{"sessionAffinity":"None"}}' ``` Or update the YAML: ```yaml apiVersion: v1 kind: Service metadata: name: nginx-test namespace: default labels: app: nginx-test spec: type: LoadBalancer # Enable external load balancing sessionAffinity: None # Explicit round-robin sessionAffinityConfig: clientIP: timeoutSeconds: 0 # No timeout (stateless) ports: - port: 80 targetPort: 80 protocol: TCP name: http selector: app: nginx-test ``` ### Enable Sticky Sessions (Optional) If you want the same client to always reach the same pod: ```yaml apiVersion: v1 kind: Service metadata: name: nginx-test namespace: default spec: type: LoadBalancer sessionAffinity: ClientIP # Sticky sessions sessionAffinityConfig: clientIP: timeoutSeconds: 10800 # 3 hours timeout ports: - port: 80 targetPort: 80 selector: app: nginx-test ``` ## Monitoring Load Distribution ### Check Current Load Balancing ```bash # Watch which pods are receiving traffic kubectl get pods -l app=nginx-test -w # Check logs to see traffic distribution kubectl logs -f -l app=nginx-test --all-containers=true | grep -E "GET|POST" # See traffic pattern with timestamps for i in {1..20}; do curl -s -H "Host: nginx-test.local" http://192.168.30.101 | \ grep -o "nginx-test-[^<]*" | head -1 echo "" done ``` ### Metrics from Prometheus If you have Prometheus installed: ```promql # Requests per pod sum(rate(nginx_requests_total[1m])) by (pod) # Pod latency histogram_quantile(0.95, rate(nginx_request_duration_seconds_bucket[5m])) by (pod) ``` ## ServiceLB Details ### View ServiceLB DaemonSet ```bash # When you create a LoadBalancer service, K3s creates: kubectl get daemonset -n kube-system | grep svclb # Example output: # NAME DESIRED CURRENT READY ... # svclb-nginx-test-xxxxx 4 4 4 ... # This means the load balancer pod is running on all 4 nodes ``` ### ServiceLB Pod Details ```bash # Check ServiceLB pods kubectl get pods -n kube-system -l app=svclb # Check which node runs the load balancer kubectl get pods -n kube-system -l app=svclb -o wide # View load balancer logs kubectl logs -n kube-system -l app=svclb ``` ## Comparison: Current vs LoadBalancer Service ### Current Setup (ClusterIP + Traefik Ingress) ``` External Request (http://192.168.30.101) ↓ Traefik Pod (runs on one node) ↓ ClusterIP Service (internal DNS resolution) ↓ Load balancing: Round-robin at kube-proxy level ↓ Backend Pod (any of 15 nginx pods) ``` **Pros:** - Simple single ingress controller - Works well for HTTP/HTTPS - Traefik handles SSL/TLS termination **Cons:** - Traffic must go through Traefik first - Traefik becomes a potential bottleneck ### LoadBalancer Service ``` External Request (http://192.168.30.101:80) ↓ ServiceLB Pod (runs on every node) ↓ Load balancing: Round-robin at each node ↓ Backend Pod (any of 15 nginx pods) ``` **Pros:** - Distributed load balancing (one per node) - Lower latency (no Traefik hop) - Direct service access **Cons:** - Requires NodePort-like external access - Different pod on each node adds resources - No SSL/TLS termination (unless added) ## Recommended Configuration for Production ### For HTTP Services (using Traefik) Keep your current setup - it's optimal for HTTP/HTTPS with ingress. ```bash # Your current setup is already using round-robin! # To verify: kubectl get service nginx-test -o yaml | grep sessionAffinity # Output: sessionAffinity: None (= round-robin) ``` ### For TCP/UDP Services Use LoadBalancer type: ```yaml apiVersion: v1 kind: Service metadata: name: my-tcp-service spec: type: LoadBalancer sessionAffinity: None # Round-robin ports: - port: 5000 targetPort: 5000 protocol: TCP selector: app: my-tcp-app ``` ## Testing Round-Robin ### Test Script ```bash #!/bin/bash # Function to get pod name from response get_pod_name() { curl -s -H "Host: nginx-test.local" http://192.168.30.101 | \ grep -o "nginx-test-[^<]*" | head -1 } # Send 30 requests and count which pods receive them declare -A pod_counts for i in {1..30}; do pod=$(get_pod_name) ((pod_counts[$pod]++)) echo "Request $i: $pod" done # Print distribution echo "" echo "Distribution Summary:" for pod in "${!pod_counts[@]}"; do echo " $pod: ${pod_counts[$pod]} requests" done ``` ### Expected Output (Perfect Round-Robin) If you have 15 replicas and send 30 requests: ``` nginx-test-abc: 2 requests nginx-test-def: 2 requests nginx-test-ghi: 2 requests ... (each pod gets ~2 requests) ``` ## Quick Reference ### Check Current Load Balancing Strategy ```bash kubectl get service nginx-test -o jsonpath='{.spec.sessionAffinity}' # Output: None (round-robin) or ClientIP (sticky) ``` ### Change to Round-Robin ```bash kubectl patch service nginx-test -p '{"spec":{"sessionAffinity":"None"}}' ``` ### Change to Sticky Sessions ```bash kubectl patch service nginx-test -p '{"spec":{"sessionAffinity":"ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":10800}}}}' ``` ### Switch Service Type ```bash # From ClusterIP to LoadBalancer kubectl patch service nginx-test -p '{"spec":{"type":"LoadBalancer"}}' # Back to ClusterIP kubectl patch service nginx-test -p '{"spec":{"type":"ClusterIP"}}' ``` ## Summary Your current setup already uses **round-robin load balancing** at multiple levels: 1. **Service Level** - Kubernetes kube-proxy distributes to pods round-robin 2. **Traefik Level** - Traefik also uses round-robin across endpoints 3. **This is the recommended configuration** for HTTP/HTTPS services If you want more direct control or non-HTTP protocols, switch to LoadBalancer type, but for your nginx-test deployment, you're already optimally configured!