πŸ“š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 ⏱ 15 minutes K8s 1.28+

Kubernetes Service Types LoadBalancer ClusterIP NodePort

Understand Kubernetes Service types: ClusterIP, NodePort, LoadBalancer, and ExternalName. When to use each type, configuration examples, and traffic routing

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

πŸ’‘ Quick Answer: ClusterIP (default) β€” internal access only via cluster DNS. NodePort β€” exposes on each node’s IP at a static port (30000-32767). LoadBalancer β€” provisions external cloud load balancer. ExternalName β€” CNAME alias to external DNS. Use ClusterIP for service-to-service; LoadBalancer or Ingress for external traffic.

The Problem

  • Need to expose applications internally (to other pods) and externally (to users)
  • Different environments require different access patterns (dev vs production)
  • Cloud vs on-premises clusters have different external access options
  • Understanding when to use LoadBalancer vs Ingress vs NodePort
  • Service discovery and DNS resolution for pod-to-pod communication

The Solution

Service Types Overview

Type          β”‚ Access Scope      β”‚ Use Case                    β”‚ Port Range
──────────────┼───────────────────┼─────────────────────────────┼───────────
ClusterIP     β”‚ Internal only     β”‚ Service-to-service comms    β”‚ Any
NodePort      β”‚ External via node β”‚ Dev/testing, on-prem        β”‚ 30000-32767
LoadBalancer  β”‚ External via LB   β”‚ Production cloud            β”‚ Any
ExternalName  β”‚ DNS alias         β”‚ External service reference  β”‚ N/A
──────────────┴───────────────────┴─────────────────────────────┴───────────

ClusterIP (Default)

apiVersion: v1
kind: Service
metadata:
  name: api-server
  namespace: production
spec:
  type: ClusterIP    # Default β€” can be omitted
  selector:
    app: api-server
  ports:
    - name: http
      port: 80           # Service port (what clients connect to)
      targetPort: 8080   # Container port (where app listens)
      protocol: TCP
# Access from within cluster:
curl http://api-server.production.svc.cluster.local:80
curl http://api-server.production:80   # Short form (same namespace)
curl http://api-server:80              # Shortest (same namespace)

NodePort

apiVersion: v1
kind: Service
metadata:
  name: api-server-nodeport
spec:
  type: NodePort
  selector:
    app: api-server
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080    # Optional: auto-assigned if omitted (30000-32767)
# Access from outside cluster:
curl http://<any-node-ip>:30080
# Also accessible internally via ClusterIP (NodePort includes ClusterIP)

LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: api-server-lb
  annotations:
    # Cloud-specific annotations:
    # AWS:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
    # GCP:
    # cloud.google.com/neg: '{"ingress": true}'
spec:
  type: LoadBalancer
  selector:
    app: api-server
  ports:
    - port: 443
      targetPort: 8080
      protocol: TCP
  # Optional: restrict source IPs
  loadBalancerSourceRanges:
    - "203.0.113.0/24"
    - "198.51.100.0/24"
# Get external IP
kubectl get svc api-server-lb
# NAME             TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)
# api-server-lb    LoadBalancer   10.96.1.100   203.0.113.50    443:31234/TCP

# Access externally:
curl https://203.0.113.50:443

ExternalName

# DNS alias to external service (no proxying)
apiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.example.com
  # Pods can now access: external-database.namespace.svc.cluster.local
  # Resolves to: database.example.com (CNAME)

Headless Service (No ClusterIP)

# For StatefulSets β€” each pod gets its own DNS record
apiVersion: v1
kind: Service
metadata:
  name: database
spec:
  clusterIP: None    # Headless
  selector:
    app: database
  ports:
    - port: 5432
# DNS returns individual pod IPs:
nslookup database.production.svc.cluster.local
# database-0.database.production.svc.cluster.local β†’ 10.0.1.5
# database-1.database.production.svc.cluster.local β†’ 10.0.1.6

When to Use What

Scenario                              β”‚ Recommended
──────────────────────────────────────┼──────────────────────────────
Internal microservice communication   β”‚ ClusterIP
Database accessed by apps             β”‚ ClusterIP (or Headless)
Web app for external users (cloud)    β”‚ LoadBalancer + Ingress
Web app for external users (on-prem)  β”‚ NodePort + external LB
                                      β”‚ or MetalLB + LoadBalancer
Development/testing quick access      β”‚ NodePort or port-forward
Reference external service by DNS     β”‚ ExternalName
StatefulSet pod-specific DNS          β”‚ Headless (clusterIP: None)
──────────────────────────────────────┴──────────────────────────────

Common Issues

LoadBalancer stuck in β€œPending” for EXTERNAL-IP

  • Cause: No cloud provider or MetalLB installed (bare-metal cluster)
  • Fix: Install MetalLB for on-prem; or use NodePort + external LB

Service has no endpoints

  • Cause: Selector labels don’t match any pod labels; or pods not ready
  • Fix: Compare kubectl get endpoints <svc> with kubectl get pods --show-labels

NodePort not reachable from outside

  • Cause: Firewall blocking high ports; or security group missing rule
  • Fix: Open port range 30000-32767 on node firewalls/security groups

ExternalName not resolving

  • Cause: DNS lookup returns CNAME but client doesn’t follow it
  • Fix: Ensure app handles CNAME; note: ExternalName doesn’t support ports or TLS termination

Best Practices

  1. Default to ClusterIP β€” most services only need internal access
  2. Use Ingress/Gateway over LoadBalancer β€” one LB for many services (cost savings)
  3. Don’t expose NodePort in production β€” use LoadBalancer or Ingress instead
  4. Set targetPort by name β€” reference container port name for flexibility
  5. Use headless for StatefulSets β€” enables pod-specific DNS records
  6. Restrict LoadBalancer source ranges β€” loadBalancerSourceRanges for IP allowlisting

Key Takeaways

  • ClusterIP: internal only (default) β€” accessed via <svc>.<ns>.svc.cluster.local
  • NodePort: external via any node IP on port 30000-32767
  • LoadBalancer: provisions cloud LB with external IP β€” includes NodePort + ClusterIP
  • ExternalName: DNS CNAME to external service β€” no proxying
  • Headless (clusterIP: None): returns individual pod IPs β€” for StatefulSets
  • In production: use Ingress/Gateway API with one LoadBalancer for many services
#services #networking #loadbalancer #clusterip #nodeport
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