La lliçó anterior va acabar amb un PersistentVolume que va aparèixer tot sol, i amb una caixa negra sense obrir: vam dir "l'aprovisionador crea el volum" sense explicar qui és aquest aprovisionador, com s'assabenta que hi ha un PVC pendent, com connecta el disc a un node i com acaba muntat al contenidor. Aquesta maquinària es diu CSI, la Container Storage Interface, i entendre-la és el que separa qui aplica manifests de qui pot diagnosticar per què un pod porta vint minuts en ContainerCreating. A més, la StorageClass anunciava dues capacitats que encara no hem fet servir: l'expansió d'un volum en marxa i els snapshots, imprescindibles abans de tocar l'esquema d'una base de dades en producció. En aquesta lliçó les posaràs a treballar sobre postgres-reserves, i tancaràs amb l'advertiment que més incidents evita: un snapshot no és una còpia de seguretat.

Contingut

  1. Quin problema va resoldre CSI
  2. L'arquitectura: connector de controlador i connector de node
  3. Els sidecars i els objectes CSIDriver i CSINode
  4. El flux complet d'un volum, d'extrem a extrem
  5. Preparar el clúster de pràctiques
  6. Expansió de volums: requisits i mecànica
  7. Expansió en línia davant d'expansió amb reinici
  8. Snapshots: els tres objectes
  9. Crear un snapshot de postgres-reserves
  10. Restaurar des d'un snapshot amb dataSource
  11. Clonar un PVC
  12. L'advertiment: consistència i què NO és un snapshot

  1. Quin problema va resoldre CSI

Abans del 2018, el codi que parlava amb AWS EBS, amb Google Persistent Disk, amb Ceph o amb NetApp vivia dins del repositori de Kubernetes. Se'ls anomenava controladors in-tree, i el model tenia tres problemes greus:

Problema Conseqüència
El codi del proveïdor anava dins de Kubernetes Una fallada al driver d'un fabricant podia tombar el kube-controller-manager
Els cicles de publicació anaven acoblats Corregir un error exigia esperar una versió de Kubernetes, i cada millora passava per la revisió del projecte
Les credencials del proveïdor vivien al pla de control Superfície d'atac considerable

CSI (Container Storage Interface) és una especificació estàndard —no exclusiva de Kubernetes: la fan servir també Mesos i Nomad— que defineix un contracte gRPC entre l'orquestrador i el sistema d'emmagatzematge. El proveïdor implementa aquest contracte en un component propi, l'empaqueta com a contenidor i el desplega al clúster, com qualsevol altra càrrega de treball. El resultat: els controladors in-tree van quedar obsolets i van ser eliminats (kubernetes.io/aws-ebs, kubernetes.io/gce-pd, kubernetes.io/azure-disk ja no existeixen a 1.30+). Avui tot l'emmagatzematge de xarxa passa per CSI, i hi ha més d'un centenar de drivers disponibles.

  1. L'arquitectura: connector de controlador i connector de node

Un driver CSI es desplega sempre en dues meitats, perquè les operacions d'emmagatzematge són de dues naturaleses diferents:

flowchart TB
    subgraph CP["Pla de control del cluster"]
        API["kube-apiserver"]
        subgraph CTRLPOD["Connector de CONTROLADOR (Deployment, 1-2 repliques)"]
            SC1["sidecars: external-provisioner,<br/>-attacher, -resizer, -snapshotter"]
            DRVC["Driver CSI<br/>(logica del proveidor)"]
        end
    end
    subgraph N1["Node 1"]
        K1["kubelet"]
        subgraph NODEPOD["Connector de NODE (DaemonSet, a CADA node)"]
            REG["node-driver-registrar"]
            DRVN["Driver CSI<br/>(operacions locals)"]
        end
    end
    NUVOL[("API del proveidor<br/>d'emmagatzematge")]

    API <--> SC1
    SC1 <-->|"gRPC per socket UNIX"| DRVC
    DRVC <--> NUVOL
    K1 <-->|"gRPC per socket UNIX"| DRVN
    REG -->|"registra el driver"| K1
    DRVN --> DISC["Formatar i muntar<br/>al sistema de fitxers del node"]
Connector de controlador Connector de node
Es desplega com Deployment (1 o 2 rèpliques) DaemonSet (un per node, 06-02)
Parla amb L'API del proveïdor d'emmagatzematge El sistema de fitxers del node
Operacions CreateVolume, DeleteVolume, ControllerPublishVolume, CreateSnapshot, ControllerExpandVolume NodeStageVolume, NodePublishVolume, NodeExpandVolume
En cristià "Crea un disc de 20 GiB i connecta'l a la màquina 3" "Formata aquest disc i munta'l en aquesta ruta"
Necessita credencials del núvol No

La separació no és capriciosa: crear un disc és una crida a una API remota que només s'ha de fer un cop des d'un sol lloc; muntar-lo és una operació local que només pot fer la màquina on és el pod.

  1. Els sidecars i els objectes CSIDriver i CSINode

Aquí hi ha la part elegant del disseny. El driver del proveïdor no parla amb l'API de Kubernetes: només implementa el contracte gRPC de CSI. Qui observa l'API i tradueix són uns contenidors auxiliars mantinguts pel projecte Kubernetes, els sidecars, que es despleguen al costat del driver al mateix pod (el patró multicontenidor de 06-04).

