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.
Last updated: August 2026

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 by kube-proxy Netfilter/IPVS rules, rewritten to the Service's ClusterIP, 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:

  1. Non-standard high-order ports (30000-32767) are unfriendly for end users and often blocked by corporate firewalls.
  2. If a target worker node goes offline, external clients hardcoded to that node's IP lose connectivity.
  3. 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 / DimensionMetalLB Layer 2 ModeMetalLB BGP Mode
Network RequirementsStandard Layer 2 Ethernet (No special routers)Upstream routers supporting BGP peering
VIP AnnouncementGratuitous ARP (IPv4) / NDP (IPv6)BGP Route Advertisement (Port 179)
Traffic DistributionSingle Node Leader receives all traffic; then forwards via kube-proxy.True Multipath (ECMP): Upstream router distributes packets across all nodes.
Bandwidth LimitConstrained to the network bandwidth of the single leader node.Aggregate bandwidth across all peered worker nodes.
Failover Time2–5 seconds (Leader election + Gratuitous ARP broadcast)Sub-second (BGP route withdrawal)
Configuration SimplicityExtremely SimpleRequires 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
Loading diagram...
MetalLB Traffic Routing: Layer 2 ARP vs BGP ECMP
Test Your Knowledge

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
B
C
D
Test Your Knowledge

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?

A
B
C
D
Test Your Knowledge

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?

A
B
C
D