Introduction

In Kubernetes, Pods are ephemeral - they can be created, destroyed, and replaced at any time. This creates a challenge: how do you reliably connect to your applications when Pod IP addresses constantly change? The answer is Kubernetes Services.

Services provide stable networking endpoints for your Pods, enabling reliable communication both within your cluster and from external clients. In this comprehensive guide, I’ll walk you through the three main Service types - ClusterIP, NodePort, and LoadBalancer - with practical examples and production best practices.

What is a Kubernetes Service?

A Service is an abstraction that defines a logical set of Pods and a policy to access them. Think of it as a stable endpoint that:

  • Provides a stable IP address that doesn’t change when Pods are recreated
  • Load balances traffic across multiple Pod replicas
  • Enables service discovery through DNS
  • Abstracts Pod locations from clients

Key Benefits:

  • Stable networking despite Pod churn
  • Built-in load balancing
  • Service discovery via DNS
  • Decoupling between services
  • Multiple exposure options (internal/external)

Prerequisites

  • kubectl installed and configured
  • Access to a Kubernetes cluster
  • Understanding of Pods and Deployments (see previous posts)
  • Basic networking knowledge

Service Architecture

Understanding how Services work:

Client Request
    ↓
Service (Stable IP + DNS)
    ↓
kube-proxy (iptables/IPVS rules)
    ↓
Load Balanced to Pods
    ↓
Pod 1, Pod 2, Pod 3...

Components:

  • Service: Defines the access policy
  • Endpoints: List of Pod IPs that match the Service selector
  • kube-proxy: Implements the Service abstraction on each node
  • DNS: Provides name resolution for Services

Service Types Overview

Kubernetes provides four Service types:

  1. ClusterIP (Default): Internal cluster communication only
  2. NodePort: Exposes Service on each Node’s IP at a static port
  3. LoadBalancer: Exposes Service externally using cloud provider’s load balancer
  4. ExternalName: Maps Service to a DNS name (advanced use case)

Let’s explore the first three in detail.

Part 1: ClusterIP Service

ClusterIP is the default Service type, providing internal cluster communication.

Characteristics

  • Internal only: Accessible only within the cluster
  • Stable IP: Gets a cluster-internal IP address
  • DNS name: Accessible via service-name.namespace.svc.cluster.local
  • Use case: Backend services, databases, internal APIs

Creating a ClusterIP Service

Scenario: Create a backend service that frontend Pods can access.

Step 1: Create Backend Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  labels:
    app: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
      - name: backend
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        - name: APP_NAME
          value: "backend-service"

Step 2: Create ClusterIP Service

apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
  - protocol: TCP
    port: 5000        # Service port
    targetPort: 80    # Container port

Deploy:

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

Verify:

kubectl get services
kubectl get endpoints backend-service

Output:

NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
backend-service   ClusterIP   10.96.100.50    <none>        5000/TCP   30s

Testing ClusterIP Service

Method 1: From another Pod

Create a test Pod:

kubectl run test-pod --image=busybox --rm -it --restart=Never -- sh

Inside the Pod:

# Using Service name (DNS)
wget -qO- http://backend-service:5000

# Using fully qualified domain name
wget -qO- http://backend-service.default.svc.cluster.local:5000

# Check DNS resolution
nslookup backend-service

Method 2: Port Forwarding (for testing)

kubectl port-forward service/backend-service 8080:5000
# Access via: http://localhost:8080

Understanding Service DNS

Kubernetes provides automatic DNS for Services:

Format: <service-name>.<namespace>.svc.<cluster-domain>

Examples:

  • Same namespace: backend-service
  • Different namespace: backend-service.production
  • Fully qualified: backend-service.production.svc.cluster.local

Part 2: NodePort Service

NodePort exposes the Service on each Node’s IP at a static port, making it accessible from outside the cluster.

Characteristics

  • External access: Accessible via <NodeIP>:<NodePort>
  • Port range: 30000-32767 (default)
  • All nodes: Service listens on all nodes
  • Use case: Development, testing, small deployments

Creating a NodePort Service

Scenario: Expose frontend application to external traffic.

Step 1: Create Frontend Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  labels:
    app: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80

Step 2: Create NodePort Service