Sidecar Què observa a l'API Què crida al driver
external-provisioner PVC pendents d'una classe seva CreateVolume / DeleteVolume
external-attacher Objectes VolumeAttachment ControllerPublishVolume
external-resizer PVC el requests.storage dels quals ha crescut ControllerExpandVolume
external-snapshotter Objectes VolumeSnapshot CreateSnapshot / DeleteSnapshot
node-driver-registrar Registra el driver davant del kubelet del node

Hi ha a més un livenessprobe que sonda la salut del driver. Gràcies a aquest disseny, un fabricant només escriu la lògica del seu emmagatzematge; tota la integració amb Kubernetes ja està feta i provada. I hi ha dos objectes de l'API que descriuen l'estat d'aquesta infraestructura:

CSIDriver

Declara les capacitats d'un driver instal·lat. El clúster el consulta per saber què li pot demanar.

kubectl describe csidriver hostpath.csi.k8s.io
Name:         hostpath.csi.k8s.io
Spec:
  Attach Required:      true      # cal la fase de connexio?
  Fs Group Policy:      File      # com s'aplica fsGroup (veure 05-03)
  Pod Info On Mount:    true      # el driver rep dades del pod en muntar
  Volume Lifecycle Modes: Persistent, Ephemeral

CSINode

Es crea automàticament per cada node i registra quins drivers hi ha disponibles allà i amb quin identificador coneix el proveïdor aquella màquina.

kubectl describe csinode rutas-norte
Name:    rutas-norte
Spec:
  Drivers:
    hostpath.csi.k8s.io:
      Node ID:       rutas-norte
      Allocatables:
        Count:       10          # maxim de volums per node
      Topology Keys: [topology.hostpath.csi/node]

Aquest Count és una limitació molt real en producció: AWS, per exemple, restringeix el nombre de volums EBS connectables a una instància. Quan s'esgota, els pods es queden Pending amb node(s) exceed max volume count, un missatge que ara saps d'on surt.

  1. El flux complet d'un volum, d'extrem a extrem

Aquest és el diagrama que cal retenir: què passa exactament des que apliques un PVC fins que el procés escriu el seu primer byte.

sequenceDiagram
    participant U as Tu (kubectl apply)
    participant API as apiserver
    participant PRO as external-provisioner
    participant DRV as Driver CSI (controlador)
    participant NUVOL as API del proveidor
    participant SCH as Planificador
    participant ATT as external-attacher
    participant KBL as kubelet del node
    participant DVN as Driver CSI (node)

    U->>API: PVC (classe rutasnorte-rapida)
    Note over API: PVC Pending (WaitForFirstConsumer)
    U->>API: Deployment postgres-reserves
    SCH->>API: pod assignat al node-2
    PRO->>DRV: CreateVolume(20Gi, zona del node-2)
    DRV->>NUVOL: crear disc
    NUVOL-->>DRV: volumeHandle vol-0a1b2c3d
    PRO->>API: crea el PV i el vincula (Bound)
    ATT->>API: crea VolumeAttachment
    ATT->>DRV: ControllerPublishVolume(vol, node-2)
    DRV->>NUVOL: connectar el disc a la instancia
    KBL->>DVN: NodeStageVolume
    Note over DVN: formata (ext4) i munta al<br/>directori global del node
    KBL->>DVN: NodePublishVolume
    Note over DVN: bind-mount al directori del pod<br/>i aplica fsGroup
    KBL->>API: pod Running

Les cinc fases, amb el seu nom tècnic i el símptoma de quan fallen:

Fase Qui Si falla, veuràs
Provision external-provisioner + controlador PVC Pending, esdeveniment ProvisioningFailed
Attach external-attacher + controlador Pod ContainerCreating, esdeveniment FailedAttachVolume, Multi-Attach error
NodeStage kubelet + connector de node FailedMount, errors de formatatge o d'opcions de muntatge
NodePublish kubelet + connector de node FailedMount, problemes de permisos o fsGroup
Unpublish → Delete Els mateixos, en ordre invers PVC en Terminating, volums orfes

Dos detalls que valen or en un diagnòstic. NodeStageVolume passa un cop per node; NodePublishVolume, un cop per pod, i per això un volum pot estar "muntat" i tot i així el pod no veure'l: són dos muntatges diferents. I pots seguir la fase de connexió a l'API amb kubectl get volumeattachments, cosa que rarament se sap: mostra una fila per volum connectat, amb el driver, el PV, el node i una columna ATTACHED; si aquesta columna és false durant minuts, el problema és al connector de controlador, no al node.

  1. Preparar el clúster de pràctiques

El provisioner de minikube (k8s.io/minikube-hostpath) no admet expansió ni snapshots, així que per a aquesta lliçó instal·larem el driver CSI de hostpath, que sí que les implementa. És un driver de referència per a proves —no el facis servir en producció— però reprodueix fidelment el comportament d'un CSI real.

# 1. Els CRDs de snapshots i el snapshot-controller (NO venen amb Kubernetes)
VER=v8.1.0
BASE=https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/$VER
for f in client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml \
         client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml \
         client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml \
         deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml \
         deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml; do
  kubectl apply -f "$BASE/$f"
done

# 2. El driver CSI de hostpath
minikube addons enable csi-hostpath-driver -p rutas-norte
minikube addons enable volumesnapshots -p rutas-norte

