Kubernetes Services: ClusterIP, NodePort, LoadBalancer, Ingress

Master Kubernetes service types and Ingress controllers to expose your applications inside and outside the cluster with proper load balancing and routing.

published: reading time: 9 min read

Kubernetes Services: ClusterIP, NodePort, LoadBalancer, and Ingress

Pods in Kubernetes are ephemeral. They get IP addresses assigned when they start and those addresses change when pods reschedule. If you want your application to be reachable, you need something stable. Kubernetes Services provide that stability by creating a persistent endpoint for a set of pods.

This post covers the four service types, when to use each one, and how Ingress controllers extend routing beyond simple port forwarding.

If you need to understand the basics of Kubernetes first, check the Kubernetes fundamentals post. For advanced networking patterns, see the Advanced Kubernetes post.

Service Types Comparison

Kubernetes offers four service types:

Decision Matrix: Choosing the Right Service Type

Access ScenarioService TypeExample
Internal microservice-to-microserviceClusterIPAPI to database
Expose single node for debuggingNodePortDev environment access
Production HTTP/HTTPS trafficIngressWeb app frontend
TCP/UDP without Ingress complexityLoadBalancerCustom protocol, legacy apps
Cross-cluster service discoveryExternalNameIntegrate external service

Skip NodePort for production HTTP services. The port range (30000-32767) is awkward, and node IPs change in dynamic clusters.

Skip LoadBalancer per microservice. One load balancer per team or product boundary is usually enough. Provisioning a cloud LB for every pod gets expensive fast.

ClusterIP is internal only. If you need external access, ClusterIP is not your answer.

Traffic Flow Architecture

Here is how a request moves from an external user down to a pod:

flowchart TD
    User([External User]) --> Internet[Internet]
    Internet --> DNS[DNS Resolution<br/>api.example.com]
    DNS --> ALB[Cloud Load Balancer<br/>NLB/ALB]
    ALB --> Ingress[Ingress Controller<br/>NGINX/Traefik]
    Ingress --> SvcClusterIP[ClusterIP Service<br/>kube-proxy routes to Pods]
    Ingress --> SvcNodePort[NodePort Service<br/>:30000-32767 on each Node]
    Ingress --> SvcLB[LoadBalancer Service<br/>Cloud-managed LB]
    SvcClusterIP --> Pod1[Pod<br/>app=v1]
    SvcClusterIP --> Pod2[Pod<br/>app=v1]
    SvcClusterIP --> Pod3[Pod<br/>app=v1]
    SvcNodePort --> Node1[Node<br/>kube-proxy]
    Node1 --> Pod1
    SvcLB --> Pod1
    SvcLB --> Pod2
    SvcLB --> Pod3

For most production HTTP/HTTPS workloads, the typical path is: User → DNS → Load Balancer → Ingress → ClusterIP Service → Pods.

For TCP services without Ingress, the path skips the Ingress step and goes directly: User → DNS → Load Balancer Service → Pods.

NodePort is mainly for development. The path there is: User → Node IP and port → Pod.


Service Types Overview

TypeAccess ScopeUse Case
ClusterIPInternal onlyMicroservices within the cluster
NodePortExposes on each node IPDevelopment, simple external access
LoadBalancerExternal via cloud LBProduction external access
ExternalNameMaps to external DNSIntegrating external services

ClusterIP is the default. You get an internal cluster IP that pods can use to communicate with each other. The other types expose services outside the cluster.

ClusterIP for Internal Access

ClusterIP is the most common service type. It creates an internal IP that load-balances traffic across all matching pods. Other pods in the cluster use the service name to reach your application.

apiVersion: v1
kind: Service
metadata:
  name: api-backend
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: api-backend
    version: v2
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: grpc
      protocol: TCP
      port: 50051
      targetPort: 50051

The targetPort can be a number or a name that matches the container port. Using names makes it easier to update ports without changing the service.

