πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Networking beginner ⏱ 10 minutes K8s 1.28+

K8s Service Types: ClusterIP NodePort LB

Kubernetes Service types explained: ClusterIP, NodePort, LoadBalancer, and ExternalName. When to use each type with YAML examples and traffic flow diagrams.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: Kubernetes has 4 Service types: ClusterIP (internal-only, default) β€” accessible within the cluster via virtual IP. NodePort β€” exposes on every node’s IP at a static port (30000-32767). LoadBalancer β€” provisions cloud load balancer with external IP. ExternalName β€” DNS CNAME alias to external service. Choose ClusterIP for microservice communication, NodePort for dev/testing, LoadBalancer for production external access.

The Problem

Pods are ephemeral β€” their IPs change on restart. Services provide:

  • Stable virtual IP for a set of pods
  • Load balancing across pod replicas
  • DNS-based service discovery
  • External access to cluster workloads

But which Service type fits your use case?

The Solution

ClusterIP (Default)

apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  type: ClusterIP   # default, can be omitted
  selector:
    app: api
  ports:
  - port: 80         # Service port
    targetPort: 8080  # Pod port
[Pod A] β†’ api-service:80 β†’ [api pod 1]
                           β†’ [api pod 2]
                           β†’ [api pod 3]
# Only accessible within the cluster
# DNS: api-service.default.svc.cluster.local

Use when: microservices communicating within the cluster.

Headless Service (ClusterIP: None)

apiVersion: v1
kind: Service
metadata:
  name: db-service
spec:
  clusterIP: None    # Headless β€” no virtual IP
  selector:
    app: postgres
  ports:
  - port: 5432
# DNS returns individual pod IPs instead of virtual IP
nslookup db-service
# db-service.default.svc.cluster.local β†’ 10.244.1.5
#                                       β†’ 10.244.2.3
# Used by StatefulSets for stable network identities

NodePort

apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080    # Optional, auto-assigned if omitted (30000-32767)
[External] β†’ <any-node-ip>:30080 β†’ [web pod 1]
                                  β†’ [web pod 2]
# Accessible from outside via any node's IP
# Port range: 30000-32767

Use when: development, testing, bare-metal clusters without cloud LB.

LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: web-lb
  annotations:
    # Cloud-specific annotations
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
[Internet] β†’ Cloud LB (external IP) β†’ [web pod 1]
                                     β†’ [web pod 2]
# Cloud provider provisions a load balancer
# kubectl get svc β†’ EXTERNAL-IP: 34.123.45.67

Use when: production external access on cloud providers (AWS, GCP, Azure).

Bare-metal: Use MetalLB to provide LoadBalancer functionality.

ExternalName

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  type: ExternalName
  externalName: db.example.com
# DNS CNAME β€” no proxy, no port mapping
nslookup external-db
# external-db.default.svc.cluster.local β†’ CNAME db.example.com

Use when: aliasing external services with cluster DNS names.

Comparison Table

TypeExternal AccessIP TypePort RangeCloud Required
ClusterIP❌ Internal onlyVirtual IPAnyNo
NodePortβœ… Via node IPNode IPs30000-32767No
LoadBalancerβœ… External IPCloud LB IPAnyYes (or MetalLB)
ExternalName❌ DNS aliasNoneN/ANo
Headless❌ Internal onlyPod IPs directlyAnyNo

Quick Create Commands

# ClusterIP
kubectl expose deployment nginx --port=80 --target-port=8080

# NodePort
kubectl expose deployment nginx --type=NodePort --port=80 --target-port=8080

# LoadBalancer
kubectl expose deployment nginx --type=LoadBalancer --port=80 --target-port=8080

# Generate YAML
kubectl expose deployment nginx --port=80 --dry-run=client -o yaml

Common Issues

LoadBalancer stuck in β€œPending” EXTERNAL-IP

No cloud LB controller. On bare-metal, install MetalLB: kubectl apply -f https://metallb.io/manifests.

NodePort not reachable

Firewall blocking port range 30000-32767. Open these ports on cloud security groups or iptables.

Service has no endpoints

Selector doesn’t match any pod labels. Check: kubectl get endpoints <svc>.

Best Practices

  • ClusterIP for 90% of services β€” internal microservice communication
  • Ingress/Gateway API over NodePort/LoadBalancer β€” one LB for many services
  • Headless for StatefulSets β€” stable network identity per pod
  • Set externalTrafficPolicy: Local on NodePort/LB β€” preserves client IP
  • Use annotations for cloud LB tuning β€” NLB vs ALB, internal vs external

Key Takeaways

  • ClusterIP is the default β€” internal-only, stable virtual IP
  • NodePort opens a port on every node (30000-32767) β€” good for dev
  • LoadBalancer provisions cloud infrastructure β€” production external access
  • ExternalName creates DNS CNAME aliases to external services
  • Most production apps use ClusterIP + Ingress (one LB for many services)
#services #networking #load-balancer #nodeport #cka
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens