Skip to main content

Kubernetes Ep 5: Networking Services (ClusterIP, NodePort, LoadBalancer)

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
kubernetes - This article is part of a series.
Part 5: This Article
Pods are ephemeral—they are created, destroyed, and rescheduled dynamically, causing their IP addresses to change constantly. A Kubernetes Service provides a stable, persistent virtual IP (VIP), DNS name, and load balancing frontend across a dynamic set of backend Pods.

TL;DR (Quick Summary)
#

  • Service Abstraction: Defines a logical set of Pods determined by a selector label query and an access policy.
  • Service Types:
    • ClusterIP (Default): Exposes the service on a cluster-internal IP. Accessible only from within the cluster.
    • NodePort: Exposes the service on each Worker Node’s IP at a static port (30000-32767). Accessible externally via <NodeIP>:<NodePort>.
    • LoadBalancer: Provisions a cloud provider’s external load balancer (AWS NLB/ALB, GCP Network LB, Azure LB) routing traffic to NodePort.
  • Endpoints Object: Kubernetes automatically populates an Endpoints object listing the active target Pod IPs matching the service selector.

1. Service Types & Architecture
#


graph TD
    ClientExt["External Client
(Internet)"] -->|Public IP: 203.0.113.10:80| LB["Cloud LoadBalancer
(Type: LoadBalancer)"] subgraph K8sCluster["Kubernetes Cluster"] LB -->|NodePort 30080| NP1["Worker Node 1
(192.168.1.10)"] LB -->|NodePort 30080| NP2["Worker Node 2
(192.168.1.11)"] NP1 --> CIP["ClusterIP Service
(10.96.100.50:80)
Selector: app=web"] NP2 --> CIP CIP --> Pod1["Pod A (10.244.1.5:80)"] CIP --> Pod2["Pod B (10.244.2.8:80)"] CIP --> Pod3["Pod C (10.244.2.9:80)"] end

2. ClusterIP (Internal Service Discovery)
#

ClusterIP is the default service type used for internal microservice-to-microservice communication (e.g., frontend API communicating with an internal database).

Create service-clusterip.yaml:

apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
  - name: http
    port: 80        # Service Virtual Port
    targetPort: 8080 # Container Port on Backend Pod

Apply and inspect the service:

kubectl apply -f service-clusterip.yaml
kubectl get svc backend-service

Expected Terminal Output:

NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
backend-service   ClusterIP   10.96.142.100   <none>        80/TCP    15s

Internal DNS Resolution
#

Inside any Pod in the cluster, CoreDNS automatically resolves the service name to 10.96.142.100:

  • Short FQDN: backend-service
  • Full FQDN: backend-service.default.svc.cluster.local

3. NodePort (Bare-Metal & Local Access)
#

NodePort allocates a port from the default range 30000-32767 on all worker nodes. Any traffic sent to <Node-IP>:<NodePort> is forwarded to the backend service.

Create service-nodeport.yaml:

apiVersion: v1
kind: Service
metadata:
  name: frontend-nodeport
  namespace: default
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080 # Optional: explicitly choose port between 30000-32767

Apply and test access:

kubectl apply -f service-nodeport.yaml
kubectl get svc frontend-nodeport
NAME                TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
frontend-nodeport   NodePort   10.96.200.45    <none>        80:30080/TCP   10s

You can now curl any worker node’s IP address:

curl http://<WORKER_NODE_IP>:30080

4. LoadBalancer (Cloud Managed Access)
#

When running in managed cloud environments (EKS, GKE, AKS), specifying type: LoadBalancer automatically provisions a cloud load balancer with a public IP address.

Create service-loadbalancer.yaml:

apiVersion: v1
kind: Service
metadata:
  name: public-web-lb
  namespace: default
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 80

Apply and check external IP provisioning:

kubectl apply -f service-loadbalancer.yaml
kubectl get svc public-web-lb --watch
NAME            TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
public-web-lb   LoadBalancer   10.96.88.12    35.240.112.50   80:31452/TCP   45s

5. Endpoints & EndpointSlices Debugging
#

How does a Service know which Pod IPs to route traffic to? Through Endpoints.

List active Endpoints matching service selectors:

kubectl get endpoints backend-service

Expected Terminal Output:

NAME              ENDPOINTS                                      AGE
backend-service   10.244.1.12:8080,10.244.2.14:8080,10.244.2.15:8080   3m
Tip

If a Service returns HTTP 503 or connection timeout, run kubectl get endpoints <service-name>. If the ENDPOINTS column says <none>, check if your Service’s spec.selector labels match the Pod’s metadata.labels exactly!


6. Summary & Next Steps
#

Services solve L4 transport-layer networking (IPs and Ports). However, creating a separate LoadBalancer service for every single microservice is cost-prohibitive in public clouds (each AWS NLB costs ~$20/month).

In Episode 06: Ingress Controllers & HTTP Path Routing, we will learn how an Ingress Controller acts as a single smart HTTP/S proxy managing L7 path-based and host-based routing across all your services!

kubernetes - This article is part of a series.
Part 5: This Article