Services

4 minute read

Pods are ephemeral — they come and go, and their IP addresses change. Services provide a stable network endpoint to access a set of Pods, with built-in load balancing and service discovery.

The Problem Services Solve

Consider this scenario:

  • You have 3 nginx Pods behind a Deployment
  • Each Pod has its own IP (e.g., 10.244.0.5, 10.244.0.6, 10.244.0.7)
  • If a Pod dies, the replacement gets a new IP
  • Other applications can’t hardcode Pod IPs

Services solve this by providing a stable virtual IP (ClusterIP) that automatically routes traffic to healthy Pods.

           ┌─────────────┐
           │   Service    │
           │ ClusterIP:   │
           │ 10.96.0.100  │
           └──────┬───────┘
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
   ┌─────────┐ ┌─────────┐ ┌─────────┐
   │  Pod 1  │ │  Pod 2  │ │  Pod 3  │
   │10.244.  │ │10.244.  │ │10.244.  │
   │  0.5    │ │  0.6    │ │  0.7    │
   └─────────┘ └─────────┘ └─────────┘

Service Types

Kubernetes offers four types of Services:

Type Accessible From Use Case
ClusterIP Inside the cluster only Internal communication between microservices
NodePort External via <NodeIP>:<NodePort> Development, testing
LoadBalancer External via cloud load balancer Production on cloud providers
ExternalName DNS alias to external service Integrating external services

ClusterIP (Default)

The default Service type. Creates an internal-only virtual IP.

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP             # Default, can be omitted
  selector:
    app: nginx                # Routes to Pods with this label
  ports:
  - protocol: TCP
    port: 80                  # Service port (what clients connect to)
    targetPort: 80            # Container port (where traffic is sent)
# Create the Service
kubectl apply -f service.yaml

# Check it
kubectl get services
# or: kubectl get svc

Output:

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

Accessing a ClusterIP Service

From inside the cluster, you can access it by:

# By Service name (same namespace)
curl http://nginx-service

# By fully qualified domain name
curl http://nginx-service.default.svc.cluster.local

# Format: <service-name>.<namespace>.svc.cluster.local

NodePort

Exposes the Service on a static port on each node’s IP. Useful for development.

apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80                  # Internal Service port
    targetPort: 80            # Container port
    nodePort: 30080           # External port (30000-32767)
# Access from outside the cluster
curl http://<node-ip>:30080

# In Minikube, get the URL:
minikube service nginx-nodeport --url

LoadBalancer

Creates an external load balancer (on cloud providers like AWS, GCP, Azure).

apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
kubectl get svc nginx-loadbalancer

Output (on a cloud provider):

NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
nginx-loadbalancer   LoadBalancer   10.96.78.210   203.0.113.50     80:31234/TCP   30s

Note: In Minikube, LoadBalancer type won’t get a real external IP. Use minikube tunnel to simulate it, or use NodePort instead.

ExternalName

Maps a Service to an external DNS name. No proxying — just a DNS CNAME record.

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

Now Pods can connect to external-db and it resolves to db.example.com.

Service Discovery

Kubernetes provides two mechanisms for service discovery:

Every Service gets a DNS entry automatically:

<service-name>.<namespace>.svc.cluster.local
# From any Pod in the "default" namespace:
curl http://nginx-service             # Same namespace
curl http://nginx-service.default     # With namespace
curl http://nginx-service.default.svc.cluster.local  # FQDN

2. Environment Variables

Kubernetes injects Service details as environment variables into Pods (only for Services that existed before the Pod was created):

# Inside a Pod:
echo $NGINX_SERVICE_SERVICE_HOST    # 10.96.45.123
echo $NGINX_SERVICE_SERVICE_PORT    # 80

Endpoints

Behind every Service is an Endpoints object that lists the IPs of all matching Pods:

kubectl get endpoints nginx-service

Output:

NAME            ENDPOINTS                                      AGE
nginx-service   10.244.0.5:80,10.244.0.6:80,10.244.0.7:80    5m

When Pods are added or removed, the Endpoints are updated automatically.

Multi-Port Services

A Service can expose multiple ports:

apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  selector:
    app: web-app
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 8080
  - name: https
    protocol: TCP
    port: 443
    targetPort: 8443

Note: When you have multiple ports, each port must have a name.

Headless Services

A headless Service (with clusterIP: None) doesn’t get a virtual IP. Instead, DNS returns the individual Pod IPs directly. Useful for StatefulSets.

apiVersion: v1
kind: Service
metadata:
  name: db-headless
spec:
  clusterIP: None
  selector:
    app: database
  ports:
  - port: 5432
# DNS returns individual Pod IPs instead of a single ClusterIP
nslookup db-headless.default.svc.cluster.local
# Returns: 10.244.0.5, 10.244.0.6, 10.244.0.7

Hands-On: Complete Example

Let’s create a Deployment and expose it with a Service:

# Create a Deployment
kubectl create deployment web --image=nginx:1.27 --replicas=3

# Expose it as a NodePort Service
kubectl expose deployment web --type=NodePort --port=80

# Check the Service
kubectl get svc web

# In Minikube, open it in a browser
minikube service web

Summary

Service Type Scope Best For
ClusterIP Internal only Microservice-to-microservice communication
NodePort External via node port Development, testing
LoadBalancer External via cloud LB Production on cloud
ExternalName DNS alias External service integration
  • Services provide stable endpoints for ephemeral Pods
  • DNS-based discovery is the recommended approach
  • Use labels and selectors to connect Services to Pods
  • Headless Services return individual Pod IPs (for StatefulSets)

What’s Next?

As your cluster grows, you’ll need to organize resources and isolate workloads. In the next chapter, we’ll learn about Namespaces — Kubernetes’ way of creating virtual clusters within a physical cluster.

Next: Namespaces →

Updated: