StatefulSets & Archiviazione persistente in Kubernetes

Distribuisci applicazioni stateful con scalabilità ordinata e dati persistenti

Kubernetes StatefulSets sono la soluzione ideale per gestire applicazioni stateful che richiedono identità stabili, archiviazione persistente e modelli di distribuzione ordinati—essenziali per database, sistemi distribuiti e strati di caching.

Se sei nuovo a Kubernetes o stai configurando un cluster, considera di esplorare distribuzioni Kubernetes come k3s o MicroK8s per lo sviluppo, o installare Kubernetes con Kubespray per cluster a livello di produzione.

presentazione nel caffè Questa bella immagine è generata da modello AI Flux 1 dev.

Cosa sono i StatefulSets?

I StatefulSets sono un oggetto API di lavoro Kubernetes progettato specificamente per gestire applicazioni stateful. A differenza delle Deployments che trattano tutti i pod come intercambiabili, i StatefulSets mantengono un’identità unica per ogni pod con garanzie riguardo all’ordinamento e all’unicità.

Funzionalità principali:

  • Identificatori di rete stabili: Ogni pod riceve un nome host prevedibile che persiste anche dopo i riavvii
  • Archiviazione persistente: Richieste di PersistentVolumeClaims dedicate che seguono i pod anche durante i riassegnamenti
  • Distribuzione ordinata: I pod vengono creati in modo sequenziale (0, 1, 2…) e terminano nell’ordine inverso
  • Aggiornamenti ordinati: Gli aggiornamenti rolling procedono nell’ordine, garantendo la stabilità dell’applicazione

I StatefulSets sono fondamentali per applicazioni come PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis e etcd—qualsiasi carico di lavoro in cui l’identità del pod e la persistenza dei dati siano importanti.

Comprendere l’archiviazione persistente in Kubernetes

Kubernetes fornisce uno strato di astrazione avanzato per l’archiviazione che separa la gestione dell’archiviazione dal ciclo di vita dei pod:

Componenti di archiviazione

PersistentVolume (PV): Un frammento di archiviazione nel cluster provvisto da un amministratore o creato dinamicamente tramite una StorageClass. I PV esistono indipendentemente dai pod.

PersistentVolumeClaim (PVC): Una richiesta di archiviazione da parte di un pod. I PVC si legano a PV disponibili che corrispondono ai loro requisiti (dimensione, modalità di accesso, classe di archiviazione).

StorageClass: Definisce diverse “classi” di archiviazione con vari provveditori (AWS EBS, GCE PD, Azure Disk, NFS, ecc.) e parametri come replicazione, livelli di prestazioni e politiche di backup.

Modalità di accesso

  • ReadWriteOnce (RWO): Volume montato in lettura/scrittura da un singolo nodo
  • ReadOnlyMany (ROX): Volume montato in sola lettura da molti nodi
  • ReadWriteMany (RWX): Volume montato in lettura/scrittura da molti nodi (richiede backend di archiviazione speciali)

Architettura di archiviazione dei StatefulSets

I StatefulSets utilizzano volumeClaimTemplates per creare automaticamente PersistentVolumeClaims per ogni replica del pod. Questo è fondamentalmente diverso dalle Deployments:

Come funzionano le volumeClaimTemplates

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Quando crei questo StatefulSet:

  1. Kubernetes crea il pod mysql-0 e il PVC data-mysql-0
  2. Poi crea il pod mysql-1 e il PVC data-mysql-1
  3. Infine crea il pod mysql-2 e il PVC data-mysql-2

Ogni pod riceve il proprio 10 GB di volume persistente dedicato. Se mysql-1 viene eliminato o riassegnato, Kubernetes lo ricrea e riconnette lo stesso PVC data-mysql-1, preservando tutti i dati.

Creare un servizio headless per i StatefulSets

I StatefulSets richiedono un servizio headless per fornire identità di rete stabili:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Questo lo rende un servizio headless
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Questo crea voci DNS per ogni pod:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Le applicazioni possono connettersi direttamente a istanze specifiche del pod utilizzando questi nomi DNS stabili.

Pattern di distribuzione dei StatefulSets

Per i team che gestiscono distribuzioni complesse di Kubernetes, Helm Charts forniscono un modo potente per imballare e distribuire i StatefulSets con modelli, versionamento e gestione delle dipendenze. Helm semplifica la gestione delle configurazioni dei StatefulSet in diversi ambienti.

Scalabilità ordinata

Quando si scalano da 3 a 5 repliche:

kubectl scale statefulset mysql --replicas=5

Kubernetes crea i pod nell’ordine: mysql-3 → attende che sia pronto → mysql-4

Quando si scalano da 5 a 3 repliche:

kubectl scale statefulset mysql --replicas=3

Kubernetes termina nell’ordine inverso: mysql-4 → attende la terminazione → mysql-3

Aggiornamenti rolling

I StatefulSets supportano due strategie di aggiornamento:

OnDelete: Aggiornamenti manuali—i pod vengono aggiornati solo quando li elimini RollingUpdate: Aggiornamenti automatici sequenziali nell’ordine inverso

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Aggiorna solo i pod con ordinal >= 2

Il parametro partition abilita le distribuzioni canary—puoi aggiornare prima i pod con numeri più alti e testare prima di distribuire a tutte le repliche.

Best Practices per l’archiviazione

Provisioning dinamico

Utilizza sempre le StorageClasses per il provisioning dinamico dei volumi:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Abilita il ridimensionamento dei PVC senza ricrearli reclaimPolicy: Retain mantiene i dati del PV dopo l’eliminazione del PVC, Delete li elimina automaticamente

Politiche di conservazione dei PVC

Kubernetes 1.23+ supporta persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Conserva i PVC quando viene eliminato il StatefulSet
    whenScaled: Delete     # Elimina i PVC quando si riduce la scala

Opzioni:

  • Retain: Conserva i PVC (comportamento predefinito, più sicuro)
  • Delete: Elimina automaticamente i PVC (utile per ambienti di sviluppo)

Strategie di backup

Snapshots del volume: Utilizza le risorse VolumeSnapshot per creare backup puntuali nel tempo

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Backup a livello di applicazione: Utilizza strumenti come mysqldump, pg_dump o Velero per i backup specifici del database

Replicazione distribuita: Configura la replicazione a livello di applicazione (replicazione MySQL, replicazione streaming PostgreSQL) come prima linea di difesa

Caso d’uso reale

Cluster del database (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Cache distribuita (Redis)

Per i cluster Redis, è necessario utilizzare sia un StatefulSet che una configurazione attenta:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Coda di messaggi (Kafka)

Kafka richiede sia l’archiviazione persistente per i log che identità di rete stabili per la coordinazione dei broker:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    match标签:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Monitoraggio e risoluzione dei problemi

Per un riferimento completo dei comandi Kubernetes utilizzati in questa sezione, vedi il Kubernetes Cheatsheet.

Verifica lo stato dei StatefulSets

# Visualizza i dettagli del StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Controlla l'ordine di creazione e lo stato dei pod
kubectl get pods -l app=mysql -w

# Visualizza lo stato dei PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Problemi comuni

Pod bloccato in stato Pending: Controlla lo stato del PVC e la disponibilità dell’archiviazione

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Archiviazione piena: Espandi il PVC se la StorageClass lo consente

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Pod che non termina: Controlla i blocchi a livello di applicazione o i problemi di smontaggio del volume

kubectl delete pod mysql-0 --grace-period=0 --force

Metriche da monitorare

  • Utilizzo dell’archiviazione: Monitora la capacità e la percentuale di utilizzo del PVC
  • Prestazioni I/O: Traccia IOPS, throughput e latenza
  • Riavvii del pod: I riavvii frequenti possono indicare problemi di archiviazione
  • Tempo di binding del PVC: Un binding lento suggerisce problemi di provisioning

Strategie di migrazione

Quando si migra ai StatefulSets, assicurati che il cluster Kubernetes sia correttamente configurato. Per le configurazioni di laboratorio domestico o piccoli cluster, consulta la nostra comparazione completa delle distribuzioni Kubernetes per scegliere la piattaforma giusta per i requisiti del tuo carico di lavoro.

Dalla Deployment ai StatefulSets

  1. Crea un StatefulSet con volumeClaimTemplates
  2. Scala giù la Deployment in modo graduale
  3. Ripristina i dati dai backup ai pod del StatefulSet
  4. Aggiorna le referenze DNS/Servizio
  5. Elimina l’antica Deployment e i PVC

Backup prima della migrazione

# Snapshot dei PVC esistenti
kubectl get pvc -o yaml > pvc-backup.yaml

# Crea snapshot del volume
kubectl apply -f volume-snapshot.yaml

Considerazioni sulla sicurezza

Crittografia dell’archiviazione

Abilita la crittografia a riposo utilizzando i parametri della StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Controllo degli accessi

Utilizza RBAC per limitare chi può creare/modificare i StatefulSets e i PVC:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Politiche di rete

Limita la comunicazione tra i pod:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Ottimizzazione delle prestazioni

Livelli di prestazioni dell’archiviazione

Scegli le StorageClasses appropriate in base al carico di lavoro:

  • Alta IOPS: Database con letture/scritture casuali pesanti (gp3, io2)
  • Alta throughput: Aggregazione dei log, analisi (st1, sc1)
  • Equilibrata: Applicazioni generali (gp3)

Distribuzione dei pod

Utilizza l’anti-affinità dei pod per distribuire i pod dei StatefulSet tra zone di disponibilità:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Richieste e limiti delle risorse

Imposta le risorse appropriate per prestazioni costanti:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Pattern avanzati

Applicazione stateful con init containers

Utilizza init containers per l’inizializzazione del database:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Pod multi-container per sidecar

Aggiungi sidecar per backup o agenti di monitoraggio:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... configurazione mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps per la configurazione dinamica

Separa la configurazione dalla definizione del StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config