Overview
This guide demonstrates how to expose an Nginx web server using MetalLB load balancer in a K3s Kubernetes cluster. MetalLB is a load-balancer implementation for bare metal Kubernetes clusters that uses standard routing protocols, providing external IP addresses for services of type LoadBalancer.
Key Concepts
LoadBalancer Service Type: In Kubernetes, a Service of type LoadBalancer automatically provisions an external load balancer. On bare metal clusters without cloud provider integration, MetalLB fulfills this role by allocating external IPs from a configured pool and announcing them via Layer 2 (ARP/NDP) or BGP protocols.
MetalLB Operating Modes:
- Layer 2 Mode: Uses ARP (IPv4) or NDP (IPv6) to announce the external IP on the local network. Simple to configure, no router cooperation needed.
- BGP Mode: Integrates with network routers using Border Gateway Protocol for advanced routing scenarios and true load balancing across multiple nodes.
Prerequisites
Before deploying, ensure:
- K3s cluster is running (K3s includes Traefik by default, but MetalLB provides LoadBalancer IPs)
- MetalLB is installed and configured with an IPAddressPool
- kubectl is configured to access your cluster
Quick Deployment Methods
Method 1: Imperative Commands (Quick Testing)
1kubectl create deploy nginx --image nginx:latest2kubectl expose deploy nginx --port 3000 --target-port 80 --type LoadBalancerCommand Breakdown:
kubectl create deploy nginx: Creates a Deployment named "nginx" with default replica count (1)--image nginx:latest: Uses the latest Nginx image from Docker Hubkubectl expose deploy nginx: Creates a Service resource for the nginx Deployment--port 3000: External port that clients will connect to--target-port 80: Internal container port where Nginx listens--type LoadBalancer: Requests an external IP from MetalLB
Method 2: Declarative YAML (Production Recommended)
1apiVersion: apps/v12kind: Deployment3metadata:4 name: nginx5spec:6 replicas: 27 selector:8 matchLabels:9 app: nginx10 template:11 metadata:12 labels:13 app: nginx14 spec:15 containers:16 - name: nginx17 image: nginx:latest18 ports:19 - containerPort: 8020---21apiVersion: v122kind: Service23metadata:24 name: nginx25spec:26 type: LoadBalancer27 selector:28 app: nginx29 ports:30 - port: 300031 targetPort: 80Manifest Explanation:
- replicas: 2: Runs two Nginx pod instances for high availability
- selector.matchLabels: Links Service to Pods with label
app: nginx - containerPort: 80: Exposes container port 80 (Nginx default)
- type: LoadBalancer: Triggers MetalLB to assign an external IP
- port: 3000: External service port
- targetPort: 80: Routes traffic to container port 80
Deployment and Verification
Deploy the Application
1# Apply the declarative manifest2kubectl apply -f nginx-lb.yaml3
4# Verify deployment status5kubectl get deployments6kubectl get pods -l app=nginx -o wide7
8# Check service and external IP assignment9kubectl get svc nginxExpected Output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.43.123.45 192.168.1.240 3000:30123/TCP 1m
The EXTERNAL-IP field shows the IP assigned by MetalLB from its configured pool.
Verify Connectivity
1# Test the external IP (replace with your assigned IP)2curl http://192.168.1.240:30003
4# Check MetalLB speaker logs5kubectl logs -n metallb-system -l component=speaker6
7# Verify service endpoints8kubectl get endpoints nginxAdvanced Configuration Options
Client IP Preservation
By default, traffic routed through a LoadBalancer may obscure the original client IP. To preserve source IPs, configure externalTrafficPolicy: Local:
1apiVersion: v12kind: Service3metadata:4 name: nginx5spec:6 type: LoadBalancer7 externalTrafficPolicy: Local8 selector:9 app: nginx10 ports:11 - port: 300012 targetPort: 80Benefits:
- Preserves client source IP addresses
- Traffic is routed directly to nodes hosting the pods
- Useful for logging, security policies, and IP-based access control
Trade-offs:
- Nodes without local endpoints are excluded from load balancing
- May result in uneven traffic distribution
Scaling the Deployment
Adjust the number of Nginx replicas based on load requirements:
1# Scale to 5 replicas2kubectl scale deployment nginx --replicas=53
4# Verify scaled pods5kubectl get pods -l app=nginx6
7# Enable horizontal pod autoscaling (requires metrics-server)8kubectl autoscale deployment nginx --min=2 --max=10 --cpu-percent=80Autoscaling Behavior:
- Monitors CPU utilization across pods
- Scales up when average CPU exceeds 80%
- Scales down when demand decreases
- Maintains minimum 2 and maximum 10 replicas
Resource Limits and Requests
Define resource constraints for production environments:
1apiVersion: apps/v12kind: Deployment3metadata:4 name: nginx5spec:6 replicas: 27 selector:8 matchLabels:9 app: nginx10 template:11 metadata:12 labels:13 app: nginx14 spec:15 containers:16 - name: nginx17 image: nginx:latest18 ports:19 - containerPort: 8020 resources:21 requests:22 memory: "64Mi"23 cpu: "100m"24 limits:25 memory: "128Mi"26 cpu: "200m"Resource Management:
- requests: Guaranteed minimum resources for scheduling
- limits: Maximum resources the container can consume
- Prevents resource starvation and improves cluster stability
MetalLB Configuration Reference
Layer 2 Mode Configuration
1apiVersion: metallb.io/v1beta12kind: IPAddressPool3metadata:4 name: first-pool5 namespace: metallb-system6spec:7 addresses:8 - 192.168.1.240-192.168.1.2509---10apiVersion: metallb.io/v1beta111kind: L2Advertisement12metadata:13 name: l2-advert14 namespace: metallb-system15spec:16 ipAddressPools:17 - first-poolLayer 2 Characteristics:
- Uses ARP/NDP for IP announcement
- All traffic for a service goes to one node (elected leader)
- Automatic failover if the leader node becomes unavailable
- No router configuration required
BGP Mode Configuration
1apiVersion: metallb.io/v1beta12kind: IPAddressPool3metadata:4 name: bgp-pool5 namespace: metallb-system6spec:7 addresses:8 - 192.168.1.240/299 autoAssign: true10---11apiVersion: metallb.io/v1beta212kind: BGPPeer13metadata:14 name: router-peer15 namespace: metallb-system16spec:17 myASN: 6451218 peerASN: 6451219 peerAddress: 192.168.1.120---21apiVersion: metallb.io/v1beta122kind: BGPAdvertisement23metadata:24 name: bgp-advert25 namespace: metallb-system26spec:27 ipAddressPools:28 - bgp-pool29 communities:30 - 65535:65282BGP Mode Advantages:
- True load balancing across multiple nodes
- Better suited for production environments
- Integrates with existing network infrastructure
- Supports advanced routing policies and communities
Troubleshooting Guide
Service Not Getting External IP
1# Check MetalLB controller logs2kubectl logs -n metallb-system -l app=metallb,component=controller3
4# Verify IPAddressPool configuration5kubectl get ipaddresspools -n metallb-system6
7# Check L2Advertisement or BGPAdvertisement8kubectl get l2advertisements -n metallb-system9kubectl get bgpadvertisements -n metallb-systemCommon Issues:
- IPAddressPool range exhausted or not defined
- Advertisement resource missing or misconfigured
- Network policy blocking MetalLB speaker communication
Pods Not Receiving Traffic
1# Check service endpoints2kubectl describe svc nginx3
4# Verify pod readiness5kubectl get pods -l app=nginx6
7# Test service internally8kubectl run test-pod --rm -it --image=busybox -- wget -O- http://nginx:3000Testing Network Connectivity
1# Check connectivity from another node2curl http://<EXTERNAL-IP>:30003
4# Verify ARP table (Layer 2 mode)5arp -a | grep <EXTERNAL-IP>6
7# Check MetalLB speaker on specific node8kubectl logs -n metallb-system <speaker-pod-name>Production Best Practices
- Use Declarative Manifests: Always use YAML files for reproducible deployments
- Configure Resource Limits: Prevent resource exhaustion with proper limits
- Enable Health Checks: Implement readiness and liveness probes
- Version Pin Images: Use specific tags instead of
:latest - Monitor External IPs: Track IP allocation and usage from MetalLB pools
- Plan IP Ranges: Ensure IPAddressPool ranges don't conflict with DHCP or other services
- Document Network Topology: Keep records of BGP ASNs, peer relationships, and IP assignments
Enhanced Deployment with Health Checks
1apiVersion: apps/v12kind: Deployment3metadata:4 name: nginx5 labels:6 app: nginx7spec:8 replicas: 39 selector:10 matchLabels:11 app: nginx12 template:13 metadata:14 labels:15 app: nginx16 spec:17 containers:18 - name: nginx19 image: nginx:1.25.3 # Pinned version20 ports:21 - containerPort: 8022 protocol: TCP23 resources:24 requests:25 memory: "64Mi"26 cpu: "100m"27 limits:28 memory: "128Mi"29 cpu: "200m"30 livenessProbe:31 httpGet:32 path: /33 port: 8034 initialDelaySeconds: 3035 periodSeconds: 1036 readinessProbe:37 httpGet:38 path: /39 port: 8040 initialDelaySeconds: 541 periodSeconds: 5Health Check Benefits:
- livenessProbe: Restarts container if it becomes unresponsive
- readinessProbe: Removes pod from service endpoints until ready
- Ensures traffic only reaches healthy pods
- Improves reliability during rolling updates