12 - Services & Networking
Why Services?
Pods are ephemeral -- they get new IPs when recreated. A Service provides:
- Stable IP (ClusterIP) that never changes
- DNS name (
my-service.default.svc.cluster.local) - Load balancing across pod replicas
- Service discovery for other pods
Without Service: With Service:
Pod A → 10.244.1.5 (Pod B) Pod A → my-service (stable)
↓ Pod B dies + restarts ↓
Pod A → 10.244.1.5 ✗ (wrong IP!) Pod A → my-service → any healthy Pod B
Service Types
1. ClusterIP (Default)
Internal-only access within the cluster:
yamlapiVersion: v1 kind: Service metadata: name: api-service spec: type: ClusterIP # Default, can omit selector: app: api # Routes to pods with this label ports: - name: http port: 80 # Service port (what clients use) targetPort: 8080 # Container port (where app listens) protocol: TCP
┌─── Cluster ──────────────────────────────────┐
│ │
│ Client Pod │
│ curl http://api-service:80 │
│ │ │
│ ▼ │
│ api-service (ClusterIP: 10.96.45.12) │
│ │ │
│ ┌────┼────┐ │
│ ▼ ▼ ▼ │
│ Pod1 Pod2 Pod3 (label: app=api) │
│ :8080 :8080 :8080 │
│ │
│ NOT accessible from outside cluster │
└──────────────────────────────────────────────┘
2. NodePort
Exposes service on every node's IP at a static port:
yamlapiVersion: v1 kind: Service metadata: name: web-service spec: type: NodePort selector: app: web ports: - port: 80 targetPort: 8080 nodePort: 30080 # Range: 30000-32767 (optional, auto-assigned)
External Client
│
▼
Node1:30080 or Node2:30080 or Node3:30080
│ │ │
└────────────────┼────────────────┘
│
web-service (ClusterIP)
│
┌─────┼─────┐
▼ ▼ ▼
Pod1 Pod2 Pod3
3. LoadBalancer
Creates an external load balancer (cloud provider only):
yamlapiVersion: v1 kind: Service metadata: name: public-web annotations: # AWS-specific service.beta.kubernetes.io/aws-load-balancer-type: nlb spec: type: LoadBalancer selector: app: web ports: - port: 80 targetPort: 8080
Internet
│
▼
Cloud Load Balancer (e.g., AWS NLB)
│ External IP: 54.23.45.67
│
├── Node1:NodePort
├── Node2:NodePort
└── Node3:NodePort
│
web-service (ClusterIP)
│
┌─────┼─────┐
Pod1 Pod2 Pod3
4. ExternalName
Maps a service to an external DNS name:
yamlapiVersion: v1 kind: Service metadata: name: external-db spec: type: ExternalName externalName: db.example.com
bash# Inside the cluster: # nslookup external-db → CNAME db.example.com # Useful for referencing external services with a K8s-native name
5. Headless Service
No ClusterIP -- returns individual pod IPs:
yamlapiVersion: v1 kind: Service metadata: name: db-headless spec: clusterIP: None # This makes it headless selector: app: database ports: - port: 5432
bash# DNS returns ALL pod IPs (not a single VIP) nslookup db-headless # db-headless.default.svc.cluster.local → 10.244.1.5 # → 10.244.2.3 # → 10.244.3.7 # Individual pod DNS (when used with StatefulSet): # db-0.db-headless.default.svc.cluster.local → 10.244.1.5 # db-1.db-headless.default.svc.cluster.local → 10.244.2.3
Service Comparison
| Feature | ClusterIP | NodePort | LoadBalancer | ExternalName |
|---|---|---|---|---|
| Internal access | Yes | Yes | Yes | Yes (DNS) |
| External access | No | Yes (node:port) | Yes (LB IP) | N/A |
| Static IP | ClusterIP | Node IPs | External IP | DNS alias |
| Cloud required | No | No | Yes | No |
| Use case | Internal services | Dev/testing | Production external | External service alias |
Kubernetes DNS
Every Service gets a DNS entry:
<service>.<namespace>.svc.cluster.local
Examples:
api-service.default.svc.cluster.local # Full FQDN
api-service.default.svc # Short form
api-service.default # Shorter
api-service # Same namespace
bash# Inside a pod: curl http://api-service # Same namespace curl http://api-service.production # Different namespace curl http://api-service.production.svc.cluster.local # Full FQDN
Pod DNS (only with Headless Service + StatefulSet):
<pod-name>.<service>.<namespace>.svc.cluster.local
db-0.db-headless.default.svc.cluster.local
Endpoints and EndpointSlices
A Service selects Pods via labels. The list of selected Pod IPs is stored in Endpoints:
bash# See which pods a service routes to kubectl get endpoints api-service # NAME ENDPOINTS AGE # api-service 10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080 # EndpointSlices (modern, more scalable) kubectl get endpointslices -l kubernetes.io/service-name=api-service
Network Policies
By default, all pods can talk to all other pods. Network Policies restrict this:
yamlapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-allow namespace: default spec: podSelector: matchLabels: app: api policyTypes: - Ingress - Egress ingress: # Allow traffic FROM web pods on port 8080 - from: - podSelector: matchLabels: app: web - namespaceSelector: matchLabels: name: frontend ports: - port: 8080 protocol: TCP egress: # Allow traffic TO database - to: - podSelector: matchLabels: app: database ports: - port: 5432 # Allow DNS - to: - namespaceSelector: {} ports: - port: 53 protocol: UDP - port: 53 protocol: TCP
Default Policies
yaml# Deny all ingress (whitelist approach) apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress spec: podSelector: {} # Applies to ALL pods in namespace policyTypes: - Ingress # No ingress rules = deny all incoming traffic
Note: Network Policies require a CNI plugin that supports them (Calico, Cilium, Weave). The default kubenet does NOT enforce them.
CNI (Container Network Interface)
CNI plugins handle pod networking:
| Plugin | Features | Used By |
|---|---|---|
| Calico | Network policies, BGP, eBPF | Most popular |
| Cilium | eBPF-based, observability, service mesh | Growing fast |
| Flannel | Simple overlay, no network policies | Basic clusters |
| Weave | Simple, encryption, network policies | Small clusters |
| AWS VPC CNI | Native AWS VPC networking | EKS |
| Azure CNI | Native Azure networking | AKS |
Service Mesh (Preview)
A service mesh adds a proxy sidecar to every pod for advanced networking:
┌─── Pod ─────────────────┐
│ ┌─────┐ ┌───────────┐ │
│ │ App │←→│ Envoy │ │ ← Sidecar proxy
│ │ │ │ Proxy │ │
│ └─────┘ └─────┬─────┘ │
└─────────────────┼───────┘
│ mTLS, retries, circuit breaking
┌─────────────────┼────────┐
│ ┌─────┐ ┌────┴────┐ │
│ │ App │←→│ Envoy │ │
│ └─────┘ └─────────┘ │
└──────────────────────────┘
Popular service meshes: Istio, Linkerd, Cilium Service Mesh See 23 - Service Mesh & Advanced Patterns for details.
kubectl Networking Commands
bash# List services kubectl get svc kubectl get svc -o wide # Describe service (see endpoints) kubectl describe svc api-service # Expose a deployment as a service kubectl expose deployment web --port=80 --target-port=8080 # Port forward (for debugging) kubectl port-forward svc/api-service 8080:80 # Test DNS from inside cluster kubectl run -it --rm debug --image=busybox -- nslookup api-service # List network policies kubectl get networkpolicies # Test connectivity kubectl run -it --rm curl --image=curlimages/curl -- curl api-service:80
FAANG Interview Angle
Common questions:
- "Explain the different Service types in Kubernetes"
- "How does service discovery work in K8s?"
- "What are Network Policies and why are they important?"
- "How would you expose an application to the internet?"
- "What is a headless service and when would you use it?"
Key answers:
- ClusterIP (internal), NodePort (node IP:port), LoadBalancer (cloud LB), ExternalName (DNS alias)
- DNS-based:
<service>.<namespace>.svc.cluster.local; also env vars - Network Policies restrict pod-to-pod traffic; default is allow-all; requires compatible CNI
- LoadBalancer Service or Ingress controller for HTTP/HTTPS routing
- Headless (clusterIP: None) returns pod IPs directly; used with StatefulSets for stable pod DNS