DEV Community

Claudio
Claudio

Posted on • Originally published at fridaydeployer.hashnode.dev

Understanding Kubernetes Networking: ClusterIP, NodePort, and LoadBalancer Services

If you've ever deployed an application to Kubernetes and wondered "How do other pods find my service?" or "How do external users access my application?", you're not alone. Kubernetes networking can seem mysterious at first, especially when you encounter terms like ClusterIP, NodePort, and LoadBalancer.

In this comprehensive guide, we'll demystify Kubernetes Services and networking. By the end, you'll understand exactly how traffic flows in a Kubernetes cluster and which Service type to use for different scenarios.

The Networking Challenge in Kubernetes

Before we dive into Services, let's understand the problem they solve. Imagine you're running a microservices architecture in Kubernetes:

  • Your frontend application needs to talk to a backend API
  • The backend API needs to connect to a database
  • Users need to access your frontend from the internet
  • You have multiple replicas of each component for high availability

Here's the challenge: Pods in Kubernetes are ephemeral. They can be created, destroyed, and rescheduled at any time. Each pod gets its own IP address, but these IPs change when pods restart. How do you maintain stable networking in such a dynamic environment?

Consider this scenario without Services:

# Three backend pods with different IPs
backend-pod-1: 10.244.1.5
backend-pod-2: 10.244.2.8
backend-pod-3: 10.244.3.12

Enter fullscreen mode Exit fullscreen mode

If your frontend needs to connect to the backend, which IP should it use? What happens when backend-pod-2 crashes and gets replaced with a new pod at 10.244.1.20? Your frontend would need to constantly track and update backend IP addresses—an impossible task at scale.

Enter Kubernetes Services

A Kubernetes Service is an abstraction that defines a logical set of pods and a policy for accessing them. Think of it as a stable endpoint that sits in front of your pods, providing:

  1. Stable IP address: A Service gets a virtual IP (VIP) that doesn't change
  2. DNS name: Each Service gets a DNS name for easy discovery
  3. Load balancing: Traffic is distributed across healthy pods
  4. Service discovery: Pods can find each other using Service names

Services use labels and selectors to identify which pods they should route traffic to:

# Backend Deployment with labels
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend  # These labels are crucial
        tier: api
    spec:
      containers:
      - name: backend
        image: myapp/backend:v1
        ports:
        - containerPort: 8080

Enter fullscreen mode Exit fullscreen mode

Now, let's explore the three main types of Services and when to use each.

ClusterIP: Internal Service Discovery

What is ClusterIP?

ClusterIP is the default Service type. It exposes the Service on an internal IP address that's only reachable from within the cluster. This is perfect for internal communication between your microservices.

How ClusterIP Works

When you create a ClusterIP Service:

  1. Kubernetes assigns it a virtual IP from the cluster's service IP range
  2. The Service is registered in the cluster's DNS
  3. kube-proxy on each node configures iptables rules to route traffic
  4. Traffic sent to the Service IP is load-balanced across all matching pods

Here's a visual representation of traffic flow:

ClusterIp

Creating a ClusterIP Service

# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
  labels:
    app: backend
spec:
  type: ClusterIP  # This is optional since ClusterIP is the default
  selector:
    app: backend  # Matches pods with this label
  ports:
  - name: http
    protocol: TCP
    port: 80        # Port exposed by the Service
    targetPort: 8080  # Port on the container

Enter fullscreen mode Exit fullscreen mode

Let's break down the key fields:

  • selector: Identifies which pods belong to this Service (must match pod labels)
  • port: The port the Service listens on
  • targetPort: The port on the pod where traffic is forwarded

Practical Example: Microservices Communication

Let's build a complete example with a frontend and backend:

# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: nginx:latest
        ports:
        - containerPort: 80
        env:
        - name: SERVICE_NAME
          value: "backend"