apiVersion: v1
kind: Service
metadata:
  name: frontend-service
spec:
  type: NodePort
  selector:
    app: frontend
  ports:
  - protocol: TCP
    port: 80          # Service port
    targetPort: 80    # Container port
    nodePort: 30080   # Optional: specify port (30000-32767)

Deploy:

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

Verify:

kubectl get services frontend-service

Output:

NAME               TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
frontend-service   NodePort   10.96.200.100   <none>        80:30080/TCP   1m

Accessing NodePort Service

Get Node IP:

kubectl get nodes -o wide

Access the service:

# Using any node IP
curl http://<NODE-IP>:30080

# With minikube
minikube service frontend-service --url
curl $(minikube service frontend-service --url)

Important Notes:

  • Traffic can reach the Service through ANY node, even if no Pods run on that node
  • kube-proxy forwards traffic to appropriate Pods
  • Not recommended for production (use LoadBalancer or Ingress instead)

Part 3: LoadBalancer Service

LoadBalancer provisions an external load balancer (cloud provider specific) to expose the Service.

Characteristics

  • Cloud integration: Requires cloud provider support
  • External IP: Gets a public IP address
  • Production ready: Designed for production workloads
  • Use case: Production applications, public-facing services

Creating a LoadBalancer Service

Available on: AWS (ELB/ALB), Azure (Azure Load Balancer), GCP (Cloud Load Balancer)

apiVersion: v1
kind: Service
metadata:
  name: frontend-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"  # AWS specific
spec:
  type: LoadBalancer
  selector:
    app: frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Deploy:

kubectl apply -f frontend-loadbalancer.yaml

Verify:

kubectl get services frontend-lb

Output (on cloud):

NAME          TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)        AGE
frontend-lb   LoadBalancer   10.96.150.50    52.12.34.56      80:31234/TCP   2m

Access:

curl http://52.12.34.56

On Minikube (simulation):

minikube tunnel  # Run in separate terminal
kubectl get services frontend-lb  # Will show EXTERNAL-IP as localhost

Cloud Provider Annotations

AWS:

metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-internal: "true"

Azure:

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

GCP:

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

Complete Example: Multi-Tier Application

Let’s create a complete application with frontend and backend services.

Architecture:

Internet
    ↓
LoadBalancer Service (frontend)
    ↓
Frontend Pods
    ↓
ClusterIP Service (backend)
    ↓
Backend Pods

backend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
      tier: api
  template:
    metadata:
      labels:
        app: backend
        tier: api
    spec:
      containers:
      - name: api
        image: nginx:alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: backend-api
spec:
  type: ClusterIP
  selector:
    app: backend
    tier: api
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 80

frontend-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
      tier: web
  template:
    metadata:
      labels:
        app: frontend
        tier: web
    spec:
      containers:
      - name: web
        image: nginx:alpine
        ports:
        - containerPort: 80
        env:
        - name: BACKEND_URL
          value: "http://backend-api:5000"
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-web
spec:
  type: LoadBalancer
  selector:
    app: frontend
    tier: web
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Deploy:

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

Service Discovery and DNS

Kubernetes provides automatic DNS-based service discovery.

DNS Records Created:

For a Service named backend-api in namespace production:

backend-api.production.svc.cluster.local

A Records:

  • ClusterIP Services: Points to Service IP
  • Headless Services: Points to Pod IPs

SRV Records:

  • Format: _<port-name>._<protocol>.<service-name>.<namespace>.svc.cluster.local
  • Example: _http._tcp.backend-api.production.svc.cluster.local

Testing DNS:

kubectl run -it --rm debug --image=busybox --restart=Never -- sh
nslookup backend-api
nslookup backend-api.production
nslookup backend-api.production.svc.cluster.local

Session Affinity

By default, Services load balance randomly. For sticky sessions:

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

Headless Services

For direct Pod-to-Pod communication without load balancing:

apiVersion: v1
kind: Service
metadata:
  name: headless-service
spec:
  clusterIP: None  # Makes it headless
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 80

Use cases:

  • StatefulSets
  • Database clusters
  • Custom load balancing logic

Service Without Selector

For external services or custom endpoints:

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306
---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
- addresses:
  - ip: 192.168.1.100
  ports:
  - port: 3306

