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)