Within the cluster, pods access the service using its fully qualified name:

http://api-backend.production.svc.cluster.local

Or just the service name if they are in the same namespace:

http://api-backend

DNS is automatic. Kubernetes maintains a DNS entry for every service.

Headless Services for StatefulSets

Set clusterIP: None to create a headless service. Instead of load balancing, DNS returns the pod IPs directly. This is useful for StatefulSets where clients need to discover individual pod addresses.

apiVersion: v1
kind: Service
metadata:
  name: postgres-cluster
  namespace: database
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432

With a headless service, DNS queries return A records for each pod: postgres-cluster-0.postgres-cluster.database.svc.cluster.local, and so on.

NodePort for Development

NodePort opens a port on every node in the cluster. Traffic arriving at http://<node-ip>:<node-port> gets routed to the service. The port range defaults to 30000-32767.

apiVersion: v1
kind: Service
metadata:
  name: web-frontend-nodeport
spec:
  type: NodePort
  selector:
    app: web-frontend
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

Setting nodePort is optional. Kubernetes assigns one from the default range if you omit it.

NodePort works for development and quick demos. For production, use LoadBalancer or Ingress. NodePort bypasses some load balancing logic and exposes infrastructure details you may not want.

LoadBalancer with Cloud Controllers

On cloud providers that support external load balancers (AWS, GCP, Azure), the LoadBalancer type provisions a managed load balancer and routes traffic to your service.

apiVersion: v1
kind: Service
metadata:
  name: web-frontend-lb
  namespace: production
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: web-frontend
  ports:
    - port: 80
      targetPort: 80
    - port: 443
      targetPort: 443

The annotation service.beta.kubernetes.io/aws-load-balancer-type: "nlb" creates a Network Load Balancer on AWS instead of a Classic Load Balancer.

Cloud controllers create load balancers with health checks pointed at your pods. They also handle SSL termination if you configure certificates.

For SSL termination on the load balancer, you need to annotate the service with the certificate ARN:

annotations:
  service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-east-1:123456789:certificate/abc123"

Ingress Controllers and Rules

Ingress is not a service type. It is a Kubernetes resource that provides HTTP/HTTPS routing rules. An Ingress controller implements the routing. Without a controller, Ingress resources do nothing.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /users
            pathType: Prefix
            backend:
              service:
                name: users-api
                port:
                  number: 80
          - path: /products
            pathType: Prefix
            backend:
              service:
                name: products-api
                port:
                  number: 80
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: admin-console
                port:
                  number: 80
  tls:
    - hosts:
        - api.example.com
        - admin.example.com
      secretName: example-com-tls

The ingressClassName field specifies which Ingress controller handles this Ingress. Common controllers include NGINX Ingress Controller, Traefik, and cloud-provider ingress controllers.

Path rewriting

The NGINX ingress controller supports path rewriting via annotations:

annotations:
  nginx.ingress.kubernetes.io/rewrite-target: /$2

With this configuration, requests to /api/v1/users get rewritten to /users before reaching the backend service.

Rate limiting via Ingress

annotations:
  nginx.ingress.kubernetes.io/limit-rps: "10"
  nginx.ingress.kubernetes.io/limit-connections: "5"

These annotations apply rate limiting at the Ingress level, protecting all backend services from excessive traffic.

Security Considerations

Services enable connectivity, but you should restrict which pods can communicate with which. Kubernetes Network Policies (covered in a separate post) let you enforce microsegmentation between pods.

For external access, prefer Ingress with TLS termination over NodePort. Ingress provides path-based routing and centralized SSL management.

Avoid exposing services directly with LoadBalancer unless you need layer 3/4 load balancing. Most web traffic works fine with Ingress and a single load balancer.

Conclusion

Kubernetes Services provide stable endpoints for your applications. ClusterIP works for internal microservice communication. NodePort is useful for development and testing. LoadBalancer integrates with cloud providers for production external access. Ingress controllers add HTTP/HTTPS routing with host-based and path-based rules, SSL termination, and rate limiting.

