DEV Community

Cover image for Part-112: 🚀 Deploy StatefulSet with Headless Service in Google Kubernetes Engine (GKE)
Latchu@DevOps
Latchu@DevOps

Posted on

Part-112: 🚀 Deploy StatefulSet with Headless Service in Google Kubernetes Engine (GKE)

In this guide, we’ll walk through deploying a Kubernetes StatefulSet, attaching persistent volumes, and exposing it via a headless service in Google Kubernetes Engine (GKE).
We’ll also explore what happens when StatefulSet Pods are deleted — and how Kubernetes ensures state persistence.


🧠 Step 01: Introduction

In this demo, you will:

✅ Create and deploy a StatefulSet
✅ Create a Headless Service to access StatefulSet pods
✅ Verify pod identity, storage persistence, and behavior during pod recreation


🧩 Step 02: Review 01-kubernetes-statefulset.yaml

Each StatefulSet Pod will get its own PersistentVolumeClaim (PVC).
Here, we use the premium-rwo storage class in GKE.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp1-sts
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "myapp1-hs-svc"
  replicas: 3
  minReadySeconds: 10
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 10
      initContainers:
      - name: init-pass-hostname
        image: alpine
        command: ["/bin/sh", "-c", "echo POD_HOSTNAME: $HOSTNAME > /usr/share/nginx/html/index.html"]
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html   
      containers:
      - name: nginx
        image: ghcr.io/stacksimplify/kubenginx:1.0.0
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html            
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "premium-rwo"
      resources:
        requests:
          storage: 1Gi
Enter fullscreen mode Exit fullscreen mode

📝 Key Points:

  • Each Pod gets its own unique PVC using volumeClaimTemplates
  • Pods are created one by one, not simultaneously
  • The init container writes its hostname to /usr/share/nginx/html/index.html

🧠 Step 03: Review 02-kubernetes-headless-service.yaml

A Headless Service gives each StatefulSet Pod a unique DNS entry instead of load balancing them.

apiVersion: v1
kind: Service
metadata:
  name: myapp1-hs-svc
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
Enter fullscreen mode Exit fullscreen mode

📝 Key Points:

  1. clusterIP: None makes it headless
  2. Each Pod gets its own resolvable DNS name

Example:

  • myapp1-sts-0.myapp1-hs-svc.default.svc.cluster.local
  • myapp1-sts-1.myapp1-hs-svc.default.svc.cluster.local

🧠 Step 04: Review 03-curl-pod.yaml

We’ll use this simple pod to test the headless service connectivity.

apiVersion: v1
kind: Pod
metadata:
  name: curl-pod
spec:
  containers:
  - name: curl
    image: curlimages/curl 
    command: [ "sleep", "600" ]
Enter fullscreen mode Exit fullscreen mode

⚙️ Step 05: Deploy StatefulSet and Verify

# Deploy Kubernetes manifests
kubectl apply -f kube-manifests/01-kubernetes-statefulset.yaml
kubectl apply -f kube-manifests/02-kubernetes-headless-service.yaml
kubectl apply -f kube-manifests/03-curl-pod.yaml

# List Pods
kubectl get pods
Enter fullscreen mode Exit fullscreen mode

s1

🧩 Observations:

  1. Pods are created one after another.
  2. Each Pod gets its own PVC automatically.
kubectl get statefulset
kubectl describe sts myapp1-sts
Enter fullscreen mode Exit fullscreen mode

✅ You’ll see:

  • PVC created for Pod-0, then Pod-0 starts
  • PVC created for Pod-1, then Pod-1 starts
  • PVC created for Pod-2, then Pod-2 starts

🗄️ Step 06: Verify Persistent Volumes

kubectl get pvc
kubectl get pv
Enter fullscreen mode Exit fullscreen mode

s2

Then, go to Google Cloud Console → Compute Engine → Disks

🧩 Observation:

You’ll see 3 Persistent Disks automatically created — one for each StatefulSet pod.

s3


🌐 Step 07: Verify Headless Service

kubectl get svc
kubectl describe svc myapp1-hs-svc
kubectl get endpoints myapp1-hs-svc
Enter fullscreen mode Exit fullscreen mode

