Resizing StatefulSet Persistent Volumes in Kubernetes
Find out how to fix “spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden
error“
Introduction
Managing persistent storage in Kubernetes can be challenging, especially when working with StatefulSets, which are designed for workloads that require stable network identities and persistent volumes — such as databases, queues, and clustered applications. As these applications scale or their data grows, you’ll often find yourself needing to resize the persistent volumes associated with them.
However, resizing storage for StatefulSets isn’t as simple as updating a field in the manifest. If you attempt to directly change the volume size under the volumeClaimTemplates
section of a StatefulSet, you’ll encounter an error like:
"spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden"
This is a built-in safeguard in Kubernetes. It prevents direct changes to fields that would affect how the StatefulSet manages persistent volumes, to avoid unintentional data loss or instability. Specifically, Kubernetes disallows updates to volumeClaimTemplates
after a StatefulSet has been created, because these templates are used to generate PersistentVolumeClaims (PVCs) for each pod, and modifying them can lead to inconsistencies.
Because of this restriction, resizing storage requires an indirect and careful approach, such as modifying the PVCs themselves, using a compatible StorageClass that supports volume expansion, and potentially restarting pods to pick up changes. In this article, we’ll dive into the strategies for safely resizing StatefulSet storage, understand the limitations imposed by Kubernetes, and provide step-by-step guidance to help you do it without disrupting your workloads.
My Setup
I am going to use NATs helm chart which uses statefulset resource and I do find that storage gets filled up quickly when using Jetstream capabilities.
For reference, “NATs is a connective technology built for the ever increasingly hyper-connected world. It is a single technology that enables applications to securely communicate across any combination of cloud vendors, on-premise, edge, web and mobile, and devices. NATS consists of a family of open source products that are tightly integrated but can be deployed easily and independently. NATS is being used globally by thousands of companies, spanning use-cases including microservices, edge computing, mobile, IoT and can be used to augment or replace traditional messaging.”
Implementation and expanding volume
1. storage class
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-100-auto
provisioner: "ebs.csi.aws.com"
parameters:
type: "gp3"
iopsPerGB: "100" # minimum 30GB storage size
encrypted: "true"
throughput: "300"
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
In order to expand volume, make sure the storage class sets “allowVolumeExpansion: true”.
2. nats helm release
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: nats
namespace: nats
spec:
releaseName: nats
chart:
spec:
chart: nats
version: 1.3.3
sourceRef:
kind: HelmRepository
name: nats
namespace: flux-system
interval: 15m0s
timeout: 20m0s
install:
remediation:
retries: 3
values:
config:
cluster:
enabled: true
replicas: 3
websocket:
enabled: true
port: 443
jetstream:
enabled: true
fileStore:
pvc:
size: 50Gi
storageClassName: gp3-100-auto
container:
merge:
resources:
requests:
cpu: "2" # suggested minimum number of CPU cores
memory: 9Gi
limits:
memory: 9Gi
Jetstream block is the one we are going to update and increase storage from 50Gi to 100Gi
Resizing a PersistentValumeClaim by a StatefulSet
StatefulSets objects can’t be modified, other than the number of replicas, the update strategy, and the object template. If you try to modify any other specification, you’ll get the error below:
spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden
- We have the following PVC using above defined storage class
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
nats-js-nats-0 Bound pvc-bdbd3441-2b3a-470a-8a4d-4513270a6243 50Gi RWO gp3-100-auto <unset> 3m25s
nats-js-nats-1 Bound pvc-37d6f9ed-9244-4c50-96a8-569a3d3c4bf5 50Gi RWO gp3-100-auto <unset> 3m25s
nats-js-nats-2 Bound pvc-698f0107-489e-4945-ae6a-e79433d6a900 50Gi RWO gp3-100-auto <unset> 3m25s
2. scale down statefulset to 0 replicas currently we have 3
kubectl scale statefulsets nats -n nats --replicas=0
3. For each PVC, change the storage
kubectl edit pvc -n nats nats-js-nats-0
Change storage from 50Gi to 100Gi
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: gp3-100-auto
volumeMode: Filesystem
4. Scale up statefulset to 3 replicas to trigger volume expansion
kubectl scale statefulsets nats -n nats --replicas=3
This gives us desired PVC expansion
nats-js-nats-0 Bound pvc-bdbd3441-2b3a-470a-8a4d-4513270a6243 100Gi RWO gp3-100-auto <unset> 15m
nats-js-nats-1 Bound pvc-37d6f9ed-9244-4c50-96a8-569a3d3c4bf5 100Gi RWO gp3-100-auto <unset> 15m
nats-js-nats-2 Bound pvc-698f0107-489e-4945-ae6a-e79433d6a900 100Gi RWO gp3-100-auto <unset> 15m
5. delete the statefulset as we can’t update the existing one
kubectl delete sts nats -n nats --cascade=orphan
6. modify the helm release resource to increase storage and re-apply
jetstream:
enabled: true
fileStore:
pvc:
size: 100Gi
storageClassName: gp3-100-auto
I simply run kubectl apply -f nats.yaml
which deploys my helm release and creates updated statefulset. Pods that get created will be associated with the Volumes which we expanded earlier.
Conclusion
Although resizing a StatefulSet volume is not supported by default by Kubernetes, this is a tested and safe workaround. I hope you find this article useful and helpful in case you run into storage issues and require resizing volumes.