# 3. Comprovar
kubectl get csidrivers
kubectl get sc
kubectl get volumesnapshotclasses
NAME                     ATTACHREQUIRED   MODES                  AGE
hostpath.csi.k8s.io      true             Persistent,Ephemeral   1m
NAME                     PROVISIONER           RECLAIMPOLICY   ALLOWVOLUMEEXPANSION
csi-hostpath-sc          hostpath.csi.k8s.io   Delete          true
NAME                     DRIVER                DELETIONPOLICY   AGE
csi-hostpath-snapclass   hostpath.csi.k8s.io   Delete           1m

Punt important: els CRDs de snapshot i el snapshot-controller no formen part de Kubernetes. En un clúster gestionat solen venir instal·lats, però en un de propi cal posar-los. Si intentes crear un VolumeSnapshot sense ells, l'error és no matches for kind "VolumeSnapshot" in version "snapshot.storage.k8s.io/v1".

Ara reescrivim rutasnorte-rapida sobre aquest driver —cal esborrar-la i recrear-la, perquè les StorageClass no s'editen—, mantenint el contracte (nom i Retain) i guanyant les dues capacitats:

# k8s/entorns/dev/storageclass-rapida-csi.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rutasnorte-rapida
  labels: { app.kubernetes.io/part-of: rutas-norte }
provisioner: hostpath.csi.k8s.io      # driver CSI de proves
reclaimPolicy: Retain
allowVolumeExpansion: true            # <-- ara SI
volumeBindingMode: Immediate

  1. Expansió de volums: requisits i mecànica

Arriba el dia que 10 GiB es queden curts: la taula de reserves ocupa 8,4 GiB i el disc va camí d'omplir-se en dues setmanes. L'expansió permet fer més gran el volum sense migrar les dades.

Els requisits, els tres alhora: la StorageClass ha de tenir allowVolumeExpansion: true, el driver CSI ha d'implementar la capacitat (ControllerExpandVolume) i el PVC ha d'estar Bound.

I la restricció absoluta: un volum no es pot reduir. Ni amb un PVC nou, ni amb kubectl edit, ni de cap manera; Kubernetes rebutja la petició amb spec.resources.requests.storage: Forbidden: field can not be less than previous value.

La raó és de seguretat de dades: reduir un sistema de fitxers exigeix moure'l i compactar-lo, i cap proveïdor no garanteix fer-ho sense risc. Conseqüència pràctica: demana amb cap, perquè només pots pujar. Reduir significa crear un PVC menor i copiar, amb aturada, com a 05-04.

Com es demana

S'edita spec.resources.requests.storage del PVC —mai del PV—. La via ràpida és un patch; la bona, editar el manifest a Git i aplicar-lo, com mana el model declaratiu de 01-06:

kubectl get pvc postgres-reserves-dades -n rutas-norte-dev   # CAPACITY: 10Gi

kubectl patch pvc postgres-reserves-dades -n rutas-norte-dev \
  -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

# Se segueix el proces per les CONDICIONS del PVC
kubectl describe pvc postgres-reserves-dades -n rutas-norte-dev | tail -10
Conditions:
  Type      Status  Message
  Resizing  True            Waiting for user to (re-)start a pod
Events:
  Normal  Resizing   external-resizer hostpath.csi.k8s.io  External resizer is resizing volume
  Normal  FileSystemResizeSuccessful  kubelet  MountVolume.NodeExpandVolume succeeded

I la comprovació que de debò importa, dins del contenidor:

kubectl get pvc postgres-reserves-dades -n rutas-norte-dev   # CAPACITY: 20Gi
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- df -h /var/lib/postgresql/data
# /dev/vdb   20G  8.4G  11G  45%  /var/lib/postgresql/data

20 GiB, amb els 8,4 GiB de dades intactes i sense haver aturat la base de dades.

  1. Expansió en línia davant d'expansió amb reinici

L'expansió passa en dues etapes, i d'aquí les dues condicions que veuràs al PVC:

flowchart LR
    A["Edites el PVC:<br/>10Gi -> 20Gi"] --> B["external-resizer<br/>ControllerExpandVolume"]
    B --> C["El disc REAL passa a 20Gi<br/>(condicio: Resizing)"]
    C --> D{"Expansio<br/>en linia?"}
    D -->|"Si"| E["kubelet: NodeExpandVolume<br/>resize2fs sobre el FS muntat"]
    D -->|"No"| F["FileSystemResizePending<br/>-> RECREAR el pod"] --> E
    E --> H["df -h dins del contenidor<br/>mostra 20Gi"]
Expansió en línia Expansió amb reinici
El pod continua corrent No: cal recrear-lo
Condició que apareix Resizing i desapareix FileSystemResizePending persistent
Suport La majoria dels drivers CSI moderns Drivers antics, alguns casos amb volumeMode: Block
Acció necessària Cap kubectl rollout restart

Si el PVC es queda amb la condició FileSystemResizePending, el disc ja és més gran però el sistema de fitxers no ho sap: df -h dins del contenidor continua mostrant la mida antiga. La solució és recrear el pod, i el kubelet executa NodeExpandVolume durant el muntatge:

kubectl get pvc postgres-reserves-dades -n rutas-norte-dev \
  -o jsonpath='{.status.conditions[*].type}'; echo   # -> FileSystemResizePending

# postgres-reserves fa servir Recreate: hi haura un tall breu. Avisa abans.
kubectl rollout restart deploy/postgres-reserves -n rutas-norte-dev
kubectl rollout status deploy/postgres-reserves -n rutas-norte-dev
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- df -h /var/lib/postgresql/data

