4.8 External Traffic Ingress: NodePort, Cloud LoadBalancers & MetalLB
Key Takeaways
- Exposing services externally in Kubernetes can be achieved via Layer 4 mechanisms (NodePort, Cloud LoadBalancer, MetalLB) or Layer 7 mechanisms (Ingress, Gateway API).
- NodePort allocates a static port (30000-32767) on all node IP addresses; external traffic reaching any node is forwarded via kube-proxy to matching backend Pods.
- In cloud environments, type: LoadBalancer delegates external VIP provisioning to the cloud provider via the Cloud Controller Manager (CCM).
- On bare-metal clusters, MetalLB solves the indefinite 'Pending' external IP state of LoadBalancer services using Layer 2 (ARP/NDP) or Layer 3 (BGP) routing modes.
- MetalLB Layer 2 mode utilizes memberlist leader election for single-node VIP ARP response, while MetalLB BGP mode enables true Equal-Cost Multi-Path (ECMP) multi-node load balancing.
4.8 External Traffic Ingress: NodePort, Cloud LoadBalancers & MetalLB
While internal cluster microservices communicate seamlessly using ClusterIP Services and CoreDNS, production Kubernetes clusters must expose services to external clients, corporate networks, and the public internet.
Kubernetes provides two foundational Layer 4 external Service types: NodePort and LoadBalancer. On public cloud platforms (AWS, GCP, Azure), type: LoadBalancer is handled natively by cloud controllers. However, on bare-metal and on-premises infrastructure, clusters lack native cloud APIs, causing type: LoadBalancer services to remain stuck in <Pending> status indefinitely. The open-source MetalLB project resolves this fundamental bare-metal limitation.
1. NodePort Service Architecture & Mechanics
A NodePort Service is the simplest mechanism to route external Layer 4 traffic into a Kubernetes cluster.
+-----------------------------------------------------------------------------------------+
| NODEPORT TRAFFIC INGRESS |
| |
| External Client (Requests http://192.168.1.10:30080) |
| | |
| +-----------------------+-----------------------+ |
| | | | |
| v v v |
| [ Worker Node 01 ] [ Worker Node 02 ] [ Worker Node 03 ] |
| (Host IP: 192.168.1.10) (Host IP: 192.168.1.20) (Host IP: 192.168.1.30) |
| Port 30080 OPEN Port 30080 OPEN Port 30080 OPEN |
| | | |
| v (DNAT to ClusterIP) v (DNAT to ClusterIP) |
| [ Pod A (10.244.1.5) ] [ Pod B (10.244.3.12) ] |
+-----------------------------------------------------------------------------------------+
NodePort Characteristics:
- Port Allocation Range: Allocated dynamically (or statically assigned) from
30000–32767(configurable via API server--service-node-port-range). - Universal Binding: The port is opened on every single worker and control plane node in the cluster across all network interfaces (
0.0.0.0). - Routing: Incoming traffic to
<NodeIP>:<NodePort>is intercepted bykube-proxyNetfilter/IPVS rules, rewritten to the Service'sClusterIP, and load-balanced to a backend Pod IP.
apiVersion: v1
kind: Service
metadata:
name: web-nodeport-svc
spec:
type: NodePort
selector:
app: web
ports:
- name: http
port: 80 # Service ClusterIP port
targetPort: 8080 # Container port
nodePort: 30080 # Static node port (optional; auto-assigned if omitted)
[!CAUTION] Operational Limitations of NodePort in Production:
- Non-standard high-order ports (
30000-32767) are unfriendly for end users and often blocked by corporate firewalls.- If a target worker node goes offline, external clients hardcoded to that node's IP lose connectivity.
- Production architectures commonly place a managed or self-hosted Layer 4/7 load balancer in front of NodePorts instead of exposing node addresses directly.
2. Cloud Provider LoadBalancer Integration
When a Service is declared with spec.type: LoadBalancer on a supported cloud provider (AWS EKS, Google Cloud GKE, Azure AKS), the cloud-controller-manager (CCM) detects the object and automatically provisions a native cloud load balancer:
+-----------------------------------------------------------------------------------------+
| CLOUD LOADBALANCER PROVISIONING PIPELINE |
| |
| [1. User Manifest] ---> Service (type: LoadBalancer, port: 80) |
| | |
| v |
| [2. Control Plane] ---> Allocates NodePort 31450 & ClusterIP 10.96.80.20 |
| | |
| v |
| [3. Cloud Controller]---> Calls Cloud API (e.g., AWS CreateLoadBalancer) |
| - Provisions Public NLB / ALB (IP: 52.14.80.120) |
| - Configures Health Checks against NodePort 31450 |
| | |
| v |
| [4. Service Status] ---> Populates .status.loadBalancer.ingress[0].ip with Public IP |
+-----------------------------------------------------------------------------------------+
Common Cloud Provider Service Annotations:
- AWS Network Load Balancer (NLB):
metadata: annotations: service.beta.kubernetes.io/aws-load-balancer-type: "external" service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance" - Internal Private Load Balancer (Azure / GCP):
metadata: annotations: service.beta.kubernetes.io/azure-load-balancer-internal: "true" networking.gke.io/load-balancer-type: "Internal"
3. Bare-Metal Load Balancing with MetalLB
On bare-metal, on-premises, or homelab Kubernetes clusters, creating a type: LoadBalancer Service leaves the external IP in <Pending> state because there is no cloud controller to allocate a VIP.
MetalLB provides a network load-balancer implementation for bare-metal Kubernetes clusters using standard network routing protocols.
+-----------------------------------------------------------------------------------------+
| METALLB ARCHITECTURE & MODES |
| |
| 1. METALLB CONTROLLER (Deployment) |
| - Watches Service objects with type: LoadBalancer. |
| - Allocates an IP address from configured IPAddressPool CRD. |
| - Updates Service .status.loadBalancer.ingress. |
| |
| 2. METALLB SPEAKER (DaemonSet on all nodes) |
| - Announces the allocated VIP to the local network using one of two modes: |
| |
| MODE A: Layer 2 (ARP / NDP) MODE B: BGP (Layer 3 Routing) |
| +-------------------------------+ +-----------------------------------+ |
| | Node 01 elected Leader | | Every node establishes BGP peer | |
| | Responds to ARP queries | | with Top-of-Rack (ToR) Switch | |
| | for the VIP IP. | | True ECMP Multipath load balance! | |
| +-------------------------------+ +-----------------------------------+ |
+-----------------------------------------------------------------------------------------+
Layer 2 Mode vs. BGP Mode Comparison:
| Feature / Dimension | MetalLB Layer 2 Mode | MetalLB BGP Mode |
|---|---|---|
| Network Requirements | Standard Layer 2 Ethernet (No special routers) | Upstream routers supporting BGP peering |
| VIP Announcement | Gratuitous ARP (IPv4) / NDP (IPv6) | BGP Route Advertisement (Port 179) |
| Traffic Distribution | Single Node Leader receives all traffic; then forwards via kube-proxy. | True Multipath (ECMP): Upstream router distributes packets across all nodes. |
| Bandwidth Limit | Constrained to the network bandwidth of the single leader node. | Aggregate bandwidth across all peered worker nodes. |
| Failover Time | 2–5 seconds (Leader election + Gratuitous ARP broadcast) | Sub-second (BGP route withdrawal) |
| Configuration Simplicity | Extremely Simple | Requires network engineering / router config |
4. Declarative MetalLB Configuration (CRDs)
Modern MetalLB (v0.13+) uses declarative Custom Resource Definitions (CRDs) in the metallb-system namespace:
Step 1: Define the IP Address Pool (IPAddressPool)
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production-public-ips
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.250 # Dedicated VIP allocation range
autoAssign: true
Step 2A: Configure Layer 2 Advertisement (L2Advertisement)
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- production-public-ips
Step 2B: Alternatively, Configure BGP Peering (BGPPeer & BGPAdvertisement)
apiVersion: metallb.io/v1beta1
kind: BGPPeer
metadata:
name: tor-router-peer
namespace: metallb-system
spec:
myASN: 64500
peerASN: 64501
peerAddress: 192.168.1.1 # Physical Top-of-Rack Switch IP
---
apiVersion: metallb.io/v1beta1
kind: BGPAdvertisement
metadata:
name: bgp-adv
namespace: metallb-system
spec:
ipAddressPools:
- production-public-ips
Step 3: Deploy Service with type: LoadBalancer
apiVersion: v1
kind: Service
metadata:
name: baremetal-web-svc
namespace: default
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
# Verify MetalLB allocated external VIP:
kubectl get svc baremetal-web-svc
# Output:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# baremetal-web-svc LoadBalancer 10.96.140.22 192.168.1.200 80:31280/TCP
An engineer initializes a bare-metal Kubernetes cluster using kubeadm. When deploying a Service with 'spec.type: LoadBalancer', the service remains stuck with 'EXTERNAL-IP: <pending>' for hours. What is the fundamental cause of this behavior?
A production bare-metal cluster utilizes MetalLB in Layer 2 mode. An administrator notices that all incoming external traffic to a 10-node cluster enters through Worker Node 02, consuming 100% of its network bandwidth while other nodes remain idle. Is this expected behavior, and what is the architectural solution?
An administrator creates a Service with 'type: LoadBalancer' and 'externalTrafficPolicy: Local' on a cloud provider. What mechanism does the cloud load balancer use to prevent sending client traffic to worker nodes that host zero healthy backend Pods?