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
- Quin problema va resoldre CSI
- L'arquitectura: connector de controlador i connector de node
- Els sidecars i els objectes
CSIDriveriCSINode - El flux complet d'un volum, d'extrem a extrem
- Preparar el clúster de pràctiques
- Expansió de volums: requisits i mecànica
- Expansió en línia davant d'expansió amb reinici
- Snapshots: els tres objectes
- Crear un snapshot de
postgres-reserves - Restaurar des d'un snapshot amb
dataSource - Clonar un PVC
- L'advertiment: consistència i què NO és un snapshot
- 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.
- 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 | Sí | 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.
- Els sidecars i els objectes
CSIDriver i CSINode
CSIDriver i CSINodeAquí 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.
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, EphemeralCSINode
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.
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.
- 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.
- 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 volumesnapshotclassesNAME 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 1mPunt 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
- 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 -10Conditions:
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 succeededI 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/data20 GiB, amb els 8,4 GiB de dades intactes i sense haver aturat la base de dades.
- 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 | Sí | 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/dataDos 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).
- 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 PVdeletionPolicy 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.
- Crear un snapshot de
postgres-reserves
postgres-reservesL'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 fotografiarkubectl apply -f k8s/base/volumesnapshotclass.yaml
kubectl apply -f k8s/entorns/dev/snapshot-previ-migracio.yaml
kubectl get volumesnapshot -n rutas-norte-devNAME READYTOUSE SOURCEPVC RESTORESIZE SNAPSHOTCONTENT AGE
snap-postgres-previ-v3 true postgres-reserves-dades 20Gi snapcontent-a1b2c3d4-... 10sREADYTOUSE: 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.
- Restaurar des d'un snapshot amb
dataSource
dataSourceAra 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;" # -> 1La 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.ioEn 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-17Les tres reserves han tornat. Quatre condicions perquè una restauració funcioni:
- El PVC nou ha de demanar com a mínim el
restoreSizedel snapshot. Menys, i falla. - Ha de fer servir la mateixa StorageClass (o almenys el mateix driver): un snapshot no creua sistemes d'emmagatzematge.
- El snapshot ha d'estar
READYTOUSE: true. - El snapshot ha d'existir al mateix namespace que el PVC nou. Per creuar namespaces cal importar el
VolumeSnapshotContenta mà.
- 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 | Sí | 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.
- 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 | Sí |
| 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:
- 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.
- 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.
- Vigila l'ompliment amb antelació amb un
df -hsobre el punt de muntatge, i automatitza-ho amb alertes al 75 % a 07-04. - 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 akube-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:
- Comprova la mida actual del volum de
postgres-reservesdes de dins del contenidor. - Amplia'l de 10 GiB a 20 GiB editant el PVC.
- Segueix les condicions del PVC fins que acabi i verifica la mida amb
df -h. - Intenta reduir-lo a 5 GiB i transcriu l'error.
- Comprova que les reserves continuen allà.
Exercici 2: snapshot, desastre i restauració
Assaja el procediment complet que faràs servir en producció:
- Insereix cinc reserves fictícies a
postgres-reserves. - Crea un
VolumeSnapshotanomenatsnap-practicaamb les anotacions de motiu, responsable i ticket. Espera aREADYTOUSE: true. - Provoca el desastre:
DROP TABLE reserves. - Crea un PVC restaurat amb
dataSourcei apunta-hi el Deployment. - Verifica que les cinc reserves han tornat.
- 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/dataEl 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 valueL'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"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 | Sí | 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ó:
- 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.
- 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.
- 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_dumpque 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 sidecars —external-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
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
