Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Kubernetes Service Types

Learn how ClusterIP, NodePort, and LoadBalancer services work in Kubernetes with a hands-on exercise and troubleshooting tips.

Focus: kubernetes service types

Sponsored

You've deployed a containerized app on Kubernetes. Pods are running — but how does the rest of the world, or even other Pods inside the cluster, actually reach them? Without a stable network endpoint, Pods are ephemeral, IPs change on restart, and load balancing is non-existent. Kubernetes Services solve exactly this problem, abstracting away Pod IP churn and giving you a reliable way to expose your application — whether it's for internal microservice communication, external testing, or production traffic.

The problem this lesson solves

Kubernetes Pods are designed to be thrown away and recreated. Each time a Pod restarts, scales up, or moves to a different node, it gets a new IP address. If your frontend Pod talks to a backend Pod by hardcoding an IP, that connection is guaranteed to break the moment the backend restarts. Worse, if you have multiple replicas of your backend, you need some way to distribute traffic among them.

Services are the Kubernetes abstraction that provides a stable virtual IP (VIP), DNS name, and load balancing across a set of Pods. They decouple the consumer from the Pod lifecycle, enabling zero-downtime scaling and self-healing. This lesson covers the three fundamental service types: ClusterIP, NodePort, and LoadBalancer.

Core concept / mental model

Think of a Service as a traffic cop sitting in front of a group of Pods. The Service selects which Pods to forward traffic to based on labels (just like a Deployment uses labels to manage Pods). It has its own stable IP address and port — that never changes, even when Pods come and go.

Service types as network exposure levels

  • ClusterIP → Internal-only VIP. Traffic cannot leave the cluster. Perfect for backend microservices talking to one another (e.g., your payment-service talking to user-service).
  • NodePort → Opens a specific port on every node in the cluster. External traffic can reach your Service via <NodeIP>:<NodePort>. Good for development or when you have a static node IP — but not cloud-production-grade.
  • LoadBalancer → Built on top of NodePort. When you run Kubernetes on a cloud provider (AWS, GCP, Azure), a LoadBalancer Service provisions a real cloud load balancer (e.g., AWS ALB/NLB) that distributes traffic to your Pods. Production-ready.

Pro tip: All three types are built on a common foundation. A NodePort is effectively a ClusterIP with a port opened on every node. A LoadBalancer is effectively a NodePort plus a cloud load balancer pointing at those node ports.

How it works step by step

1. Label-Based Pod Selection

A Service uses a selector field to match Pods. For example, if you have Pods with the label app: my-app, the Service will automatically discover and forward traffic to all Pods with that label. It doesn't care about individual Pod IPs — it watches the API server for endpoint changes.

2. Stable Virtual IP (ClusterIP)

Kubernetes assigns a static virtual IP from the service CIDR range (usually 10.96.0.0/12). This IP persists for the Service's lifetime. When a client sends a packet to the Service's IP, kube-proxy on each node intercepts it and performs load balancing (either iptables-based or IPVS) to one of the matching Pods.

3. NodePort Allocation

When you specify type: NodePort, Kubernetes allocates a port (by default between 30000-32767) on every node in the cluster. The in-cluster ClusterIP still exists, but now external traffic hitting <any-node-ip>:<node-port> gets routed to the Service VIP → Pod.

4. Cloud Load Balancer Integration

With type: LoadBalancer, the cloud-controller-manager for your provider provisions a real load balancer (e.g., AWS NLB, GCP TCP LB). The cloud LB sends traffic to the NodePort on any node, and from there kube-proxy takes over to the Pods.

Hands-on walkthrough

Let's create a simple web app (nginx) and expose it using each service type.

Prerequisites

  • A running Kubernetes cluster (minikube, kind, or real cluster)
  • kubectl configured

Step 1: Deploy a test application

# deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
kubectl apply -f deploy.yaml
kubectl get pods -l app=nginx

Step 2: Create a ClusterIP Service

# svc-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-clusterip
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80          # Port exposed by the Service
      targetPort: 80    # Port on the Pod
  type: ClusterIP
kubectl apply -f svc-clusterip.yaml
kubectl get svc nginx-service-clusterip

Expected output (IP will differ):

NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service-clusterip   ClusterIP   10.96.123.45    <none>        80/TCP    10s

Test from inside a Pod:

# Run a temporary Pod that curls the Service
kubectl run test-pod --image=busybox --rm -it --restart=Never -- sh -c 'wget -qO- http://nginx-service-clusterip:80'

You should see the nginx welcome page HTML.

Step 3: Upgrade to NodePort

# svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-nodeport
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080   # Optional: let K8s assign if omitted
  type: NodePort
kubectl apply -f svc-nodeport.yaml
kubectl get svc nginx-service-nodeport

Expected output:

NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
nginx-service-nodeport   NodePort    10.96.234.56    <none>        80:30080/TCP     10s

Access from outside the cluster (using minikube):

# If using minikube
minikube service nginx-service-nodeport

Or use the node IP:

# Get node IP (for minikube: minikube ip)
NODE_IP=$(minikube ip)
curl http://$NODE_IP:30080

Step 4: Create a LoadBalancer Service

Note: LoadBalancer only works fully on cloud clusters or minikube tunnel. On local clusters, the EXTERNAL-IP shows <pending> until a load balancer is provisioned.

# svc-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service-lb
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer
kubectl apply -f svc-loadbalancer.yaml
kubectl get svc nginx-service-lb -w

