Services and Networking
Learn how Services expose Pods reliably using stable virtual IPs, DNS, and service types — with hands-on YAML examples.
Why Services Exist
Pods are ephemeral. When a Pod dies and a new one starts, it gets a different IP.
A Service solves this by:
- Using label selectors to track which Pods are healthy
- Maintaining a stable ClusterIP and DNS name regardless of Pod churn
- Load-balancing traffic across all matching Pods
Client → Service (stable IP: 10.96.0.1) → Pod A (10.244.0.3)
→ Pod B (10.244.0.4)
→ Pod C (10.244.0.5)
ClusterIP (Default)
Internal-only access. The most common type for pod-to-pod communication.
# service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
selector:
app: api # routes to pods with this label
ports:
- protocol: TCP
port: 80 # port on the Service
targetPort: 8080 # port on the Pod
type: ClusterIP
kubectl apply -f service-clusterip.yaml
# View the service and its ClusterIP
kubectl get service api-service
kubectl describe service api-service
# Test from inside a temporary pod
kubectl run test --rm -it --image=curlimages/curl -- sh
# Inside the pod:
curl http://api-service # same namespace
curl http://api-service.default.svc.cluster.local # fully qualified
NodePort
Exposes the service on every node’s IP at a static port. Mainly used for development or legacy load balancers.
# service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: api-nodeport
spec:
selector:
app: api
ports:
- protocol: TCP
port: 80 # internal cluster port
targetPort: 8080 # pod port
nodePort: 30080 # port exposed on every node (30000–32767)
type: NodePort
kubectl apply -f service-nodeport.yaml
# Access via any node IP
kubectl get nodes -o wide # get node external IP
curl http://<node-ip>:30080
# With minikube
minikube service api-nodeport --url
LoadBalancer
Provisions a cloud load balancer (AWS ELB, GCP LB, Azure LB). Gives you a public IP.
# service-lb.yaml
apiVersion: v1
kind: Service
metadata:
name: api-lb
spec:
selector:
app: api
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
kubectl apply -f service-lb.yaml
# Watch for EXTERNAL-IP to be assigned (takes a minute on cloud)
kubectl get service api-lb --watch
With minikube, run minikube tunnel in a separate terminal to simulate a LoadBalancer IP.
Service Types Summary
| Type | Access scope | Use case |
|---|---|---|
ClusterIP | Inside cluster only | Pod-to-pod communication |
NodePort | Node IP + static port | Dev/testing, legacy setups |
LoadBalancer | External IP from cloud | Production internet-facing apps |
ExternalName | DNS alias to external service | Abstracting external APIs |
DNS-Based Service Discovery
# Every service is reachable via DNS inside the cluster:
# <service-name>.<namespace>.svc.cluster.local
# Same namespace: just use service name
curl http://api-service
# Cross-namespace: include namespace
curl http://api-service.production.svc.cluster.local
# Check DNS from inside a pod
kubectl run dns-test --rm -it --image=busybox -- nslookup api-service
Endpoints
Kubernetes automatically tracks healthy Pod IPs in an Endpoints object.
# See which Pod IPs the service is forwarding to
kubectl get endpoints api-service
# If endpoints are empty, check your selector labels match pod labels
kubectl get pods --show-labels
kubectl describe service api-service | grep Selector
Headless Service (StatefulSets)
A headless service returns individual Pod DNS names instead of a ClusterIP — used with StatefulSets so each pod is addressable.
apiVersion: v1
kind: Service
metadata:
name: db-headless
spec:
clusterIP: None # headless — no virtual IP
selector:
app: db
ports:
- port: 5432
# DNS returns individual pod addresses, e.g.:
# db-0.db-headless.default.svc.cluster.local
# db-1.db-headless.default.svc.cluster.local
Learning Outcomes
You can:
- Write ClusterIP, NodePort, and LoadBalancer Service manifests
- Explain label selection and how endpoints are maintained
- Use DNS for service discovery inside a cluster
- Debug services with
kubectl describe,kubectl get endpoints