Dos avisos operatius. Primer, subPath pot impedir l'expansió automàtica del sistema de fitxers en alguns drivers: és la raó, anunciada a 05-03, per la qual a postgres-reserves preferim la variable PGDATA al subPath. I segon, vigila l'ompliment abans que sigui urgent: Kubernetes exposa kubelet_volume_stats_available_bytes i kubelet_volume_stats_capacity_bytes, i una alerta al 75 % dona marge de sobres (07-03 i 07-04).

  1. Snapshots: els tres objectes

Un snapshot és una còpia puntual del contingut d'un volum, feta pel sistema d'emmagatzematge. La seva gran virtut és que és gairebé instantània —els sistemes moderns fan servir còpia sobre escriptura, així que no dupliquen les dades en crear-la— i que permet crear volums nous a partir d'ella. El model d'objectes és un calc exacte del dels volums, cosa que fa que ja te'l sàpigues:

Volums Snapshots Paper
StorageClass VolumeSnapshotClass Quin driver i amb quina política (recurs de clúster)
PersistentVolumeClaim VolumeSnapshot La petició de l'usuari (amb namespace)
PersistentVolume VolumeSnapshotContent L'objecte que representa el snapshot real (recurs de clúster)
flowchart LR
    subgraph NS["namespace rutas-norte-dev"]
        PVC["PersistentVolumeClaim<br/>postgres-reserves-dades"]
        VS["VolumeSnapshot<br/>snap-abans-migracio"]
    end
    subgraph CL["Recursos de cluster"]
        PV["PersistentVolume"]
        VSC["VolumeSnapshotContent<br/>snapcontent-a1b2..."]
        VSCLASS["VolumeSnapshotClass<br/>rutasnorte-snapclass"]
    end
    PVC --> PV
    VS -->|"source"| PVC
    VS --> VSC
    VSC -.->|"classe"| VSCLASS
    VSC -->|"viu al MATEIX<br/>sistema d'emmagatzematge"| PV

I la VolumeSnapshotClass, anàloga a la StorageClass:

# k8s/base/volumesnapshotclass.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: rutasnorte-snapclass
  labels: { app.kubernetes.io/part-of: rutas-norte }
driver: hostpath.csi.k8s.io       # ha de coincidir amb el de la StorageClass
deletionPolicy: Retain            # Retain o Delete, com als PV

deletionPolicy decideix què passa amb el snapshot real en esborrar l'objecte VolumeSnapshot: Delete el destrueix; Retain el conserva, i és l'elecció coherent amb rutasnorte-rapida per als snapshots de la base de dades.

Requisits previs, que ja vas cobrir a l'apartat 5: els tres CRDs, el snapshot-controller i el sidecar external-snapshotter al costat del driver.

  1. Crear un snapshot de postgres-reserves

L'escenari real: demà es desplega la versió 3.0 d'api-reserves, que inclou una migració d'esquema —afegeix columnes a reserves i reescriu la taula de clients—. Si la migració surt malament, cal tornar enrere en minuts, i un rollout undo del Deployment (02-04) no desfà els canvis a la base de dades. Un snapshot previ sí.

Estat de partida: tres reserves a la taula, segons kubectl exec ... psql -c "SELECT count(*) FROM reserves;". El manifest del snapshot:

# k8s/entorns/dev/snapshot-previ-migracio.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: snap-postgres-previ-v3
  namespace: rutas-norte-dev
  labels: { app: postgres-reserves, app.kubernetes.io/part-of: rutas-norte, entorn: dev }
  annotations:
    rutasnorte.example/motiu: "previ a la migracio d'esquema d'api-reserves 3.0.0"
    rutasnorte.example/responsable: "[email protected]"
    rutasnorte.example/ticket: "RN-874"
spec:
  volumeSnapshotClassName: rutasnorte-snapclass
  source:
    persistentVolumeClaimName: postgres-reserves-dades   # el PVC a fotografiar
kubectl apply -f k8s/base/volumesnapshotclass.yaml
kubectl apply -f k8s/entorns/dev/snapshot-previ-migracio.yaml
kubectl get volumesnapshot -n rutas-norte-dev
NAME                     READYTOUSE  SOURCEPVC                RESTORESIZE  SNAPSHOTCONTENT           AGE
snap-postgres-previ-v3   true        postgres-reserves-dades  20Gi         snapcontent-a1b2c3d4-...  10s

READYTOUSE: true és la columna que cal mirar: fins que no ho sigui, el snapshot no serveix per restaurar. El describe amplia amb el Bound Volume Snapshot Content Name, la Creation Time, el Restore Size i els esdeveniments CreatingSnapshot i SnapshotCreated del snapshot-controller.

Les anotacions amb el motiu, el responsable i el ticket no són adorn: un clúster acumula snapshots, cadascun costa diners, i sense aquesta informació ningú no s'atreveix a esborrar-ne cap. És l'aplicació directa del que vas aprendre a 02-07.

  1. Restaurar des d'un snapshot amb dataSource

Ara simulem el desastre. La migració s'executa i va malament: esborra reserves.

kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- \
  psql -U rutasnorte -d reserves -c "DELETE FROM reserves WHERE id > 1;
                                     SELECT count(*) FROM reserves;"   # -> 1