Start with ClusterIP for internal traffic. Add Ingress when you need external HTTP/HTTPS access. Use LoadBalancer only when you need TCP-level load balancing or integration with non-HTTP services.

Understanding these networking primitives helps you design services that are reachable, scalable, and secure. For deeper Kubernetes networking concepts like Network Policies, see the Advanced Kubernetes post.

Production Failure Scenarios

ClusterIP Service Not Reachable After Pod Restart

When a pod restarts, its IP changes. If your application hardcodes pod IPs instead of using the ClusterIP service name, communication breaks.

Symptoms: Pod-to-pod communication fails after restarts, Connection refused errors.

Diagnosis:

kubectl get pod -o wide  # Check pod IPs
kubectl get endpoints <service-name>  # Should list pod IPs
kubectl describe pod <pod-name>  # Check status

Mitigation: Always use the ClusterIP service DNS name for pod-to-pod communication, never pod IPs.

NodePort Service Port Conflicts

When multiple services use the same nodePort value, one service fails to start.

Symptoms: nodePort port is already allocated error when applying a Service.

Mitigation: Let Kubernetes assign the port automatically, or track port allocations explicitly. Do not hardcode nodePort values across multiple services without coordination.

LoadBalancer Service Stays Pending

On cloud providers, LoadBalancer provisioning can fail due to quota limits, missing IAM permissions, or unsupported service configurations.

Symptoms: ExternalLB Pending status persists for minutes.

Diagnosis:

kubectl describe service <name> -n <namespace>
# Check events for error messages
kubectl get events --sort-by='.lastTimestamp' -n <namespace>

Mitigation: Verify cloud IAM permissions for the service account. Check cloud quota for load balancers. Use annotations to specify the correct load balancer type (NLB vs CLB on AWS).

Anti-Patterns

Exposing Services Directly Without Ingress

Exposing every microservice with its own LoadBalancer quickly exhausts cloud quotas and gets expensive. Use Ingress for HTTP/HTTPS services and reserve LoadBalancer for TCP-level services or non-HTTP protocols.

Using NodePort in Production

NodePort exposes a service on a high port across all nodes. This is useful for debugging but should not be used for production access. The port range (30000-32767) is not standard, and node IPs change in dynamic clusters.

Skipping Health Checks on Headless Services

For StatefulSets with headless services, clients need working health checks to discover which pod is the primary. Without proper readiness probes, clients may attempt to write to a replica that is not ready.

Quick Recap Checklist

Use this checklist when working with Kubernetes services:

  • Used ClusterIP for internal microservice communication
  • Used Ingress (not NodePort) for production HTTP/HTTPS access
  • Used LoadBalancer only for non-HTTP TCP/UDP services
  • Set targetPort explicitly to avoid port mismatch issues
  • Used headless services (ClusterIP: None) for StatefulSet discovery
  • Configured health checks for external load balancers via service annotations
  • Avoided hardcoding pod IPs in application code
  • Used endpoints watches in client applications for dynamic service discovery
  • Applied Network Policies to restrict service-to-service communication
  • Used TLS annotations for SSL termination at the Ingress or LoadBalancer level

Category

Related Posts

Kubernetes Network Policies: Securing Pod-to-Pod Communication

Implement microsegmentation in Kubernetes using Network Policies to control traffic flow between pods and enforce zero-trust networking.

#kubernetes #network-policies #security

Cloud Security: IAM, Network Isolation, and Encryption

Implement defense-in-depth security for cloud infrastructure—identity and access management, network isolation, encryption, and security monitoring.

#cloud #security #iam

Container Security: Image Scanning and Vulnerability Management

Implement comprehensive container security: from scanning images for vulnerabilities to runtime security monitoring and secrets protection.

#container-security #docker #kubernetes