Video Thumbnail for Lesson
4.9: StatefulSet

StatefulSet

In Kubernetes, a StatefulSet is used to manage stateful applications. Unlike Deployments, StatefulSets maintain a sticky identity for each of their Pods.

These Pods are created from the same spec but are not interchangeable: each has a persistent identifier that it maintains across any rescheduling.

Official docs: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/

Hands-On: Working with StatefulSets

We will create and examine a StatefulSet to understand it's behavior.

1. Create a Namespace for the Examples

First, we'll create a namespace for these examples and set it as the default.

# task 01-create-namespace
# - Create a namespace for these examples and set as default.
kubectl apply -f Namespace.yaml
kubens 04--statefulset

2. Apply a Minimal StatefulSet Configuration

Next, we will apply a minimal StatefulSet configuration:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-minimal
spec:
  serviceName: nginxs
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.26.0
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
  # We haven't covered PersistentVolumeClaims yet... we can ignore for now
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "standard"
        resources:
          requests:
            storage: 100Mi
# Service.nginxs.yaml ("headless" service)
apiVersion: v1
kind: Service
metadata:
  name: nginxs
spec:
  type: ClusterIP
  clusterIP: None
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
# Service.nginx.yaml (Standard clusterIP Service)
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Because each pod in the StatefulSet has a unique identity, we need to be able to connect to them independently.

The way this is handled in Kubernetes is through a "headless service", whichi is a ClusterIP with clusterIP: None.

With a headless service, Kubernetes does NOT provision a single IP to load balance across the pods.

# task 02-apply-minimal
# - Apply a minimal StatefulSet configuration.
kubectl apply -f Service.nginx.yaml
kubectl apply -f Service.nginxs.yaml
kubectl apply -f StatefulSet.nginx-minimal.yaml

To connect to one of the pods from within the clsuter we could then use the fully qualified domain name of:

nginx-minimal-0.nginxs.04-statefulset.svc.cluster.local

3. Delete the Minimal StatefulSet Configuration

The minimal StatefulSet example doesn't really demonstrate how one might use a StatefulSet in the real world so we let's delete it.

# task 03-delete-minimal
# - Delete the minimal StatefulSet configuration.
kubectl delete -f StatefulSet.nginx-minimal.yaml

4. Apply a StatefulSet Configuration with an Init Container

One method you might utilize the identity of a StatefulSet is to use an Init Container to generate a unique config for each replica.

For example, you might set pod 0 to be the primary replica of a database while pods 1-N might be read replicas.

We will demonstrate a simple version of this by using an init container to write the pods ordinal (number) to a file for the primary pod to read. The Kubernetes docs provide a similar example showing how a MySQL StatefulSet might do this.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-with-init-container
spec:
  serviceName: nginxs
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      initContainers:
        - name: populate-default-html
          image: nginx:1.26.0
          command:
            - bash
            - "-c"
            - |
              set -ex
              [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
              ordinal=${BASH_REMATCH[1]}
              echo "<h1>Hello from pod $ordinal</h1>" > /usr/share/nginx/html/index.html
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
      containers:
        - name: nginx
          image: nginx:1.26.0
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "standard"
        resources:
          requests:
            storage: 100Mi
# task 04-apply-with-init
# - Apply a statefulset configuration with an init container.
kubectl apply -f Service.nginx.yaml
kubectl apply -f Service.nginxs.yaml
kubectl apply -f StatefulSet.nginx-with-init-container.yaml

Each pod in the StatefulSet will then serve a file containing:

<h1>Hello from pod N</h1>

5. Delete the Namespace to Clean Up

Finally, clean up by deleting the namespace, which will also delete all resources within it.

# task 05-delete-namespace
# - Delete the namespace to clean up.
kubectl delete -f Namespace.yaml