La restauració no sobreescriu el volum original: crea un PVC nou el dataSource del qual apunta al snapshot. És una diferència crucial, perquè conserva l'evidència de l'estat danyat per poder analitzar-lo després.

# k8s/entorns/dev/pvc-restaurat.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-reserves-dades-restaurat
  namespace: rutas-norte-dev
  labels: { app: postgres-reserves, app.kubernetes.io/part-of: rutas-norte, entorn: dev }
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: rutasnorte-rapida       # la MATEIXA classe de l'origen
  resources: { requests: { storage: 20Gi } }   # >= restoreSize del snapshot
  dataSource:
    name: snap-postgres-previ-v3
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

En aplicar-lo apareix un segon PVC Bound al costat de l'original, amb el seu propi PV. I apuntem el Deployment al volum restaurat:

kubectl apply -f k8s/entorns/dev/pvc-restaurat.yaml
kubectl scale deploy postgres-reserves -n rutas-norte-dev --replicas=0
kubectl patch deploy postgres-reserves -n rutas-norte-dev --type=json -p='[
  {"op":"replace",
   "path":"/spec/template/spec/volumes/0/persistentVolumeClaim/claimName",
   "value":"postgres-reserves-dades-restaurat"}]'
kubectl scale deploy postgres-reserves -n rutas-norte-dev --replicas=1
kubectl rollout status deploy/postgres-reserves -n rutas-norte-dev

kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- \
  psql -U rutasnorte -d reserves -c "SELECT * FROM reserves;"
 id |     client      |     trajecte      |    data
----+-----------------+-------------------+------------
  1 | Marta Iglesias  | Bilbao-Santander  | 2026-08-15
  2 | Ignacio Sabater | Oviedo-Gijon      | 2026-08-16
  3 | Lucia Berenguer | Leon-Ponferrada   | 2026-08-17

Les tres reserves han tornat. Quatre condicions perquè una restauració funcioni:

  1. El PVC nou ha de demanar com a mínim el restoreSize del snapshot. Menys, i falla.
  2. Ha de fer servir la mateixa StorageClass (o almenys el mateix driver): un snapshot no creua sistemes d'emmagatzematge.
  3. El snapshot ha d'estar READYTOUSE: true.
  4. El snapshot ha d'existir al mateix namespace que el PVC nou. Per creuar namespaces cal importar el VolumeSnapshotContent a mà.

  1. Clonar un PVC

Un cas germà i molt útil: crear una còpia d'un volum directament des d'un altre PVC, sense passar per un snapshot. Canvia només el kind del dataSource:

# k8s/entorns/dev/pvc-clon-per-proves.yaml (mateix esquema que el restaurat)
metadata:
  name: postgres-reserves-dades-clon-qa
  annotations:
    rutasnorte.example/motiu: "clon per provar la migracio 3.0 sense tocar dev"
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: rutasnorte-rapida
  resources: { requests: { storage: 20Gi } }
  dataSource:
    name: postgres-reserves-dades       # UN ALTRE PVC, no un snapshot
    kind: PersistentVolumeClaim         # <-- l'unic que canvia
Restaurar de snapshot Clonar un PVC
dataSource.kind VolumeSnapshot PersistentVolumeClaim
Origen Una foto d'un moment passat L'estat actual del volum
Requereix snapshot previ No
Namespace El mateix El mateix
Ús típic Recuperar d'un desastre Muntar un entorn de proves amb dades reals

La clonació és la forma neta de donar a l'equip de desenvolupament una còpia de les dades de preproducció per assajar una migració. I compte amb la lletra petita: aquest clon conté dades personals reals de clients —nom, DNI, telèfon, correu—. Un clon per a proves és un tractament de dades igual que l'original i exigeix les mateixes proteccions, o millor, una anonimització prèvia. Es reprèn a 05-06.

Una limitació tècnica que convé saber: clonar un PVC mentre la base de dades està escrivint produeix una còpia equivalent a la d'un tall de llum, amb els mateixos problemes de consistència de l'apartat següent.

  1. L'advertiment: consistència i què NO és un snapshot

Un snapshot de disc pot no ser consistent

Aquest és l'advertiment central de la lliçó. Un snapshot fotografia els blocs del disc, no l'estat lògic de l'aplicació. I una base de dades en marxa té, en qualsevol instant, pàgines modificades en memòria que encara no s'han escrit al disc, transaccions a mig confirmar, i escriptures al WAL que el sistema operatiu té a la seva memòria cau i no ha bolcat.

El snapshot resultant equival a l'estat del disc després d'un tall de corrent. La bona notícia és que PostgreSQL està preparat per a això: en arrencar sobre aquell volum executa una recuperació per WAL i arriba a un estat coherent. La dolenta és doble: aquesta recuperació pot trigar minuts en una base gran, i perdràs les transaccions que no van arribar al disc. I amb motors menys robustos, o amb dades repartides entre diversos volums que es fotografien per separat, el resultat pot ser directament inservible. La solució professional és coordinar el snapshot amb l'aplicació: deixar-la en un estat consistent just abans, i alliberar-la just després.

Nivell de consistència Com s'aconsegueix Risc
Crash-consistent Snapshot sense més Recuperació per WAL en arrencar; pèrdua del que no s'ha bolcat
Filesystem-consistent sync i congelar el sistema de fitxers (fsfreeze) abans Baix
Application-consistent L'aplicació entra en mode còpia (pg_backup_start / pg_backup_stop) Mínim