---
# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80
---
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: curlimages/curl:latest
        command: ['sh', '-c', 'while true; do sleep 30; done']
        env:
        # The backend service DNS name
        - name: BACKEND_URL
          value: "http://backend-service:8080"

Enter fullscreen mode Exit fullscreen mode

Deploy these resources:

kubectl apply -f backend-deployment.yaml
kubectl apply -f frontend-deployment.yaml

# Test connectivity from frontend to backend
kubectl exec -it deployment/frontend -- curl http://backend-service:8080

Enter fullscreen mode Exit fullscreen mode

DNS Resolution in Kubernetes

Kubernetes provides built-in DNS service discovery. Services can be reached using:

  1. Short name (same namespace): backend-service
  2. Fully qualified domain name: backend-service.default.svc.cluster.local

The DNS format is: <service-name>.<namespace>.svc.cluster.local

# From any pod in the default namespace
curl http://backend-service:8080

# From a pod in a different namespace
curl http://backend-service.default.svc.cluster.local:8080

Enter fullscreen mode Exit fullscreen mode

When to Use ClusterIP

Use ClusterIP when:

  • Communication is between services within the cluster
  • You're building microservices that only need internal connectivity
  • You want to keep services private and not expose them externally
  • Examples: databases, internal APIs, caching layers, message queues

Common Use Cases:

  • Backend API → Database
  • Frontend → Backend API
  • API Gateway → Microservices
  • Application → Redis/Memcached

NodePort: External Access via Node IPs

What is NodePort?

NodePort extends ClusterIP by opening a specific port on all nodes in the cluster. External traffic can reach your Service by connecting to any node's IP address on the allocated port.

How NodePort Works

When you create a NodePort Service:

  1. Kubernetes creates a ClusterIP Service (internal access)
  2. Opens the same port (30000-32767 range) on every node
  3. Routes traffic from <NodeIP>:<NodePort> to the Service
  4. The Service then load-balances to the backend pods

Traffic flow:

Node port

Creating a NodePort Service

# frontend-nodeport-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - name: http
    protocol: TCP
    port: 80          # ClusterIP port
    targetPort: 8080  # Container port
    nodePort: 30080   # Port opened on each node (optional, auto-assigned if omitted)

Enter fullscreen mode Exit fullscreen mode

Key points about NodePort:

  • nodePort range: 30000-32767 by default (configurable in kube-apiserver)
  • Auto-assignment: If you don't specify nodePort, Kubernetes assigns one automatically
  • All nodes: The port is opened on every node, even those not running your pods

Practical Example: Exposing a Web Application

Let's deploy a simple web application and expose it via NodePort:

# webapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: nginxdemos/hello:latest
        ports:
        - containerPort: 80
---
# webapp-nodeport-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp-service
spec:
  type: NodePort
  selector:
    app: webapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30100

Enter fullscreen mode Exit fullscreen mode

Deploy and test:

kubectl apply -f webapp-deployment.yaml

# Get the NodePort
kubectl get service webapp-service

# Get node IPs
kubectl get nodes -o wide

# Access the application
# Replace <NODE_IP> with any node's IP address
curl http://<NODE_IP>:30100

Enter fullscreen mode Exit fullscreen mode

NodePort Limitations and Considerations

Limitations:

  1. Port range constraints: Limited to 30000-32767 (can become restrictive)
  2. No automatic DNS: Users must know node IPs
  3. Manual load balancing: You need an external load balancer to distribute traffic across nodes
  4. Security exposure: Opens ports on all nodes
  5. Node failure: If a node fails, clients using that node's IP will lose connectivity

When to Use NodePort:

NodePort is useful for:

  • Development and testing environments
  • On-premises clusters without load balancer integration
  • Exposing services when you have a limited number of external IPs
  • Temporary external access needs
  • Integrating with external load balancers manually

LoadBalancer: Cloud-Native External Access

What is LoadBalancer?