Wait for the cloud LB to provision (can take a minute). Once ready:

# Get the external IP
kubectl get svc nginx-service-lb --output jsonpath='{.status.loadBalancer.ingress[0].ip}'

Then curl that IP.

Compare options / when to choose what

Feature ClusterIP NodePort LoadBalancer
Access from outside cluster ❌ No ✅ Yes (node IP + port) ✅ Yes (cloud LB)
Stable IP ✅ Virtual IP ✅ Virtual IP ✅ Cloud LB DNS/IP
Port range Any (no allocation) 30000-32767 Cloud LB port (usually 80/443)
Load balancing across Pods ✅ Round-robin via kube-proxy ✅ Same as ClusterIP ✅ Hybrid (cloud + kube-proxy)
Use case Microservice-to-microservice Dev/test, on-prem, or direct access Production internet-facing
Cloud dependency None None Requires cloud provider integration
Cost Free (cluster internal) Free (cluster internal) Extra cost (cloud LB charges)

Pro tip: Starting with ClusterIP for internal services and only upgrading to NodePort or LoadBalancer when external access is genuinely needed is a best practice. Overusing LoadBalancer for every microservice will rack up cloud costs quickly.

When to choose what

  • Choose ClusterIP if the service is only called by other services inside the cluster (e.g., database, internal API, cache).
  • Choose NodePort if you need a quick way to test from outside, or you are running on bare metal without a cloud LB. It's also used as a building block for Ingress controllers.
  • Choose LoadBalancer if you need a production-grade external endpoint with a fixed IP and SSL termination. Many teams use one LoadBalancer per application (or per namespace) and use Ingress to route multiple services through a single LB.

Troubleshooting & edge cases

Pods aren't selected

If your Service shows endpoints as empty (kubectl get endpoints <svc>), the selector labels don't match any pods. Verify pod labels:

kubectl describe service nginx-service-clusterip
kubectl get pods --show-labels

NodePort not accessible

  • Ensure you are using the node's IP, not the Pod IP.
  • Check firewall/security groups — the NodePort range must be open.
  • For minikube, use minikube service <svc> to open a tunnel automatically.

LoadBalancer stuck on <pending>

Common causes: - Running on a cluster without cloud provider integration (e.g., bare metal, kind, Docker Desktop). Use metallb or an Ingress instead. - Cloud provider quota exceeded (e.g., too many LBs per region). - kubectl describe svc nginx-service-lb shows events with error details.

Headless Services (an edge case)

Setting clusterIP: None creates a headless Service — no load balancing, no single IP. Instead, DNS returns all Pod IPs. Useful for stateful workloads (e.g., databases) where each Pod needs a unique identity.

What you learned & what's next

You now understand the three core Kubernetes Service types:

  • ClusterIP — stable internal VIP for Pod-to-Pod communication.
  • NodePort — opens a port on every node for external access.
  • LoadBalancer — cloud-provisioned LB for production internet exposure.

You learned how to create each type, test connectivity, and choose the right type for different scenarios. You also saw common pitfalls like label mismatches and LB provisioning issues.

Next step: Services expose Pods, but managing multiple services externally can become complex and expensive. The next lesson introduces Ingress — a smarter way to route HTTP/HTTPS traffic to multiple services through a single external endpoint, with path- and host-based routing.

Practice recap

Create a second Deployment (app: web) and expose it with a LoadBalancer Service. Then scale the Deployment to 3 replicas and watch the Service endpoints update automatically (kubectl get endpoints <svc> -w). Verify you can curl the external IP from outside the cluster.

Common mistakes

  • Forgetting to match the Service selector with the Pod labels — always run kubectl get pods --show-labels to verify.
  • Using NodePort on a cloud cluster without opening the node port range in the security group — traffic will be silently dropped.
  • Assuming LoadBalancer works on a local cluster (minikube, kind, Docker Desktop) without a tunnel or MetalLB — it will remain pending.
  • Hardcoding a Pod IP instead of using the Service DNS name — breaks on restart.

Variations

  1. ExternalName Service: Maps a Kubernetes Service to an external DNS name (e.g., my-service.default.svc.cluster.localapi.example.com) without proxying traffic through kube-proxy.
  2. Headless Service (clusterIP: None): Used for stateful apps like databases where each Pod needs its own stable network identity and DNS resolution returns all Pod IPs.
  3. Multi-port Services: Define multiple ports entries in a single Service to expose different protocols (e.g., port 80 HTTP, port 443 HTTPS) — requires port or name disambiguation.

Real-world use cases

  • Microservice backend: A payment-service Pod communicates to a user-service via a ClusterIP Service with DNS name user-service.default.svc.cluster.local.
  • Staging environment: A NodePort Service on port 30080 allows developers to test a new release from their laptops by hitting any cluster node IP.
  • Production web app: A LoadBalancer Service provisions an AWS ALB that terminates SSL and forwards traffic to the app Pods, serving millions of requests.

Key takeaways

  • Kubernetes Services provide a stable virtual IP and DNS name that decouples clients from ephemeral Pod IPs.
  • ClusterIP is the default and only works inside the cluster — ideal for internal microservices.
  • NodePort opens a static port on every node (range 30000-32767) for external access — good for dev/test.
  • LoadBalancer provisions a cloud load balancer and is the production choice for internet-facing services.
  • All three types use label selectors to find matching Pods; mismatched labels mean no endpoints.
  • Services are the foundation for Ingress — an Ingress points to a ClusterIP Service, not directly to Pods.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.