Kubernetes ofereix el mecanisme per al tercer: els hooks de pre i post que executen una ordre dins del contenidor abans i després del snapshot. No és una capacitat nativa del VolumeSnapshot, sinó de les eines de còpia de seguretat, i per això s'estudia a la lliçó següent, 05-06, amb Velero.

Un snapshot NO és una còpia de seguretat

És l'afirmació més important del mòdul i l'error conceptual que més dades ha destruït:

Snapshot Còpia de seguretat
On viu Al mateix sistema d'emmagatzematge que l'original En un sistema independent (magatzem d'objectes, una altra regió)
Sobreviu a perdre la cabina, la zona o el compte No Sí (si és en un altre compte)
Sobreviu a un xifratge per ransomware amb credencials robades Normalment no Sí, amb immutabilitat
Portable a un altre clúster o proveïdor No
Temps de creació / cost Segons, cost baix (incremental) Minuts o hores, cost més gran
Inclou els manifests de Kubernetes No: només les dades del volum Sí, si l'eina els captura

Si s'esborra el compte del núvol, si la zona es perd, si algú amb credencials d'administrador destrueix el projecte, els snapshots se'n van amb tota la resta. Són una eina magnífica per al que són —desfer un canvi local, ràpid, en minuts—, però no són una estratègia de recuperació davant de desastres. Regla de Rutas Norte: els snapshots són la xarxa de seguretat de curt termini —abans d'una migració, d'un desplegament arriscat, de tocar producció—, amb retenció de dies; la còpia de seguretat de veritat va a un magatzem d'objectes extern i és el que es construeix a la lliçó següent.

Errors Comuns i Consells

Error Símptoma Solució
Crear un VolumeSnapshot sense CRDs ni controlador no matches for kind "VolumeSnapshot" Instal·la els CRDs i el snapshot-controller
Intentar reduir un PVC field can not be less than previous value No es pot: copiar a un PVC menor amb aturada
Expandir amb allowVolumeExpansion: false El PVC no canvia i apareix un esdeveniment de rebuig La classe s'ha de recrear amb el camp a true
Editar el PV en lloc del PVC per expandir No passa res útil L'expansió es demana sempre al PVC
No mirar df -h dins del contenidor Es creu que l'expansió va acabar La condició FileSystemResizePending exigeix recrear el pod
Restaurar en un PVC més petit que el snapshot, o amb una altra classe El PVC no s'aprovisiona Demana >= restoreSize, i mateixa classe i driver
Creure que un snapshot és una còpia de seguretat Pèrdua total si cau el sistema d'emmagatzematge Còpia externa (05-06)
Snapshot d'una BD en marxa sense coordinar Recuperació llarga o dades inservibles Hooks pre/post, o bolcat lògic
Snapshots sense anotacions ni retenció Desenes de snapshots cars que ningú no s'atreveix a esborrar Anota motiu, responsable i ticket; defineix retenció
Clonar producció a un entorn de proves sense pensar Dades personals de clients en un entorn menys protegit Anonimitza o aplica les mateixes proteccions

Consells:

  1. Abans de qualsevol canvi irreversible —migració d'esquema, actualització major del motor, script de neteja— fes un snapshot i anota'l amb el ticket. Costa segons i ha salvat carreres.
  2. Verifica que un snapshot serveix, no que existeix. Un snapshot no provat no és res. Restaura periòdicament en un PVC de prova i comprova les files.
  3. Vigila l'ompliment amb antelació amb un df -h sobre el punt de muntatge, i automatitza-ho amb alertes al 75 % a 07-04.
  4. Diagnostica l'emmagatzematge per fases. Si un pod no arrenca pel volum, mira en aquest ordre: kubectl describe pvc (provisió), kubectl get volumeattachments (connexió), kubectl describe pod (muntatge) i els registres del pod del driver CSI a kube-system.

Exercicis

Exercici 1: expandir el volum de la base de dades

Amb el driver CSI de hostpath instal·lat i rutasnorte-rapida recreada amb allowVolumeExpansion: true:

  1. Comprova la mida actual del volum de postgres-reserves des de dins del contenidor.
  2. Amplia'l de 10 GiB a 20 GiB editant el PVC.
  3. Segueix les condicions del PVC fins que acabi i verifica la mida amb df -h.
  4. Intenta reduir-lo a 5 GiB i transcriu l'error.
  5. Comprova que les reserves continuen allà.

Exercici 2: snapshot, desastre i restauració

Assaja el procediment complet que faràs servir en producció:

  1. Insereix cinc reserves fictícies a postgres-reserves.
  2. Crea un VolumeSnapshot anomenat snap-practica amb les anotacions de motiu, responsable i ticket. Espera a READYTOUSE: true.
  3. Provoca el desastre: DROP TABLE reserves.
  4. Crea un PVC restaurat amb dataSource i apunta-hi el Deployment.
  5. Verifica que les cinc reserves han tornat.
  6. Mesura el temps total des del pas 3 fins al 5 i anota'l: és el teu RTO real per a aquest tipus d'incident.

Exercici 3: dissenyar la política de snapshots de Rutas Norte

Respon de forma raonada, en forma de taula de decisió que puguis presentar a l'equip:

  • A. Quins volums de Rutas Norte mereixen snapshots automàtics i quins no? Justifica cada component.
  • B. Amb quina freqüència i amb quina retenció? Tingues en compte que en un pont es venen unes 400 reserves per hora i que un snapshot de 20 GiB costa diners cada mes.
  • C. Per què els snapshots no basten com a estratègia de protecció de dades de Rutas Norte? Enumera tres escenaris concrets en què no salvarien la situació.

Solucions

Exercici 1

# 1: parteix de 9.8G, 2% usat
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- df -h /var/lib/postgresql/data

# 2
kubectl patch pvc postgres-reserves-dades -n rutas-norte-dev \
  -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

# 3
kubectl get pvc postgres-reserves-dades -n rutas-norte-dev -w    # Ctrl-C en veure 20Gi
kubectl get pvc postgres-reserves-dades -n rutas-norte-dev \
  -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- df -h /var/lib/postgresql/data

El df -h final ha de dir 20G amb només un 1 % usat. Si status.conditions mostra FileSystemResizePending=True, falta la segona etapa: un kubectl rollout restart deploy/postgres-reserves i ja ho dirà.

# 4
kubectl patch pvc postgres-reserves-dades -n rutas-norte-dev \
  -p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
# 5
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- \
  psql -U rutasnorte -d reserves -c "SELECT count(*) FROM reserves;"
The PersistentVolumeClaim "postgres-reserves-dades" is invalid:
spec.resources.requests.storage: Forbidden: field can not be less than previous value

L'expansió no toca les dades: només fa més gran el dispositiu i estén el sistema de fitxers sobre l'espai nou.

Exercici 2

# 1
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- psql -U rutasnorte -d reserves -c \
  "TRUNCATE reserves;
   INSERT INTO reserves (client, trajecte, data) VALUES
     ('Marta Iglesias','Bilbao-Santander','2026-08-15'),
     ('Ignacio Sabater','Oviedo-Gijon','2026-08-16'),
     ('Lucia Berenguer','Leon-Ponferrada','2026-08-17'),
     ('Ander Zuloaga','Vitoria-Burgos','2026-08-18'),
     ('Rosa Ferreiro','Lugo-A Coruna','2026-08-19');"
# 2: snap-practica.yaml (el VolumeSnapshot de l'apartat 9, amb un altre nom)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: snap-practica
  namespace: rutas-norte-dev
  labels: { app: postgres-reserves, app.kubernetes.io/part-of: rutas-norte, entorn: dev }
  annotations:
    rutasnorte.example/motiu: "assaig de restauracio del modul 5"
    rutasnorte.example/responsable: "[email protected]"
    rutasnorte.example/ticket: "RN-901"
spec:
  volumeSnapshotClassName: rutasnorte-snapclass
  source: { persistentVolumeClaimName: postgres-reserves-dades }
kubectl apply -f snap-practica.yaml
kubectl wait --for=jsonpath='{.status.readyToUse}'=true \
  volumesnapshot/snap-practica -n rutas-norte-dev --timeout=180s

# 3: el desastre. A partir d'aqui, cronometre.
INICI=$(date +%s)
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- \
  psql -U rutasnorte -d reserves -c "DROP TABLE reserves;"

# 4: el PVC restaurat de l'apartat 10, amb dataSource -> snap-practica
kubectl apply -f k8s/entorns/dev/pvc-restaurat.yaml
kubectl scale deploy postgres-reserves -n rutas-norte-dev --replicas=0
kubectl patch deploy postgres-reserves -n rutas-norte-dev --type=json -p='[
  {"op":"replace","path":"/spec/template/spec/volumes/0/persistentVolumeClaim/claimName",
   "value":"postgres-reserves-dades-restaurat"}]'