LoadBalancer is the most straightforward way to expose a Service to the internet. It provisions an external load balancer (if your cluster runs in a supported cloud environment) and assigns it a public IP address.

How LoadBalancer Works

When you create a LoadBalancer Service:

  1. Kubernetes creates a NodePort Service
  2. Kubernetes creates a ClusterIP Service
  3. Kubernetes requests an external load balancer from the cloud provider
  4. The cloud provider provisions a load balancer and assigns a public IP
  5. The load balancer forwards traffic to the NodePorts on your cluster nodes

Traffic flow:

Load balancer

Creating a LoadBalancer Service

# webapp-loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: webapp-lb-service
  annotations:
    # Cloud-specific annotations (examples)
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"  # AWS
    # cloud.google.com/load-balancer-type: "Internal"  # GCP
spec:
  type: LoadBalancer
  selector:
    app: webapp
  ports:
  - protocol: TCP
    port: 80          # External port on load balancer
    targetPort: 8080  # Container port
  # Optional: Restrict source IP ranges
  loadBalancerSourceRanges:
  - "203.0.113.0/24"
  - "198.51.100.0/24"

Enter fullscreen mode Exit fullscreen mode

Practical Example: Production Web Application

Let's deploy a complete production-ready application:

# production-webapp.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webapp-prod
  labels:
    app: webapp
    environment: production
spec:
  replicas: 5
  selector:
    matchLabels:
      app: webapp
      environment: production
  template:
    metadata:
      labels:
        app: webapp
        environment: production
    spec:
      containers:
      - name: webapp
        image: mycompany/webapp:v2.1.0
        ports:
        - containerPort: 8080
        env:
        - name: ENVIRONMENT
          value: "production"
        # Health checks
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: webapp-lb
  annotations:
    # AWS-specific annotations
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    # Health check configuration
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: "/health"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "10"
spec:
  type: LoadBalancer
  selector:
    app: webapp
    environment: production
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443
  sessionAffinity: ClientIP  # Sticky sessions
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600

Enter fullscreen mode Exit fullscreen mode

Deploy and access:

kubectl apply -f production-webapp.yaml

# Watch the load balancer being provisioned
kubectl get service webapp-lb -w

# Once EXTERNAL-IP is assigned (may take 1-5 minutes)
kubectl get service webapp-lb
# NAME        TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)
# webapp-lb   LoadBalancer   10.96.45.123    203.0.113.10      80:31234/TCP

# Access your application
curl http://203.0.113.10

Enter fullscreen mode Exit fullscreen mode

Cloud Provider Integration

Different cloud providers have specific features and annotations:

AWS (EKS)

metadata:
  annotations:
    # Network Load Balancer (Layer 4)
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

    # Classic Load Balancer (default)
    service.beta.kubernetes.io/aws-load-balancer-type: "clb"

    # Internal load balancer
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"

    # SSL certificate
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:..."

    # Backend protocol
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"

    # Connection draining
    service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"

Enter fullscreen mode Exit fullscreen mode

GCP (GKE)

metadata:
  annotations:
    # Internal load balancer
    cloud.google.com/load-balancer-type: "Internal"

    # Backend configuration
    cloud.google.com/backend-config: '{"ports": {"80":"backend-config"}}'

    # NEG (Network Endpoint Groups) for container-native load balancing
    cloud.google.com/neg: '{"ingress": true}'

Enter fullscreen mode Exit fullscreen mode

Azure (AKS)

metadata:
  annotations:
    # Internal load balancer
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"

    # Subnet
    service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "apps-subnet"

    # Public IP name
    service.beta.kubernetes.io/azure-pip-name: "myPublicIP"

Enter fullscreen mode Exit fullscreen mode

LoadBalancer Cost Considerations

Each LoadBalancer Service provisions a separate cloud load balancer, which incurs costs:

  • AWS NLB: ~$0.0225/hour + data processing charges
  • GCP Load Balancer: ~$0.025/hour + forwarding rule charges
  • Azure Load Balancer: ~$0.025/hour + data processing