Service Management Commands

# Create service imperatively
kubectl expose deployment myapp --type=ClusterIP --port=80
kubectl expose deployment myapp --type=NodePort --port=80
kubectl expose deployment myapp --type=LoadBalancer --port=80

# Get services
kubectl get services
kubectl get svc -o wide
kubectl get svc --all-namespaces

# Describe service
kubectl describe service myapp

# Get endpoints
kubectl get endpoints myapp

# Delete service
kubectl delete service myapp

# Port forward for testing
kubectl port-forward service/myapp 8080:80

# Get service URL (minikube)
minikube service myapp --url

Troubleshooting Services

Issue 1: Service Not Accessible

Check Service:

kubectl get service myapp
kubectl describe service myapp

Check Endpoints:

kubectl get endpoints myapp
# If empty, selector doesn't match any Pods

Verify Selector:

kubectl get pods --show-labels
# Ensure Pod labels match Service selector

Issue 2: LoadBalancer Stuck in Pending

Symptoms:

kubectl get svc
# EXTERNAL-IP shows <pending>

Causes:

  • Not running on cloud provider
  • Cloud provider integration not configured
  • Insufficient permissions

Solution for local testing:

# Use minikube tunnel
minikube tunnel

# Or use NodePort instead
kubectl patch svc myapp -p '{"spec":{"type":"NodePort"}}'

Issue 3: Connection Refused

Check Pod Status:

kubectl get pods
kubectl logs <pod-name>

Verify Port Configuration:

kubectl describe service myapp
# Ensure port and targetPort are correct

Test from within cluster:

kubectl run test --image=busybox --rm -it -- wget -qO- http://myapp:80

Best Practices

1. Use Appropriate Service Type

  • ClusterIP: Internal services (databases, APIs)
  • NodePort: Development/testing only
  • LoadBalancer: Production external services
  • Ingress: Multiple services (preferred over LoadBalancer)

2. Set Resource Limits on Pods

resources:
  requests:
    memory: "64Mi"
    cpu: "100m"
  limits:
    memory: "128Mi"
    cpu: "200m"

3. Use Meaningful Names

metadata:
  name: user-api-service  # Clear, descriptive
  labels:
    app: user-api
    tier: backend
    environment: production

4. Implement Health Checks

spec:
  containers:
  - name: app
    livenessProbe:
      httpGet:
        path: /health
        port: 80
    readinessProbe:
      httpGet:
        path: /ready
        port: 80

5. Use Service Mesh for Advanced Features

  • Istio, Linkerd for:
    • Advanced traffic management
    • Mutual TLS
    • Observability
    • Circuit breaking

6. Monitor Service Performance

# Check service metrics
kubectl top pods -l app=myapp

# View service logs
kubectl logs -l app=myapp --tail=100

7. Use Namespaces for Organization

kubectl create namespace production
kubectl apply -f service.yaml -n production

Service vs Ingress

When to use Service:

  • Single application exposure
  • TCP/UDP protocols
  • Simple load balancing

When to use Ingress:

  • Multiple services
  • HTTP/HTTPS only
  • Path-based routing
  • SSL termination
  • Cost-effective (single LoadBalancer)

Example Ingress (preview for next post):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

Real-World Production Example

Complete production-ready service configuration:

apiVersion: v1
kind: Service
metadata:
  name: production-api
  namespace: production
  labels:
    app: api
    environment: production
    version: v1.0.0
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
  type: LoadBalancer
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600
  selector:
    app: api
    environment: production
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443

Conclusion

Kubernetes Services are fundamental to application networking, providing stable endpoints and load balancing for your Pods. Key takeaways:

  • ClusterIP for internal communication
  • NodePort for development/testing
  • LoadBalancer for production external access
  • Use DNS for service discovery
  • Implement health checks for reliability
  • Consider Ingress for HTTP/HTTPS workloads
  • Monitor and troubleshoot effectively

Understanding Services is crucial for building production-ready Kubernetes applications. Combined with Deployments and proper networking configuration, you have the foundation for scalable, reliable systems.

In the next post, we’ll explore Ingress Controllers for advanced HTTP routing and ConfigMaps & Secrets for configuration management.

Happy networking! 🚀

Resources