Article 16 – Services – LoadBalancer: Moving to the Cloud

The Limitations of Raw NodePorts in Cloud Environments

If you deploy your applications in a public cloud environment (such as AWS, GCP, or Azure) and expose them using NodePort services on high-range ports like 30008, you run into production limitations. You cannot easily ask external users to type high-range ports into their browsers. Customers expect standard HTTP/HTTPS access on port 80 or 443.

Additionally, exposing node IP addresses directly is a resilience risk. If a node fails, any client attempting to access that specific IP address will experience an outage, even if your application pods are running on other healthy hosts. You need a stable, cloud-native load balancer to manage incoming connections.

Automated Cloud Balancer Provisioning and Source IP Preservation

The LoadBalancer Service addresses this requirement. When you define a service type as LoadBalancer in a cloud environment, the cluster’s Cloud Controller Manager (CCM) automatically requests the cloud provider API to provision a native load balancer (such as an AWS NLB or GCP Load Balancer).

Under the hood, the LoadBalancer service acts as an extension of a NodePort service. The cloud provider configures an external load balancer with a public IP or DNS name. It then routes incoming traffic on port 80/443 to the cluster nodes on an auto-allocated high-range nodePort (e.g., 31245). Once traffic hits the nodes, Kube-proxy routes it to the active pods.

A critical configuration in production is externalTrafficPolicy:
* Cluster (Default): Traffic hitting a node is routed to any active pod in the cluster, even if the pod is on a different node. This can cause an extra network hop across nodes and obscures the client’s original source IP address (replacing it with the node’s internal CNI IP).
* Local: Traffic hitting a node is only routed to pods running on that specific node. This preserves the original client Source IP address and avoids extra network hops. If a node has no running pods for that service, the load balancer stops sending traffic to that node.

Here is how the cloud load balancer integrates with your cluster nodes:

LoadBalancer Service Routing (Cloud LB -> NodePorts -> Pods)

Deploying a LoadBalancer Service

Let’s create a LoadBalancer service configuration. Save the following YAML configuration as web-lb-service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: cloud-web-service
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local  # Preserves client source IP address
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx-web

Apply the service configuration:

$ kubectl apply -f web-lb-service.yaml
service/cloud-web-service created

Check the service status:

$ kubectl get services
NAME                TYPE           CLUSTER-IP     EXTERNAL-IP                              PORT(S)        AGE
kubernetes          ClusterIP      10.96.0.1      <none>        443/TCP        12d
cloud-web-service   LoadBalancer   10.100.45.182  a123-out.elb.amazonaws.com               80:31245/TCP   30s

If you are running in a supported cloud environment, the EXTERNAL-IP field will populate with a public IP or DNS hostname. If you are running locally (e.g., on minikube), this field will show <pending> because there is no cloud provider API available to provision the physical hardware.

To simulate a cloud load balancer locally, run the minikube tunnel command in a separate terminal:

$ minikube tunnel

Once active, the tunnel routes traffic to a local IP address in the EXTERNAL-IP column:

$ kubectl get services cloud-web-service
NAME                TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
cloud-web-service   LoadBalancer   10.100.45.182  192.168.49.10    80:31245/TCP   2m

You can now visit http://192.168.49.10 in your browser to access your Nginx application directly on port 80, using a stable ingress path.