CKAD — Service Types
How Kubernetes Networking Works
Every Pod gets its own IP address. Pods can communicate with each other directly using these IPs. But Pod IPs change when Pods restart or reschedule.
Services solve this by providing:
- A stable IP address (ClusterIP)
- A stable DNS name
- Load balancing across matching Pods
The Service–Pod Relationship
Services find Pods using label selectors. When a Service matches Pods, Kubernetes creates an Endpoints object that tracks the Pod IPs.
┌──────────────┐
│ Service │
│ 10.96.0.50 │
│ app=web │
└──────┬───────┘
│ selector: app=web
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Pod │ │ Pod │ │ Pod │
│ app=web │ │ app=web │ │ app=web │
│ 10.1.0.5 │ │ 10.1.0.6 │ │ 10.1.0.7 │
└─────────┘ └─────────┘ └─────────┘
Service Types
| Type | Description | Accessible From |
|---|---|---|
ClusterIP |
Internal-only IP (default) | Inside the cluster |
NodePort |
Exposes on each node’s IP at a static port | Outside the cluster via <NodeIP>:<NodePort> |
LoadBalancer |
Provisions an external load balancer | Outside via cloud provider LB |
ExternalName |
Maps to an external DNS name | DNS alias only |
ClusterIP (Default)
The most common type. Only accessible from within the cluster.
# Imperative — create a deployment first
kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl expose deployment web --port=80 --target-port=80
# This creates a ClusterIP Service
kubectl get svc web
# Expected:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web ClusterIP 10.96.45.12 <none> 80/TCP 5s
# Declarative
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 80
protocol: TCP
| Field | Description |
|---|---|
port |
Port the Service listens on |
targetPort |
Port on the Pod the traffic is sent to |
protocol |
TCP (default) or UDP |
Test ClusterIP from Inside the Cluster
# Run a temporary Pod to test
kubectl run test --image=busybox:1.36 --rm -it -- wget -qO- http://web
# Expected: nginx welcome page HTML
# Or use the full DNS name
kubectl run test --image=busybox:1.36 --rm -it -- wget -qO- http://web.default.svc.cluster.local
NodePort
Exposes the Service on a static port (30000-32767) on every node.
# Imperative
kubectl expose deployment web --type=NodePort --port=80 --target-port=80 --name=web-nodeport
# Declarative
apiVersion: v1
kind: Service
metadata:
name: web-nodeport
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Optional: Kubernetes assigns one if omitted
kubectl get svc web-nodeport
# Expected:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web-nodeport NodePort 10.96.78.34 <none> 80:30080/TCP 5s
# Access from outside: http://<any-node-ip>:30080
LoadBalancer
Extends NodePort. The cloud provider provisions an external load balancer.
apiVersion: v1
kind: Service
metadata:
name: web-lb
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
kubectl get svc web-lb
# Expected (cloud environment):
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web-lb LoadBalancer 10.96.12.55 34.120.15.200 80:31234/TCP 30s
On local clusters (kind, minikube), EXTERNAL-IP stays <pending> unless you have MetalLB installed.
ExternalName
Maps a Service to an external DNS name. No proxying — just a CNAME record.
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.example.com
# From inside a Pod:
# nslookup external-db.default.svc.cluster.local
# Returns: db.example.com
Use cases:
- Referencing external databases or APIs
- Migrating services gradually to Kubernetes
- Abstracting external service locations