s4

🧩 Observation:

Endpoints look like:

10.124.0.11:80, 10.124.1.12:80, 10.124.2.11:80
Enter fullscreen mode Exit fullscreen mode

Test using curl-pod

kubectl exec -it curl-pod -- /bin/sh
Enter fullscreen mode Exit fullscreen mode

Inside curl-pod, run:

curl myapp1-hs-svc.default.svc.cluster.local
Enter fullscreen mode Exit fullscreen mode

Or continuously:

while true; do curl -s "http://myapp1-hs-svc.default.svc.cluster.local"; sleep 1; done
Enter fullscreen mode Exit fullscreen mode

s5

🧩 Observation:

You’ll see responses from multiple Pods, confirming traffic distribution.

Test individual Pod endpoints

# nslookup test
nslookup myapp1-sts-0.myapp1-hs-svc.default.svc.cluster.local
nslookup myapp1-sts-1.myapp1-hs-svc.default.svc.cluster.local
nslookup myapp1-sts-2.myapp1-hs-svc.default.svc.cluster.local

# curl test
curl myapp1-sts-0.myapp1-hs-svc.default.svc.cluster.local
curl myapp1-sts-1.myapp1-hs-svc.default.svc.cluster.local
curl myapp1-sts-2.myapp1-hs-svc.default.svc.cluster.local
Enter fullscreen mode Exit fullscreen mode

🧩 Observation:

  • Each request goes directly to the specific Pod. (Perfect for databases like MySQL master/replica setups.)

🔁 Step 08: Delete a Pod and Observe Behavior

kubectl get pods
kubectl delete pod myapp1-sts-0
kubectl get pods
Enter fullscreen mode Exit fullscreen mode

s6

🧩 Observation:

The Pod is automatically recreated with the same name (myapp1-sts-0)

It reattaches the same PersistentVolumeClaim

Verify:

kubectl describe pod myapp1-sts-0
Enter fullscreen mode Exit fullscreen mode

✅ You’ll see the same claim name:

ClaimName: www-myapp1-sts-0
Enter fullscreen mode Exit fullscreen mode

🧹 Step 09: Clean-Up

# Delete manifests
kubectl delete -f kube-manifests/01-kubernetes-statefulset.yaml
kubectl delete -f kube-manifests/02-kubernetes-headless-service.yaml
kubectl delete -f kube-manifests/03-curl-pod.yaml
Enter fullscreen mode Exit fullscreen mode

🧩 Observation:

PVCs and PVs still remain — Persistent data survives even after StatefulSet deletion.

To fully clean up:

kubectl delete pvc www-myapp1-sts-0
kubectl delete pvc www-myapp1-sts-1
kubectl delete pvc www-myapp1-sts-2
Enter fullscreen mode Exit fullscreen mode

Then confirm:

kubectl get pvc
kubectl get pv
Enter fullscreen mode Exit fullscreen mode

🧩 Observation:

  • Volumes will take a couple of minutes to delete.
  • Check in GCP → Compute Engine → Disks to verify deletion.

s7


✅ Summary

Concept Description
StatefulSet Manages Pods with stable identity and storage
Headless Service Provides DNS-based access to individual Pods
PVC/PV Ensures persistent data even if Pods restart
Deletion Behavior Pods recreate with the same name and storage

💡 Real-World Use Cases

  • Databases (MySQL, PostgreSQL)
  • Message Brokers (Kafka, RabbitMQ)
  • Caching Systems (Redis)
  • Search Engines (Elasticsearch)
  • Coordination Services (Zookeeper, Cassandra)

🎯 Final Thoughts

StatefulSets are essential when your workloads require stable identity, ordered deployment, and persistent storage.
They ensure data integrity and reliable scaling for stateful workloads — a cornerstone of modern cloud-native database and messaging systems.


🌟 Thanks for reading! If this post added value, a like ❤️, follow, or share would encourage me to keep creating more content.


— Latchu | Senior DevOps & Cloud Engineer

☁️ AWS | GCP | ☸️ Kubernetes | 🔐 Security | ⚡ Automation
📌 Sharing hands-on guides, best practices & real-world cloud solutions

Top comments (0)