Services
A Service gives a stable name and IP to a set of Pods. ClusterIP for inside-cluster traffic, NodePort for exposing on each node, LoadBalancer for cloud LBs, ExternalName for DNS aliases.
ClusterIP / NodePort / LoadBalancer / headless
EXAMPLE
# 1) ClusterIP — default. In-cluster only.
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
type: ClusterIP
selector: { app: web }
ports:
- port: 80 # cluster-side port
targetPort: 3000 # container port
protocol: TCP
# Reach it as web.namespace.svc.cluster.local:80
# 2) NodePort — opens the same port on every node
apiVersion: v1
kind: Service
metadata: { name: web }
spec:
type: NodePort
selector: { app: web }
ports:
- port: 80
targetPort: 3000
nodePort: 30080 # 30000-32767 range
# Reach it as <ANY_NODE_IP>:30080
# 3) LoadBalancer — provisions a cloud LB (AWS NLB/ALB, GCP, Azure)
apiVersion: v1
kind: Service
metadata:
name: web
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
type: LoadBalancer
selector: { app: web }
ports:
- port: 80
targetPort: 3000
# kubectl get svc → EXTERNAL-IP column
# 4) ExternalName — DNS alias (no proxying)
apiVersion: v1
kind: Service
metadata: { name: postgres }
spec:
type: ExternalName
externalName: rds.cluster-abcdef.us-east-1.rds.amazonaws.com
# postgres.default.svc → CNAME → rds...
# 5) Headless service — DNS for individual Pods (StatefulSets)
apiVersion: v1
kind: Service
metadata: { name: postgres-headless }
spec:
clusterIP: None # ← makes it headless
selector: { app: postgres }
ports: [{ port: 5432 }]
# DNS returns one A record per Pod IP:
# postgres-0.postgres-headless.default.svc.cluster.local
# postgres-1.postgres-headless.default.svc.cluster.local
# 6) Session affinity — sticky sessions by client IP
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP: { timeoutSeconds: 10800 } # 3 hours
# 7) Internal load balancer (cloud) — keep inside the VPC
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: internal
cloud.google.com/load-balancer-type: Internal
# 8) Service discovery
# In-cluster DNS works automatically:
# curl http://web.production.svc.cluster.local
# Short form within the same namespace:
# curl http://web
# Environment variables — auto-injected by kubelet (older pattern, rarely used now)
# WEB_SERVICE_HOST=10.0.1.42
# WEB_SERVICE_PORT=80
# 9) Multi-port Service
spec:
ports:
- name: http
port: 80
targetPort: 3000
- name: metrics
port: 9090
targetPort: metrics-port # named port on the Pod
# 10) Common kubectl
kubectl get svc -A
kubectl describe svc web
kubectl port-forward svc/web 8080:80 # local debug
kubectl run tmp --rm -it --image=alpine -- /bin/sh
# wget -qO- web.production:80
# 11) Endpoints & EndpointSlices
# Kubernetes maintains an EndpointSlice per Service:
kubectl get endpointslice -l kubernetes.io/service-name=web
# Lists current Pod IPs the Service routes to. Updates as Pods scale.
# 12) Best practices
# • Prefer Ingress + ClusterIP over many LoadBalancer Services (cost!)
# • Use a Service per logical role, not per Pod
# • Set readinessProbe on Pods — Service routes to ready Pods only
# • Use NetworkPolicies to restrict who can call which Service
Why it matters
Use one Ingress + many ClusterIP Services instead of many LoadBalancer Services — one cloud LB instead of dozens, same routing flexibility, and you get TLS termination + host/path-based routing in the bargain.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
kind: Service
spec:
selector: { app: api }
ports: [{ port: 80, targetPort: 3000 }]
type: ClusterIP # NodePort, LoadBalancer, ExternalName
Try it Yourself »
Exercise
Default Service type for internal-only is…
type:
PascalCase; 9 chars.
Discussion
Loading…