For multiple services, consider using an Ingress Controller instead, which allows multiple services to share a single load balancer.

When to Use LoadBalancer

Use LoadBalancer when:

  • You need to expose a service to the internet
  • You're running in a cloud environment with load balancer support
  • You want automatic cloud load balancer provisioning
  • You need a public IP address for your service
  • You have a small number of services to expose (due to cost)

Best Practices:

  • Use for production applications requiring external access
  • Combine with Ingress for HTTP/HTTPS routing to multiple services
  • Enable health checks for automatic pod traffic management
  • Use SSL/TLS termination at the load balancer when possible
  • Configure appropriate timeout and connection settings

Comparing the Service Types

Let's see all three types side by side:

Feature ClusterIP NodePort LoadBalancer
Accessibility Internal only External via node IPs External via load balancer IP
DNS Name Yes (internal) Yes (internal) Yes (internal) + External IP
Port Range Any 30000-32767 Any
Load Balancing Internal only Manual external LB needed Automatic
Cloud Provider Required No No Yes
Cost Free Free Load balancer costs
Use Case Microservices Dev/test, on-prem Production external access
IP Address Cluster IP Node IPs + Cluster IP External IP + Node IPs + Cluster IP

Advanced Service Patterns

Multi-Port Services

Services can expose multiple ports:

apiVersion: v1
kind: Service
metadata:
  name: multi-port-service
spec:
  selector:
    app: myapp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443
  - name: metrics
    protocol: TCP
    port: 9090
    targetPort: 9090

Enter fullscreen mode Exit fullscreen mode

Headless Services

Sometimes you don't want load balancing, but need DNS records for individual pods:

apiVersion: v1
kind: Service
metadata:
  name: database-headless
spec:
  clusterIP: None  # This makes it headless
  selector:
    app: database
  ports:
  - port: 5432
    targetPort: 5432

Enter fullscreen mode Exit fullscreen mode

With a headless service, DNS returns the IP addresses of all pods instead of a single service IP. Useful for:

  • Stateful applications (databases)
  • Service discovery without load balancing
  • Direct pod-to-pod communication

ExternalName Services

Map a service to an external DNS name:

apiVersion: v1
kind: Service
metadata:
  name: external-database
spec:
  type: ExternalName
  externalName: database.example.com

Enter fullscreen mode Exit fullscreen mode

This allows you to use external-database in your cluster, which resolves to database.example.com.

Session Affinity

Maintain sticky sessions by routing requests from the same client to the same pod:

apiVersion: v1
kind: Service
metadata:
  name: webapp-sticky
spec:
  selector:
    app: webapp
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800  # 3 hours
  ports:
  - port: 80
    targetPort: 8080

Enter fullscreen mode Exit fullscreen mode

Conclusion

Kubernetes Services are the backbone of cluster networking, providing stable endpoints for your applications. Understanding the three main Service types is crucial:

  • ClusterIP: Internal communication between microservices (default choice)
  • NodePort: External access for development, testing, or on-premises clusters
  • LoadBalancer: Production-grade external access with cloud-native load balancing

Key Takeaways:

  1. Start with ClusterIP for all internal services
  2. Use LoadBalancer for production external access in cloud environments
  3. Consider NodePort for development or when LoadBalancer isn't available
  4. Leverage DNS for service discovery instead of hard-coding IPs
  5. Monitor endpoints to ensure healthy pod registration
  6. Implement network policies for security
  7. Use labels and selectors correctly to route traffic

As you build more complex applications, you'll likely use all three Service types together. Internal microservices communicate via ClusterIP, while user-facing applications use LoadBalancer for external access. Some administrative tools might use NodePort for restricted access.

Remember that Services are abstractions—they don't run your application; they simply provide a stable way to access it. Understanding this distinction is key to mastering Kubernetes networking.

Top comments (0)