kubectl scale deploy postgres-reserves -n rutas-norte-dev --replicas=1
kubectl rollout status deploy/postgres-reserves -n rutas-norte-dev

# 5 i 6
kubectl exec -n rutas-norte-dev deploy/postgres-reserves -- \
  psql -U rutasnorte -d reserves -c "SELECT count(*) FROM reserves;"
echo "RTO real: $(( $(date +%s) - INICI )) segons"
 count
-------
     5
RTO real: 94 segons

Aquest número —de l'ordre d'un a tres minuts en un clúster local, i de cinc a quinze en producció amb volums grans— és el teu RTO per a aquest tipus d'incident. Anota'l: és la dada que cal portar a la conversa sobre objectius de recuperació de 05-06. I fixa't que el volum original continua allà, amb la taula esborrada, disponible per investigar què va passar.

Exercici 3

A. Què mereix snapshots:

Component Snapshot? Justificació
postgres-reserves Sí, prioritari Únic origen de veritat de reserves i dades personals. La seva pèrdua atura la venda
PVC de còpies de seguretat Conté els bolcats lògics; perdre'ls deixa sense xarxa de seguretat
redis-cache No Reconstruïble des de la base de dades. Un snapshot només guardaria dades caducades
botiga-web, api-reserves, worker-notificacions No Sense estat: el seu contingut és a la imatge i a Git, i la cua de pendents viu a PostgreSQL
Volum de treball d'informes-ocupacio No Efímer per disseny; l'informe es regenera

B. Freqüència i retenció:

Tipus Freqüència Retenció Motiu
Programat nocturn 1 al dia (03:00) 7 dies Cobreix l'error detectat al cap de pocs dies. 7 snapshots de 20 GiB incrementals són barats
Abans de cada canvi amb risc Sota demanda 72 h després de validar el canvi És la seva finestra útil: si a les 72 h no ha explotat, no explotarà per aquell canvi
Temporada alta (ponts, estiu) Cada 4 h 48 h A 400 reserves/hora, un snapshot diari implicaria perdre fins a 9.600 reserves

