La lliçó anterior va acabar amb un diagnòstic precís: tots els volums que coneixíem —emptyDir, configMap, secret, projected— moren amb el pod, i hostPath sobreviu però lliga les dades a un node concret i obre un forat de seguretat inacceptable. Perquè postgres-reserves conservi les reserves cal una cosa diferent: un objecte d'emmagatzematge amb vida pròpia al clúster, independent de qualsevol pod. Aquest objecte és el PersistentVolume (PV). En aquesta lliçó veuràs per què Kubernetes el va separar del consum, desmuntaràs la seva especificació camp a camp —inclòs el parany dels modes d'accés, que s'apliquen per node i no per pod—, en recorreràs les fases amb un diagrama d'estats, i crearàs un PersistentVolume real de 10 GiB per a la base de dades de Rutas Norte al teu clúster de pràctiques.
Contingut
- El problema real: qui administra i qui consumeix
- Què és un PersistentVolume
- Anatomia de l'objecte:
capacityivolumeMode - Els modes d'accés i el parany del node
- La política de reclamació:
Retain,Deletei l'obsoletRecycle storageClassName,mountOptionsinodeAffinity- Els backends possibles i quins modes admet cada família
- Cicle de vida: les fases del PV
- Què fer amb un PV en
Released - Pràctica: un PV de 10 GiB per a
postgres-reserves - Per què l'aprovisionament estàtic no escala
- El problema real: qui administra i qui consumeix
Abans de mirar un sol camp YAML, cal entendre quin problema organitzatiu resol aquesta abstracció, perquè si no, la parella PV/PVC sembla burocràcia innecessària.
A Rutas Norte hi ha dos papers diferents i no se solapen:
| Equip de plataforma | Equip d'aplicació | |
|---|---|---|
| Sap | Quines cabines de disc hi ha, quines classes de rendiment, en quines zones, quant costa cada GiB, com es fan els snapshots | Que postgres-reserves necessita 10 GiB ràpids i que no els pot perdre |
| No sap | Què necessita cada aplicació concreta | Si a sota hi ha un EBS gp3, una LUN d'iSCSI o un directori del node |
| Escriu | PersistentVolume i StorageClass | PersistentVolumeClaim |
| Recurs | Sense namespace: és del clúster | Amb namespace: viu al costat de l'aplicació |
Si el manifest de postgres-reserves hagués d'anomenar directament un disc d'AWS amb el seu identificador, passarien tres coses dolentes: el manifest deixaria de ser portable entre minikube, preproducció i producció; el desenvolupador necessitaria credencials i coneixement de la infraestructura; i qualsevol canvi de proveïdor obligaria a reescriure tots els manifests de la plataforma.
La solució de Kubernetes és un contracte amb dues cares:
flowchart TB
subgraph PLATAFORMA["Equip de plataforma (recursos de cluster)"]
SC["StorageClass<br/>rutasnorte-rapida<br/>(05-04)"]
PV["PersistentVolume<br/>capacity: 10Gi<br/>accessModes: RWO<br/>reclaimPolicy: Retain"]
end
subgraph APP["Equip d'aplicacio (namespace rutas-norte-pro)"]
PVC["PersistentVolumeClaim<br/>'vull 10Gi RWO<br/>de classe rutasnorte-rapida'"]
POD["Pod postgres-reserves<br/>volumes:<br/>persistentVolumeClaim"]
end
CTRL["Controlador PersistentVolume<br/>(kube-controller-manager)"]
PVC -->|"1. ho sol·licita"| CTRL
PV -->|"2. candidats disponibles"| CTRL
CTRL -->|"3. VINCULA (Bound)"| PVC
SC -.->|"aprovisiona sota demanda"| PV
POD -->|"4. munta"| PVC
La frase que resumeix la lliçó sencera: el PVC és la demanda, el PV és l'oferta, i el controlador els aparella. Qui desplega l'aplicació escriu únicament la demanda.
- Què és un PersistentVolume
Un PersistentVolume és un objecte de l'API que representa una porció concreta d'emmagatzematge ja existent al clúster, aprovisionada per un administrador o creada dinàmicament per una StorageClass.
Tres propietats defineixen la seva naturalesa i convé fixar-les d'entrada:
- No té namespace. És un recurs d'àmbit de clúster, com els nodes o les StorageClasses. Ho comproves amb el que vas aprendre a 02-06:
kubectl api-resources | grep -E "^NAME|persistentvolume"
El PVC sí que té namespace: pertany a l'aplicació. El PV no.NAME SHORTNAMES APIVERSION NAMESPACED KIND persistentvolumeclaims pvc v1 true PersistentVolumeClaim persistentvolumes pv v1 false PersistentVolume - El seu cicle de vida és independent del pod. Pots esborrar el Deployment sencer, el pod, fins i tot el PVC, i el PV pot continuar existint amb les dades a dins. Aquesta independència és justament el que faltava a 05-01.
- És un objecte declaratiu com qualsevol altre. Té
apiVersion: v1,kind: PersistentVolume,metadata,specistatus, exactament el model de 01-06. Explora'l:kubectl explain pv.spec kubectl explain pv.spec.accessModes
- Anatomia de l'objecte:
capacity i volumeMode
capacity i volumeModeUn PV complet, amb tots els camps que importen, comentat:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-postgres-reserves-10gi # sense namespace: es del cluster
labels:
app: postgres-reserves
app.kubernetes.io/part-of: rutas-norte
entorn: dev
tipus-disc: ssd # util per als selectors del PVC (05-03)
spec:
capacity:
storage: 10Gi # mida declarada del volum
volumeMode: Filesystem # Filesystem (per defecte) o Block
accessModes:
- ReadWriteOnce # un sol NODE en lectura-escriptura
persistentVolumeReclaimPolicy: Retain # que fer quan s'alliberi
storageClassName: rutasnorte-rapida # la classe a la qual pertany
mountOptions: # opcions passades al muntatge
- noatime
hostPath: # el backend: NOMES per a proves
path: /data/postgres-reserves
type: DirectoryOrCreatecapacity.storage
La mida del volum, en unitats de Kubernetes (Gi = 2^30 bytes, G = 10^9 bytes; fes servir sempre Gi). És una dada declarada, no mesurada: Kubernetes no comprova que el disc tingui realment aquesta mida. Amb un backend hostPath de proves pots escriure capacity: 10Gi sobre un directori d'un disc de 4 GiB i ningú no protestarà fins que s'ompli. La seva funció real és servir de criteri en l'aparellament amb el PVC.
Avui storage és l'únic recurs admès a capacity; atributs com les IOPS s'expressen mitjançant els parameters de la StorageClass (05-04).
volumeMode: Filesystem davant de Block
Filesystem (per defecte) |
Block |
|
|---|---|---|
| Què rep el pod | Un directori muntat a mountPath |
Un dispositiu de blocs cru a devicePath |
| Formatatge | El fa Kubernetes/CSI (ext4, xfs) | No hi ha sistema de fitxers |
| Camp al contenidor | volumeMounts |
volumeDevices |
| Ús típic | El 99 % dels casos, inclòs PostgreSQL | Bases de dades que gestionen la seva pròpia E/S, sistemes d'emmagatzematge distribuït |
En mode bloc el contenidor no fa servir volumeMounts sinó volumeDevices, amb devicePath: /dev/xvda en lloc de mountPath: el dispositiu apareix cru, sense sistema de fitxers.
A Rutas Norte fem servir Filesystem a tot arreu. Block només té sentit si l'aplicació sap escriure directament sobre el dispositiu, i PostgreSQL no ho fa.
- Els modes d'accés i el parany del node
Els accessModes declaren de quines maneres es pot muntar el volum. Són quatre:
| Mode | Abreviatura | Significat exacte |
|---|---|---|
ReadWriteOnce |
RWO | Lectura i escriptura des d'un sol node |
ReadOnlyMany |
ROX | Només lectura des de molts nodes |
ReadWriteMany |
RWX | Lectura i escriptura des de molts nodes |
ReadWriteOncePod |
RWOP | Lectura i escriptura des d'un sol pod a tot el clúster |
El parany és a ReadWriteOnce, i gairebé tothom hi cau almenys una vegada. El "Once" es refereix a un node, no a un pod. Això vol dir que dos, tres o vint pods poden muntar simultàniament el mateix volum RWO en lectura i escriptura si el planificador els col·loca al mateix node. Cap capa de Kubernetes no ho impedirà.
La conseqüència per a una base de dades és directa i greu: si un rollout amb estratègia RollingUpdate aixeca un pod nou de postgres-reserves al mateix node abans d'acabar el vell, tindràs dos processos de PostgreSQL sobre el mateix directori de dades. Per això postgres-reserves porta strategy: Recreate des del mòdul 2, i per això existeix accessModes: ["ReadWriteOncePod"].
ReadWriteOncePod és l'única garantia que un sol pod pot fer servir el volum; el segon es queda en Pending amb un esdeveniment explícit. És un mode estable des de Kubernetes 1.29 i requereix que el driver CSI el suporti. Quan el driver ho permet, és l'elecció correcta per a una base de dades amb una única rèplica.
Dos advertiments més sobre els modes:
- La llista és una declaració de capacitats, no una restricció activa. Un PV pot declarar
["ReadWriteOnce", "ReadOnlyMany"]per dir que admet tots dos usos; el PVC en triarà un. - El que un backend admet de veritat no depèn del que escriguis. Pots posar
ReadWriteManyen un PV suportat per un disc de blocs d'AWS, i Kubernetes ho acceptarà sense piular; la fallada apareixerà en intentar muntar-lo des del segon node. La taula de l'apartat 7 recull què admet realment cada família.
- La política de reclamació:
Retain, Delete i l'obsolet Recycle
Retain, Delete i l'obsolet RecyclepersistentVolumeReclaimPolicy decideix què passa amb el volum i amb les dades quan el PVC que l'utilitzava s'esborra. És el camp amb més conseqüències de l'objecte.
| Política | En esborrar el PVC | Estat del PV | Es perden les dades? |
|---|---|---|---|
Retain |
No es fa res amb el volum | Passa a Released |
No. Queden intactes, esperant intervenció manual |
Delete |
S'esborra el PV i el volum real subjacent | Desapareix l'objecte PV | Sí, de manera irreversible |
Recycle |
Un rm -rf /volum/* i torna a Available |
Available |
Sí |
Recycle és obsolet i no s'ha de fer servir: només funcionava amb hostPath i NFS, esborrava amb un pod auxiliar sense cap garantia, i no encaixa amb el model CSI. El seu substitut és l'aprovisionament dinàmic (05-04): en lloc de reciclar un volum usat, se'n crea un de nou i net.
La decisió pràctica a Rutas Norte:
postgres-reserves→Retain. L'escenari que cal evitar és que algú esborri per error el namespacerutas-norte-pro—recorda de 02-06 quekubectl delete nss'emporta els PVC— i que això destrueixi les dades de tots els clients. AmbRetain, el PVC desapareix però el volum continua allà i es pot recuperar.- Volums reconstruïbles (memòria cau, espais de treball) →
Delete. Recrear és més barat que netejar a mà, iDeleteevita facturar discos orfes.
Un detall important que confon molt: en l'aprovisionament dinàmic el PV hereta la política de la StorageClass, i la majoria de les classes per defecte dels núvols fan servir Delete. És a dir, si no fas res, esborrar un PVC de producció esborra el disc. Es comprova amb kubectl get storageclass -o custom-columns=NOM:.metadata.name,POLITICA:.reclaimPolicy.
I en un PV ja existent es pot canviar en calent, que és la maniobra defensiva estàndard abans de tocar res:
kubectl patch pv pv-postgres-reserves-10gi \
-p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
storageClassName, mountOptions i nodeAffinity
storageClassName, mountOptions i nodeAffinitystorageClassName
És una etiqueta d'aparellament: un PVC només es vincula a un PV la classe del qual coincideixi exactament. Tres situacions que cal distingir amb cura (i que tornarem a veure a 05-04):
| Al PV | Al PVC | Resultat |
|---|---|---|
storageClassName: rapida |
storageClassName: rapida |
Es poden aparellar |
Camp absent o "" |
storageClassName: "" |
Es poden aparellar (PV estàtic "sense classe") |
| Camp absent | Camp absent | El PVC farà servir la classe per defecte i no mirarà aquest PV |
Un PV estàtic pot portar un storageClassName que no correspon a cap StorageClass existent (per exemple manual). És una pràctica habitual i perfectament vàlida: la cadena només es fa servir per casar oferta i demanda.
mountOptions
Opcions que es passen tal qual al muntatge del sistema de fitxers —noatime i nodiratime per reduir escriptures, o hard i nfsvers=4.1 en NFS—. Kubernetes no les valida: si el backend no les admet, el muntatge falla i el pod es queda en ContainerCreating amb un esdeveniment de FailedMount.
nodeAffinity
Restringeix des de quins nodes es pot muntar el volum. És obligatori en els volums de tipus local, perquè el disc està físicament en una màquina i el planificador ho ha de saber per no col·locar el pod on el volum no existeix.
spec:
local:
path: /mnt/discs/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["rutas-norte"] # el node de minikubeAquest és un cas d'emmagatzematge condicionant la planificació, un tema que s'aprofundeix a 06-05. Al núvol el mecanisme és idèntic però la clau sol ser topology.kubernetes.io/zone: un disc creat a eu-west-1a només es pot muntar des de nodes d'aquesta zona, i d'aquí neix el problema del volumeBindingMode que resoldrà 05-04.
- Els backends possibles i quins modes admet cada família
L'spec del PV conté exactament una clau de backend, que defineix d'on surt l'emmagatzematge de veritat.
hostPath i local: per a proves i per a discos locals
hostPath com a backend d'un PV té els mateixos perills descrits a 05-01 i només s'ha de fer servir en clústers d'un sol node com el teu minikube. local és el seu cosí seriós: també fa servir un disc de la màquina, però és un tipus pensat per a producció, respecta la planificació gràcies a la nodeAffinity obligatòria i no permet rutes arbitràries del sistema. Tot i així, un volum local no sobreviu a la pèrdua del node: la redundància és responsabilitat de l'aplicació (replicació de PostgreSQL, per exemple).
NFS: el clàssic compartit
spec:
capacity: { storage: 100Gi }
accessModes: ["ReadWriteMany"] # diversos nodes alhora: aquesta es la seva gracia
nfs:
server: nas.rutasnorte.example
path: /exports/adjunts
mountOptions: ["hard", "nfsvers=4.1"]NFS és la via més senzilla per aconseguir ReadWriteMany, útil per exemple perquè les tres rèpliques de botiga-web comparteixin un directori d'imatges de trajectes. No és adequat per al fitxer de dades de PostgreSQL: el bloqueig de fitxers sobre NFS és una font clàssica de corrupció.
Controladors CSI: el que es fa servir en producció
En un clúster gestionat no escriuràs gairebé mai un PV a mà; els crearà el driver CSI del proveïdor. Quan els inspeccionis, veuràs una clau csi amb driver: ebs.csi.aws.com, el volumeHandle (l'identificador del disc real al proveïdor), el fsType i uns volumeAttributes.
L'arquitectura de CSI s'estudia a fons a 05-05.
Quins modes admet realment cada família
Taula orientativa; la veritat última la marca el driver i la seva versió.
| Família | RWO | ROX | RWX | RWOP | Nota |
|---|---|---|---|---|---|
hostPath (un node) |
Sí | Sí | Sí | Sí | Tot "funciona" perquè només hi ha un node. No és producció |
local |
Sí | Sí | No | Sí | Lligat al node per nodeAffinity |
| NFS | Sí | Sí | Sí | Sí | L'opció clàssica per compartir |
| Discos de blocs al núvol (EBS, PD, Azure Disk) | Sí | No | No | Sí | Un disc només es connecta a una màquina |
| Fitxers compartits al núvol (EFS, Filestore, Azure Files) | Sí | Sí | Sí | Sí | Més car i amb més latència |
| Ceph RBD | Sí | Sí | No | Sí | Blocs |
| CephFS | Sí | Sí | Sí | Sí | Sistema de fitxers distribuït |
La regla que se'n deriva: si necessites ReadWriteMany, necessites un sistema de fitxers compartit, i això és una decisió d'arquitectura i de cost, no un camp YAML. Per a postgres-reserves no ens cal: una base de dades amb una rèplica vol ReadWriteOnce (o millor ReadWriteOncePod) sobre un disc de blocs ràpid.
- Cicle de vida: les fases del PV
El PV té un camp status.phase amb quatre valors possibles:
stateDiagram-v2
[*] --> Available: es crea el PV (estatic)<br/>o l'aprovisiona la StorageClass
Available --> Bound: el controlador l'aparella<br/>amb un PVC compatible
Bound --> Released: s'esborra el PVC<br/>(les dades SEGUEIXEN alli)
Released --> [*]: reclaimPolicy Delete<br/>-> s'esborra PV i volum
Released --> Available: intervencio manual<br/>(reclaimPolicy Retain)
Available --> Failed: fallada en l'aprovisionament<br/>o en la recuperacio
Bound --> Failed: fallada del backend
Failed --> [*]: correccio manual
| Fase | Significat | Què fer |
|---|---|---|
Available |
Lliure i disponible per ser vinculat | Res; espera un PVC |
Bound |
Vinculat a un PVC concret | Estat normal d'operació |
Released |
El PVC s'ha esborrat; el PV conserva les dades però no es pot reutilitzar tal qual | Recuperar les dades o alliberar-lo a mà (apartat 9) |
Failed |
La recuperació automàtica ha fallat | Investigar amb kubectl describe pv |
Comprovació al clúster:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-postgres-reserves-10gi 10Gi RWO Retain Bound rutas-norte-dev/postgres-reserves-dades rutasnorte-rapida 4m
pv-adjunts-50gi 50Gi RWX Delete Available rutasnorte-estandar 4mLa columna CLAIM et diu, per a cada PV, quin PVC de quin namespace el té agafat. És la vista d'un cop d'ull que més es fa servir en el dia a dia.
- Què fer amb un PV en
Released
ReleasedAquesta és una situació operativa que apareix sempre i desconcerta la primera vegada. Algú esborra el PVC de postgres-reserves; com que la política és Retain, el PV passa a Released. Crees de nou el mateix PVC, amb el mateix nom i la mateixa petició… i es queda en Pending per sempre, tot i que el PV que vol és allà amb 10Gi lliures.
La causa és que el PV conserva a spec.claimRef la referència al PVC anterior, inclòs el seu uid:
{"kind":"PersistentVolumeClaim","namespace":"rutas-norte-dev",
"name":"postgres-reserves-dades","uid":"1c5b7f2a-3d9e-4c11-9f88-0a2b6d4e7c31"}Encara que el PVC nou es digui igual, el seu uid és diferent, així que no casa. I aquest bloqueig és deliberat: és la protecció que impedeix que les dades d'un inquilí acabin muntades per error al pod d'un altre.
El procediment correcte té tres camins, en ordre de preferència:
a) Reutilitzar el volum per al mateix propòsit (el cas d'una recuperació després d'un esborrat accidental). S'elimina la referència caducada i el PV torna a Available:
kubectl patch pv pv-postgres-reserves-10gi \
--type=json -p='[{"op": "remove", "path": "/spec/claimRef"}]'
kubectl get pv pv-postgres-reserves-10giNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-postgres-reserves-10gi 10Gi RWO Retain Available rutasnorte-rapida 1hAra qualsevol PVC compatible el pot agafar. Les dades continuen a dins.
b) Preassignar-lo a un PVC concret, que és més segur que deixar-lo solt. S'edita el claimRef deixant namespace i name però sense uid: el PV només acceptarà aquest PVC exacte.
spec:
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
namespace: rutas-norte-dev
name: postgres-reserves-dades
# sense uid: s'emplenara en vincular-sec) Descartar-lo. Si les dades ja no valen, s'esborra el PV i es neteja el volum real a mà (amb Retain, esborrar l'objecte PV no esborra les dades del backend: al núvol el disc es continua facturant).
Consell operatiu: abans de reutilitzar un PV
Releasedque contingui dades, fes-ne una còpia. L'apartat 10 crea el volum; la còpia de seguretat s'estudia a 05-06, però la regla s'aplica des d'avui.
- Pràctica: un PV de 10 GiB per a
postgres-reserves
postgres-reservesAnem a crear el volum que salda el deute del mòdul. A minikube el suport serà un directori del node, i aquí hostPath és acceptable per ser un clúster d'un únic node i de proves; el manifest porta un comentari que ho deixa explícit perquè ningú no el copiï a producció.
Primer preparem el directori al node:
minikube ssh -p rutas-norte -- "sudo mkdir -p /data/postgres-reserves && \
sudo chmod 777 /data/postgres-reserves && ls -ld /data/postgres-reserves"A minikube, /data és un directori que persisteix als reinicis de la màquina virtual, a diferència de la major part del sistema de fitxers. Per això és el lloc correcte per a aquesta pràctica.
Ara el PersistentVolume:
# k8s/base/pv-postgres-reserves.yaml
#
# AVIS: el backend hostPath es valid UNICAMENT al minikube de practiques,
# que es un cluster d'un sol node. En pre i pro aquest PV el crea el driver CSI
# de forma dinamica a partir de la StorageClass rutasnorte-rapida (veure 05-04).
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-postgres-reserves-10gi
labels:
app: postgres-reserves
app.kubernetes.io/name: postgres-reserves
app.kubernetes.io/component: base-dades
app.kubernetes.io/part-of: rutas-norte
entorn: dev
tipus-disc: ssd
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
# Retain: si algu esborra el PVC (o el namespace sencer), les dades
# de reserves i clients NO es destrueixen. Decisio deliberada.
persistentVolumeReclaimPolicy: Retain
storageClassName: rutasnorte-rapida
mountOptions:
- noatime
hostPath:
path: /data/postgres-reserves
type: DirectoryOrCreatepersistentvolume/pv-postgres-reserves-10gi created
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-postgres-reserves-10gi 10Gi RWO Retain Available rutasnorte-rapida 3sAvailable: existeix, està lliure i espera un PVC. Fixa't que no hi ha cap namespace a la sortida: és un recurs del clúster.
Vista detallada:
Name: pv-postgres-reserves-10gi
Finalizers: [kubernetes.io/pv-protection]
StorageClass: rutasnorte-rapida
Status: Available
Claim:
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 10Gi
Node Affinity: <none>
Source:
Type: HostPath (bare host directory volume)
Path: /data/postgres-reserves
HostPathType: DirectoryOrCreate
Events: <none>Dos detalls que val la pena llegir:
Finalizers: [kubernetes.io/pv-protection]. És el mecanisme de 01-06: impedeix que el PV s'esborri mentre estigui vinculat a un PVC. Si esborres un PV en ús, l'objecte queda enTerminatingfins que deixa d'estar-ho.Claim:buit. Ningú no l'ha reclamat encara. Aquest buit l'omplirà el PVC de la lliçó següent.
Ara comprova el que encara no passa: el pod de postgres-reserves continua sense fer-lo servir. Un PV no es munta sol; cal un PVC que el reclami. Això és exactament el contingut de 05-03.
- Per què l'aprovisionament estàtic no escala
El que acabes de fer s'anomena aprovisionament estàtic: un humà crea el PV a mà, per endavant, i espera que algú el reclami. Funciona, i per a un directori a minikube o per a una cabina d'emmagatzematge fixa en un centre de dades propi és perfectament raonable. Però com a model general falla per quatre bandes:
| Problema | Per què fa mal |
|---|---|
| Feina manual al camí crític | Cada equip que desplega alguna cosa amb estat ha d'esperar que un administrador li creï el PV. La plataforma es converteix en un coll d'ampolla |
| Ajust de mides impossible | Si prepares PV de 10, 20 i 100 GiB, un PVC de 12 GiB es vincularà al de 20 i malgastaràs 8 GiB; i si en demana 150 no hi haurà candidat encara que hi hagi terabytes lliures |
| Volums orfes | Els PV Released amb Retain s'acumulen; al núvol continuen facturant encara que ningú no els faci servir |
| Topologia a cegues | L'administrador crea el PV en una zona sense saber on acabarà el pod; l'aparellament pot deixar el pod impossible de planificar |
La solució és invertir l'ordre: en lloc de crear volums per si de cas, crear el volum exacte en el moment en què algú el demana. Això és l'aprovisionament dinàmic, i l'objecte que el fa possible és la StorageClass, que ja hem anomenat a storageClassName sense explicar-la. La veuràs a 05-04 i la maquinària que hi ha al darrere, CSI, a 05-05.
La bona notícia és que el que estàs aprenent no canvia: en aprovisionament dinàmic continuen existint PV amb exactament aquests camps —capacitat, modes d'accés, política de reclamació, afinitat de node—; l'única diferència és qui els escriu.
Errors Comuns i Consells
| Error | Símptoma | Solució |
|---|---|---|
Creure que ReadWriteOnce significa "un pod" |
Dos pods escriuen alhora i corrompen les dades | Significa un node. Fes servir ReadWriteOncePod i strategy: Recreate |
Declarar ReadWriteMany sobre un disc de blocs |
El pod del segon node no arrenca: Multi-Attach error |
Kubernetes no valida els modes; consulta la taula del backend |
Deixar la política Delete en un volum de dades |
S'esborra un PVC i desapareixen les dades | Retain en tot el que guardi dades de negoci |
Intentar reutilitzar un PV Released |
El PVC nou es queda en Pending sense explicació clara |
Elimina spec.claimRef amb kubectl patch --type=json |
Crear un PV local sense nodeAffinity |
El pod es planifica en un node sense el disc: FailedMount |
La nodeAffinity és obligatòria a local |
Suposar que capacity mesura alguna cosa |
El volum s'omple molt abans del que s'ha declarat | capacity és una dada declarada, no mesurada |
hostPath en un clúster de diversos nodes |
Les dades "canvien" segons on caigui el pod | Només en clústers d'un node; en producció, CSI |
Posar mountOptions que el backend no admet |
Pod encallat en ContainerCreating, esdeveniment FailedMount |
Comprova abans les opcions del driver |
Buscar el PV amb -n <namespace> |
"No resources found" tot i que existeix | El PV no té namespace |
Consells operatius:
- Etiqueta els PV. Sense etiquetes no els pots fer servir amb
selectordes d'un PVC ni filtrar-los en una auditoria. Fes servir les mateixes convencions que la resta de Rutas Norte, inclosaapp.kubernetes.io/part-of. - Auditoria ràpida de risc — quins volums es destruirien en esborrar el seu PVC:
kubectl get pv -o custom-columns=\ NOM:.metadata.name,POLITICA:.spec.persistentVolumeReclaimPolicy,\ ESTAT:.status.phase,CLAIM:.spec.claimRef.name | grep Delete - Abans de qualsevol operació delicada, canvia la política a
Retainambkubectl patch. És reversible i ha salvat moltes bases de dades. kubectl get pv,pvc -Aen una sola ordre et dona la foto completa d'oferta i demanda del clúster.
Exercicis
Exercici 1: el PV de la base de dades i les seves fases
Crea el PersistentVolume pv-postgres-reserves-10gi de l'apartat 10 al teu minikube. Després:
- Comprova que és un recurs sense namespace i que la seva fase és
Available. - Escriu un fitxer dins del directori del node i verifica que hi és.
- Canvia la seva política de reclamació a
Deleteambkubectl patchi torna a deixar-la enRetain, comprovant el canvi a cada pas. - Intenta esborrar el PV i explica quin finalitzador apareix a
kubectl describe.
Exercici 2: dissenyar tres PV per a Rutas Norte
L'equip de plataforma ha de preparar l'emmagatzematge de tres necessitats. Per a cadascuna, decideix capacity, accessModes, persistentVolumeReclaimPolicy, volumeMode i el tipus de backend, i justifica cada elecció:
- A. Les dades de
postgres-reservesen producció: 200 GiB, una sola instància, contenen dades personals de clients, rendiment crític. - B. Un directori d'imatges de trajectes que les tres rèpliques de
botiga-webhan de llegir simultàniament, i que un procés d'administració actualitza un cop al dia: 20 GiB. - C. Un espai de treball de 500 GiB perquè
informes-ocupaciogeneri l'informe nocturn i el borri en acabar.
Escriu el manifest del cas B.
Exercici 3: rescatar un PV bloquejat
Reprodueix i resol l'incident clàssic:
- Crea un PV
pv-practica-rescatd'1 GiB ambRetain, classemanuali suporthostPatha/data/rescat. - Crea un PVC anomenat
dades-practicaarutas-norte-devque el vinculi i escriu un fitxer testimoni des d'un pod. - Esborra el PVC i observa la fase del PV.
- Torna a crear el mateix PVC. Què passa i per què?
- Arregla-ho sense perdre el fitxer testimoni.
Solucions
Exercici 1
# 1
kubectl apply -f k8s/base/pv-postgres-reserves.yaml
kubectl get pv -n rutas-norte-dev pv-postgres-reserves-10gi # el -n s'IGNORA
kubectl api-resources --namespaced=false | grep persistentvolumes
kubectl get pv pv-postgres-reserves-10gi -o jsonpath='{.status.phase}'; echo# 2
minikube ssh -p rutas-norte -- "echo 'testimoni del PV' | sudo tee /data/postgres-reserves/PROVA.txt"
# 3
for P in Delete Retain; do
kubectl patch pv pv-postgres-reserves-10gi \
-p "{\"spec\":{\"persistentVolumeReclaimPolicy\":\"$P\"}}"
kubectl get pv pv-postgres-reserves-10gi \
-o jsonpath='{.spec.persistentVolumeReclaimPolicy}'; echo
done
# 4
kubectl describe pv pv-postgres-reserves-10gi | grep -i finalizersMentre el PV estigui Available l'esborrat és immediat: el finalitzador es retira sol. Si estigués Bound a un PVC, l'objecte quedaria en Terminating fins que el PVC desaparegués. És la protecció de volum en ús, germana de la pvc-protection que veuràs a 05-03.
Exercici 2
| Cas | capacity |
accessModes |
reclaimPolicy |
volumeMode |
Backend |
|---|---|---|---|---|---|
A. postgres-reserves pro |
200Gi | ReadWriteOncePod |
Retain |
Filesystem |
Disc de blocs SSD per CSI |
| B. Imatges de trajectes | 20Gi | ReadWriteMany |
Retain |
Filesystem |
NFS o fitxer compartit al núvol |
C. Espai d'informes-ocupacio |
500Gi | ReadWriteOnce |
Delete |
Filesystem |
Disc de blocs estàndard |
Justificacions:
- A. Una sola instància de PostgreSQL:
ReadWriteOncePodgaranteix que cap segon pod no el pugui muntar, ni tan sols al mateix node, cosa que elimina el risc de corrupció per dos processos.Retainperquè conté dades personals de clients i un esborrat accidental del PVC no les pot destruir. Disc de blocs per rendiment: els fitxers compartits afegeixen una latència inacceptable per a una base de dades. - B. Diversos lectors en nodes diferents exigeixen
ReadWriteMany, i això obliga a un sistema de fitxers compartit: un disc de blocs no serveix aquí per molt que s'escriguiRWXal YAML.Retainperquè les imatges són contingut de negoci recuperable però costós de regenerar. - C. Dades completament reconstruïbles:
Deleteevita pagar 500 GiB orfes cada nit. En realitat, el cas C és el candidat perfecte per a un volum efímer genèric (05-01), que ni tan sols requereix crear el PV.
# k8s/base/pv-imatges-trajectes.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-imatges-trajectes-20gi
labels:
app: botiga-web
app.kubernetes.io/name: imatges-trajectes
app.kubernetes.io/part-of: rutas-norte
entorn: pro
spec:
capacity:
storage: 20Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany # les 3 repliques de botiga-web llegeixen alhora
persistentVolumeReclaimPolicy: Retain
storageClassName: rutasnorte-compartida
mountOptions:
- hard
- nfsvers=4.1
nfs:
server: nas.rutasnorte.example
path: /exports/imatges-trajectesExercici 3
# 1
minikube ssh -p rutas-norte -- "sudo mkdir -p /data/rescat && sudo chmod 777 /data/rescat"
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-practica-rescat
labels: { app: practica, entorn: dev }
spec:
capacity: { storage: 1Gi }
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: { path: /data/rescat, type: DirectoryOrCreate }
EOF
# 2
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dades-practica
namespace: rutas-norte-dev
labels: { app: practica, entorn: dev }
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: manual
resources: { requests: { storage: 1Gi } }
EOF
kubectl get pv pv-practica-rescat
minikube ssh -p rutas-norte -- "echo 'RESERVA-2026-0815-MARTA' | sudo tee /data/rescat/testimoni.txt"
# 3
kubectl delete pvc dades-practica -n rutas-norte-dev
kubectl get pv pv-practica-rescatNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pv-practica-rescat 1Gi RWO Retain Released rutas-norte-dev/dades-practica manual 2m# 4
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dades-practica
namespace: rutas-norte-dev
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: manual
resources: { requests: { storage: 1Gi } }
EOF
kubectl get pvc dades-practica -n rutas-norte-dev
kubectl describe pvc dades-practica -n rutas-norte-dev | tail -5Es queda en Pending. Per què: el PV està en Released, no en Available, perquè conserva un spec.claimRef que apunta al PVC anterior amb el seu uid. El PVC nou es diu igual però té un altre uid, així que el controlador no el considera candidat. A més, un PV en fase Released mai no s'ofereix per vincular.
# 5
kubectl get pv pv-practica-rescat -o jsonpath='{.spec.claimRef.uid}'; echo
kubectl patch pv pv-practica-rescat \
--type=json -p='[{"op": "remove", "path": "/spec/claimRef"}]'
kubectl get pvc dades-practica -n rutas-norte-dev
minikube ssh -p rutas-norte -- "cat /data/rescat/testimoni.txt"NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
dades-practica Bound pv-practica-rescat 1Gi RWO manual 2m
RESERVA-2026-0815-MARTAEl fitxer testimoni continua intacte: Retain va complir la seva funció. Per netejar, esborra el PVC, repeteix el patch del claimRef i esborra el PV.
Conclusió
Has incorporat l'abstracció que sosté tot l'emmagatzematge de Kubernetes, i el primer que has entès és per què existeix: separa qui administra l'emmagatzematge —l'equip de plataforma, que escriu PersistentVolumes i StorageClasses, recursos del clúster— de qui el consumeix —l'equip d'aplicació, que escriu un PersistentVolumeClaim al seu namespace sense saber què hi ha a sota—. Gràcies a aquesta separació, el manifest de postgres-reserves és idèntic a minikube i en producció encara que a sota hi hagi un directori del node o un SSD d'un núvol.
Coneixes l'especificació completa del PV. La capacity, que és declarada i no mesurada. El volumeMode, amb Filesystem per al 99 % dels casos i Block per a qui gestioni la seva pròpia E/S. Els quatre modes d'accés, i sobretot el parany que més car es paga: ReadWriteOnce significa un node, no un pod, de manera que dos pods a la mateixa màquina poden escriure alhora sobre els fitxers de PostgreSQL; la garantia real és ReadWriteOncePod, estable des de 1.29. La política de reclamació, amb Retain per a tot el que guardi dades de negoci, Delete per al que és reconstruïble —i l'avís que les classes per defecte dels núvols vénen en Delete, així que esborrar un PVC esborra el disc— i Recycle obsolet i substituït per l'aprovisionament dinàmic. I storageClassName com a etiqueta d'aparellament, mountOptions sense validació prèvia i nodeAffinity obligatòria als volums local, primer exemple d'emmagatzematge que condiciona la planificació.
Saps quins backends existeixen i què admet cada família de veritat —hostPath i local per a proves i discos locals, NFS com a via senzilla a ReadWriteMany, i els controladors CSI en producció— i tens clar que escriure ReadWriteMany sobre un disc de blocs no el converteix en compartit. Domines les fases (Available, Bound, Released, Failed) i, molt en particular, l'operació que apareix sempre en un incident real: un PV atrapat en Released pel seu claimRef caducat, que es rescata eliminant aquest camp amb kubectl patch --type=json sense perdre ni un sol byte.
I has creat el volum: pv-postgres-reserves-10gi, Retain, ReadWriteOnce, classe rutasnorte-rapida, esperant en fase Available amb la columna CLAIM buida. Aquest buit és l'argument de la lliçó següent. Un PersistentVolume no es munta sol: algú l'ha de reclamar des del namespace de l'aplicació, i aquest algú és l'objecte que converteix per fi en persistent la base de dades de Rutas Norte. El construïm a Reclamacions de Volums Persistents, on a més farem la prova que portem tot el mòdul prometent: crear una reserva, esborrar el pod i trobar-la intacta.
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
