MINI PROJECT: Headless Service with StatefulSet (MySQL-style behavior)
π― Goal ## π§ Mental model (keep this in mind) ## π§© Project structure ## 1οΈβ£ Headless Service (NO Cluster IP) ## 2οΈβ£ StatefulSet (stable pod identity) ## 3οΈβ£ DNS test pod (to observe behavior) ## 4οΈβ£ Apply everything (ORDER MATTERS) ## 5οΈβ£ π₯ THE MOST IMPORTANT PART β DNS BEHAVIOR ### Enter the test pod ### π DNS lookup of the HEADLESS service ### π₯ DNS lookup of INDIVIDUAL PODS (THIS IS THE KEY) ## 6οΈβ£ Why databases NEED this ## 7οΈβ£ Visual intuition (whatβs happening) ## 8οΈβ£ One-line interview answer (remember this) ## π₯ SAME APP, TWO SERVICES ## π¦ CASE 1 β ClusterIP Service (DEFAULT BEHAVIOR) ## YAML (normal service) ### What Kubernetes does ### DNS behavior ## π What happens to traffic ## β Why this breaks databases ## π¨ CASE 2 β Headless Service (NO CLUSTER IP) ## YAML (headless service) ## π DNS behavior (this is the key difference) ## π§ Still not enough alone (important) ## π© ENTER STATEFULSET (THIS IS THE MISSING PIECE) ## π₯ THIS IS THE MOMENT IT CLICKS ### Write traffic ### Read traffic ## π SIDE-BY-SIDE SUMMARY (MEMORIZE THIS) ## π§ͺ Why we used dns-test pod ## π― ONE-LINE INTERVIEW ANSWER (IMPORTANT) By the end, you will clearly see: π mysql-headless.yaml π mysql-statefulset.yaml π What StatefulSet guarantees: β You will see MULTIPLE IPs (one per pod): π This is DNS round-robin. β Each one resolves to a specific pod IP. This is what ClusterIP can NEVER do. π‘ This is impossible with Deployment + ClusterIP. βA headless service removes the virtual IP and exposes pod identities via DNS. Combined with StatefulSets, it enables stable per-pod DNS, which is required for databases and leader-follower architectures.β That IP is NOT a pod.
Itβs a virtual service IP. Kubernetes decides where each request goes. ClusterIP is stateless-friendly, stateful-hostile. Now we change only one line. β οΈ No virtual IP
β οΈ DNS returns pod IPs directly This is DNS round-robin, not kube-proxy load balancing. If you stop here and use: Your app may still hit different pods, because DNS answers rotate. So headless alone is not the full solution. StatefulSet guarantees stable pod names: And Kubernetes automatically creates DNS records like: No guessing. No randomness. You cannot βsee DNSβ from your laptop. So we create a pod just to ask: Thatβs how SREs debug real prod issues. βClusterIP services hide pod identity and load balance traffic, which is unsuitable for databases. Headless services expose pod IPs via DNS, and when combined with StatefulSets, provide stable per-pod DNS required for stateful workloads.β Templates let you quickly answer FAQs or store snippets for re-use. as well , this person and/or CODE_BLOCK:
headless-demo/
βββ mysql-headless.yaml
βββ mysql-statefulset.yaml
βββ dns-test.yaml CODE_BLOCK:
headless-demo/
βββ mysql-headless.yaml
βββ mysql-statefulset.yaml
βββ dns-test.yaml CODE_BLOCK:
headless-demo/
βββ mysql-headless.yaml
βββ mysql-statefulset.yaml
βββ dns-test.yaml COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS MAKES IT HEADLESS selector: app: mysql ports: - port: 3306 COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS MAKES IT HEADLESS selector: app: mysql ports: - port: 3306 COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS MAKES IT HEADLESS selector: app: mysql ports: - port: 3306 COMMAND_BLOCK:
apiVersion: apps/v1
kind: StatefulSet
metadata: name: mysql
spec: serviceName: mysql-headless # π REQUIRED replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: root ports: - containerPort: 3306 COMMAND_BLOCK:
apiVersion: apps/v1
kind: StatefulSet
metadata: name: mysql
spec: serviceName: mysql-headless # π REQUIRED replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: root ports: - containerPort: 3306 COMMAND_BLOCK:
apiVersion: apps/v1
kind: StatefulSet
metadata: name: mysql
spec: serviceName: mysql-headless # π REQUIRED replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: root ports: - containerPort: 3306 CODE_BLOCK:
mysql-0 mysql-1 mysql-2 CODE_BLOCK:
mysql-0 mysql-1 mysql-2 CODE_BLOCK:
mysql-0 mysql-1 mysql-2 CODE_BLOCK:
apiVersion: v1
kind: Pod
metadata: name: dns-test
spec: containers: - name: dns image: busybox:1.28 command: ["sleep", "3600"] CODE_BLOCK:
apiVersion: v1
kind: Pod
metadata: name: dns-test
spec: containers: - name: dns image: busybox:1.28 command: ["sleep", "3600"] CODE_BLOCK:
apiVersion: v1
kind: Pod
metadata: name: dns-test
spec: containers: - name: dns image: busybox:1.28 command: ["sleep", "3600"] COMMAND_BLOCK:
kubectl apply -f mysql-headless.yaml
kubectl apply -f mysql-statefulset.yaml
kubectl apply -f dns-test.yaml COMMAND_BLOCK:
kubectl apply -f mysql-headless.yaml
kubectl apply -f mysql-statefulset.yaml
kubectl apply -f dns-test.yaml COMMAND_BLOCK:
kubectl apply -f mysql-headless.yaml
kubectl apply -f mysql-statefulset.yaml
kubectl apply -f dns-test.yaml COMMAND_BLOCK:
kubectl get pods COMMAND_BLOCK:
kubectl get pods COMMAND_BLOCK:
kubectl get pods CODE_BLOCK:
mysql-0 Running
mysql-1 Running
mysql-2 Running
dns-test Running CODE_BLOCK:
mysql-0 Running
mysql-1 Running
mysql-2 Running
dns-test Running CODE_BLOCK:
mysql-0 Running
mysql-1 Running
mysql-2 Running
dns-test Running COMMAND_BLOCK:
kubectl exec -it dns-test -- sh COMMAND_BLOCK:
kubectl exec -it dns-test -- sh COMMAND_BLOCK:
kubectl exec -it dns-test -- sh CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
Address: 10.244.0.12
Address: 10.244.0.13
Address: 10.244.0.14 CODE_BLOCK:
Address: 10.244.0.12
Address: 10.244.0.13
Address: 10.244.0.14 CODE_BLOCK:
Address: 10.244.0.12
Address: 10.244.0.13
Address: 10.244.0.14 CODE_BLOCK:
nslookup mysql-0.mysql-headless
nslookup mysql-1.mysql-headless
nslookup mysql-2.mysql-headless CODE_BLOCK:
nslookup mysql-0.mysql-headless
nslookup mysql-1.mysql-headless
nslookup mysql-2.mysql-headless CODE_BLOCK:
nslookup mysql-0.mysql-headless
nslookup mysql-1.mysql-headless
nslookup mysql-2.mysql-headless CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-clusterip
spec: selector: app: mysql ports: - port: 3306 CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-clusterip
spec: selector: app: mysql ports: - port: 3306 CODE_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-clusterip
spec: selector: app: mysql ports: - port: 3306 CODE_BLOCK:
nslookup mysql-clusterip CODE_BLOCK:
nslookup mysql-clusterip CODE_BLOCK:
nslookup mysql-clusterip CODE_BLOCK:
Name: mysql-clusterip
Address: 10.96.120.15 <-- ONE IP CODE_BLOCK:
Name: mysql-clusterip
Address: 10.96.120.15 <-- ONE IP CODE_BLOCK:
Name: mysql-clusterip
Address: 10.96.120.15 <-- ONE IP COMMAND_BLOCK:
todo-app | v
mysql-clusterip (10.96.120.15) | +--> mysql-0 +--> mysql-1 +--> mysql-2 COMMAND_BLOCK:
todo-app | v
mysql-clusterip (10.96.120.15) | +--> mysql-0 +--> mysql-1 +--> mysql-2 COMMAND_BLOCK:
todo-app | v
mysql-clusterip (10.96.120.15) | +--> mysql-0 +--> mysql-1 +--> mysql-2 COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS IS EVERYTHING selector: app: mysql ports: - port: 3306 COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS IS EVERYTHING selector: app: mysql ports: - port: 3306 COMMAND_BLOCK:
apiVersion: v1
kind: Service
metadata: name: mysql-headless
spec: clusterIP: None # π THIS IS EVERYTHING selector: app: mysql ports: - port: 3306 CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
nslookup mysql-headless CODE_BLOCK:
Address: 10.244.0.10 (mysql-0)
Address: 10.244.0.11 (mysql-1)
Address: 10.244.0.12 (mysql-2) CODE_BLOCK:
Address: 10.244.0.10 (mysql-0)
Address: 10.244.0.11 (mysql-1)
Address: 10.244.0.12 (mysql-2) CODE_BLOCK:
Address: 10.244.0.10 (mysql-0)
Address: 10.244.0.11 (mysql-1)
Address: 10.244.0.12 (mysql-2) CODE_BLOCK:
mysql-headless CODE_BLOCK:
mysql-headless CODE_BLOCK:
mysql-headless CODE_BLOCK:
mysql-0
mysql-1
mysql-2 CODE_BLOCK:
mysql-0
mysql-1
mysql-2 CODE_BLOCK:
mysql-0
mysql-1
mysql-2 CODE_BLOCK:
mysql-0.mysql-headless
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
mysql-0.mysql-headless
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
mysql-0.mysql-headless
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
mysql-0.mysql-headless CODE_BLOCK:
mysql-0.mysql-headless CODE_BLOCK:
mysql-0.mysql-headless CODE_BLOCK:
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
mysql-1.mysql-headless
mysql-2.mysql-headless CODE_BLOCK:
nslookup <service-name> CODE_BLOCK:
nslookup <service-name> CODE_BLOCK:
nslookup <service-name> - Why ClusterIP hides pod identity
- Why Headless Service exposes pod identity
- How StatefulSet + Headless Service gives stable DNS per pod
- Why databases need this - No virtual IP
- DNS will return pod IPs - Pods named: - Names NEVER change
- Identity is stable - Writes β mysql-0.mysql-headless
- Reads β replicas
- Replication β stable target - Creates ONE virtual IP
- Hides all pod IPs
- kube-proxy load balances traffic - Signup request β mysql-2 (write happens there)
- Login request β mysql-0 (no data)
- β login fails - write and read hit different pods
- pod identity is hidden
- you cannot say βalways go to mysql-0β