L'aritmètica que justifica el punt tres: amb snapshot diari, l'RPO és de 24 h i en un pont això són unes 9.600 reserves perdudes. Amb snapshot cada 4 h, l'RPO baixa a 4 h, és a dir, unes 1.600. I amb el WAL arxivat de forma contínua baixaria a minuts, que és la solució de veritat i es comenta a 05-06. L'RPO és una decisió de negoci, no tècnica: algú ha de decidir quantes reserves és acceptable perdre.

C. Tres escenaris en què els snapshots no salven la situació:

  1. Pèrdua del sistema d'emmagatzematge o de la zona. Els snapshots viuen a la mateixa cabina o regió que els volums originals. Si desapareix, desapareixen amb ella. Una còpia de seguretat en un magatzem d'objectes d'una altra regió sí que sobreviu.
  2. Esborrat del compte o el projecte del núvol, accidental o maliciós. Un administrador compromès, o un error de facturació que suspèn el compte, s'emporta volums i snapshots per igual. La protecció és una còpia en un altre compte, amb credencials diferents i immutabilitat activada.
  3. Corrupció lògica no detectada a temps. Un error de l'aplicació que corromp registres a poc a poc durant tres setmanes fa inútils els snapshots de 7 dies: tots contenen ja la corrupció. Calen còpies amb retenció llarga —mensuals, anuals— i, sobretot, bolcats lògics verificables: un pg_dump que es restaura correctament demostra que les dades són coherents, cosa que un snapshot de blocs no demostra. Un quart escenari que convé esmentar: migrar a un altre proveïdor o restaurar en un altre clúster; un snapshot d'EBS no es restaura a GKE, un bolcat lògic sí.

Conclusió

Has obert la caixa negra. Saps que CSI existeix perquè tenir el codi de cada fabricant dins de Kubernetes era insostenible, i que la seva arquitectura són dues meitats: un connector de controlador, desplegat com a Deployment, que parla amb l'API del proveïdor i crea, connecta i fotografia volums; i un connector de node, desplegat com a DaemonSet, que formata i munta a cada màquina. Entre el driver i l'API de Kubernetes s'interposen els sidecarsexternal-provisioner, external-attacher, external-resizer, external-snapshotter, node-driver-registrar—, que tradueixen objectes de l'API en crides gRPC i permeten que un fabricant només escrigui la lògica del seu emmagatzematge. I coneixes els objectes que descriuen aquesta infraestructura: CSIDriver, amb les capacitats declarades, i CSINode, amb els drivers de cada node i aquell Count que explica l'exceed max volume count. Sobretot, tens el flux complet al cap —Provision, Attach, NodeStage, NodePublish— i saps quin símptoma produeix cada fase en fallar, inclòs el kubectl get volumeattachments que gairebé ningú no fa servir i que distingeix en un segon un problema de controlador d'un de node.

Has expandit en calent el volum de postgres-reserves de 10 a 20 GiB, amb la base de dades en marxa i sense tocar una sola dada. Coneixes els tres requisits —allowVolumeExpansion, suport del driver i PVC Bound—, que la petició es fa sempre al PVC i mai al PV, i les dues etapes del procés: la del disc real i la del sistema de fitxers, amb la condició FileSystemResizePending que delata quan cal recrear el pod perquè df -h digui la veritat. I tens gravada la restricció: no es pot reduir, així que la mida es pensa abans.

I domines els snapshots, amb el seu model d'objectes calcat del dels volums —VolumeSnapshotClass, VolumeSnapshot, VolumeSnapshotContent, exactament el paper de StorageClass, PVC i PV—, els seus requisits previs que no vénen amb Kubernetes (els CRDs i el snapshot-controller), i el flux de treball real: snapshot anotat amb motiu, responsable i ticket abans d'una migració d'esquema; desastre; i restauració creant un PVC nou amb dataSource, que no sobreescriu l'original i conserva l'evidència. Saps clonar un PVC en calent canviant el kind del dataSource, i que aquell clon de producció porta dades personals reals i mereix la mateixa cura que l'original.

I t'emportes els dos advertiments que donen sentit a la lliçó següent. El primer: un snapshot d'un disc amb una base de dades en marxa és crash-consistent, equivalent a l'estat després d'un tall de llum; PostgreSQL es recupera per WAL, però triga i pot perdre el que no s'ha bolcat, i el remei és coordinar l'aplicació amb hooks abans i després. El segon, el més important del mòdul: un snapshot no és una còpia de seguretat. Viu al mateix sistema d'emmagatzematge que l'original, així que no sobreviu a la pèrdua de la zona, ni a l'esborrat del compte, ni a una corrupció lògica descoberta tres setmanes tard, ni serveix per restaurar en un altre clúster. És una xarxa de seguretat de curt termini, excel·lent per al que és.

Falta, per tant, el que de veritat protegeix Rutas Norte: treure les dades del clúster, posar-les en un magatzem independent, incloure també els objectes de l'API, coordinar la base de dades perquè la còpia sigui consistent, definir retenció i —això és innegociable— provar la restauració. Això és Còpies de Seguretat i Restauració de Dades, la lliçó que tanca el mòdul.

Curs de Kubernetes

Mòdul 1: Introducció a Kubernetes

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats