# Activitats de Volums en Kubernetes
**Durada estimada:** 3-4 hores
**Nivell:** Intermedi
**Prerequisits:** Coneixements bàsics de Pods, Deployments i Services
**Entorn:** Minikube amb driver Docker
---
## 📚 Taula de continguts
1. [Activitat 1: emptyDir - Volums temporals compartits](#activitat-1-emptydir---volums-temporals-compartits)
2. [Activitat 2: hostPath - Persistència bàsica amb el node](#activitat-2-hostpath---persistència-bàsica-amb-el-node)
3. [Activitat 3: PersistentVolume i PersistentVolumeClaim - WordPress complet](#activitat-3-persistentvolume-i-persistentvolumeclaim---wordpress-complet)
4. [Recursos addicionals](#recursos-addicionals)
---
## Activitat 1: emptyDir - Volums temporals compartits
### 🎯 Objectius
- Comprendre el funcionament dels volums `emptyDir`
- Aprendre a compartir dades entre containers d'un mateix Pod
- Entendre el cicle de vida dels volums temporals
### 📖 Teoria
Un volum `emptyDir` es crea quan s'assigna un Pod a un node i existeix mentre el Pod s'executa en aquest node. Com el seu nom indica, inicialment està buit. Tots els containers del Pod poden llegir i escriure fitxers en el volum `emptyDir`.
**Característiques clau:**
- ✅ Ideal per compartir dades entre containers d'un mateix Pod
- ✅ Útil per emmagatzematge temporal (cache, scratch space)
- ⚠️ Les dades es perden quan el Pod s'elimina o es reinicia
- ⚠️ Les dades NO sobreviuen a fallades del container
**Arquitectura:**
```
┌─────────────────────────────────────┐
│ POD │
│ ┌──────────────┐ ┌─────────────┐ │
│ │ Container 1 │ │ Container 2 │ │
│ │ (Writer) │ │ (Reader) │ │
│ └──────┬───────┘ └──────┬──────┘ │
│ │ │ │
│ └────────┬────────┘ │
│ ┌───▼────┐ │
│ │emptyDir│ │
│ └────────┘ │
└─────────────────────────────────────┘
```
### 💻 Pràctica
#### Pas 1: Crear el namespace de treball
```bash
kubectl create namespace volums-lab
kubectl config set-context --current --namespace=volums-lab
```
#### Pas 2: Crear el fitxer YAML del Pod
Crea un fitxer anomenat `emptydir-pod.yaml`:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: shared-data-pod
labels:
app: emptydir-demo
spec:
containers:
# Container que escriu dades
- name: writer
image: busybox:1.36
command: ["/bin/sh"]
args:
- -c
- |
while true; do
echo "$(date): Message from writer" >> /data/shared.log
sleep 5
done
volumeMounts:
- name: shared-storage
mountPath: /data
# Container que llegeix dades
- name: reader
image: busybox:1.36
command: ["/bin/sh"]
args:
- -c
- |
while true; do
echo "=== Latest log entries ==="
tail -n 3 /data/shared.log 2>/dev/null || echo "Waiting for data..."
sleep 10
done
volumeMounts:
- name: shared-storage
mountPath: /data
readOnly: true # El reader només necessita llegir
# Definició del volum emptyDir
volumes:
- name: shared-storage
emptyDir: {} # Volum temporal buit
```
#### Pas 3: Desplegar el Pod
```bash
kubectl apply -f emptydir-pod.yaml
```
#### Pas 4: Verificar el desplegament
```bash
# Comprovar que el Pod està running
kubectl get pods
# Veure els logs del container writer
kubectl logs shared-data-pod -c writer
# Veure els logs del container reader
kubectl logs shared-data-pod -c reader -f
```
### ✅ Verificació i experimentació
**Experiment 1: Compartició de dades**
```bash
# Executar una shell interactiva al container writer
kubectl exec -it shared-data-pod -c writer -- sh
# Dins del container, crear un fitxer
echo "Hello from writer!" > /data/test.txt
cat /data/shared.log
exit
# Ara accedir al container reader
kubectl exec -it shared-data-pod -c reader -- sh
# Dins del container, llegir el fitxer
cat /data/test.txt
cat /data/shared.log
exit
```
**Experiment 2: Permanència de dades**
```bash
# Eliminar un dels containers (forçant restart)
kubectl exec -it shared-data-pod -c writer -- kill 1
# Esperar que el container es reiniciï
kubectl get pods -w
# Comprovar si les dades segueixen existint
kubectl exec -it shared-data-pod -c reader -- cat /data/test.txt
# ✅ Les dades sobreviuen al restart del container
```
**Experiment 3: Eliminació del Pod**
```bash
# Eliminar el Pod complet
kubectl delete pod shared-data-pod
# Tornar a crear-lo
kubectl apply -f emptydir-pod.yaml
# Intentar llegir les dades anteriors
kubectl exec -it shared-data-pod -c reader -- cat /data/test.txt
# ❌ Les dades NO sobreviuen a l'eliminació del Pod
```
### 🤔 Preguntes de reflexió
1. Per què les dades sobreviuen al restart d'un container però no a l'eliminació del Pod?
2. En quins casos seria útil utilitzar un `emptyDir` en lloc d'un volum persistent?
3. Què passaria si el node on s'executa el Pod falla?
### 🧹 Neteja
```bash
kubectl delete pod shared-data-pod --force
```
---
## Activitat 2: hostPath - Persistència bàsica amb el node
### 🎯 Objectius
- Comprendre el funcionament dels volums `hostPath`
- Aprendre a persistir dades més enllà del cicle de vida d'un Pod
- Entendre les limitacions i riscos dels volums `hostPath`
### 📖 Teoria
Un volum `hostPath` munta un fitxer o directori del sistema de fitxers del node host dins del Pod. Això permet que les dades persisteixin més enllà del cicle de vida del Pod.
**Característiques clau:**
- ✅ Les dades sobreviuen a l'eliminació i recreació de Pods
- ✅ Útil per desenvolupament i proves locals
- ⚠️ Només funciona si el Pod es crea al mateix node
- ⚠️ Risc de seguretat: accés directe al filesystem del node
- ❌ NO recomanat per producció en clusters multi-node
- ❌ Fa els Pods dependents d'un node específic
**Arquitectura:**
```
┌─────────────────────────────────┐
│ NODE (Minikube) │
│ │
│ /mnt/data/ (directori host) │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ POD │ │
│ │ ┌────────┐ │ │
│ │ │Container│ │ │
│ │ │ /data ──┼─┼──► hostPath │
│ │ └────────┘ │ │
│ └──────────────┘ │
└─────────────────────────────────┘
```
### 💻 Pràctica
#### Pas 1: Preparar el directori al node de Minikube
```bash
# Accedir al node de Minikube
minikube ssh
# Crear el directori i afegir dades de prova
sudo mkdir -p /mnt/data
echo "Persistent data from host" | sudo tee /mnt/data/host-file.txt
sudo chmod -R 777 /mnt/data
exit
```
#### Pas 2: Crear el fitxer YAML del Deployment
Crea un fitxer anomenat `hostpath-deployment.yaml`:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hostpath
labels:
app: nginx-persistent
spec:
replicas: 1 # IMPORTANT: només 1 rèplica amb hostPath
selector:
matchLabels:
app: nginx-persistent
template:
metadata:
labels:
app: nginx-persistent
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
volumeMounts:
- name: html-storage
mountPath: /usr/share/nginx/html
- name: logs-storage
mountPath: /var/log/nginx
volumes:
- name: html-storage
hostPath:
path: /mnt/data/html # Directori al host per HTML
type: DirectoryOrCreate # Crea el directori si no existeix
- name: logs-storage
hostPath:
path: /mnt/data/logs # Directori al host per logs
type: DirectoryOrCreate
```
**Tipus de hostPath disponibles:**
- `DirectoryOrCreate`: Crea el directori si no existeix
- `Directory`: El directori ha d'existir
- `FileOrCreate`: Crea el fitxer si no existeix
- `File`: El fitxer ha d'existir
- `Socket`, `CharDevice`, `BlockDevice`: Per recursos específics
#### Pas 3: Desplegar l'aplicació
```bash
kubectl apply -f hostpath-deployment.yaml
# Esperar que el Pod estigui ready
kubectl wait --for=condition=ready pod -l app=nginx-persistent --timeout=60s
# Obtenir el nom del Pod
kubectl get pods -l app=nginx-persistent
```
#### Pas 4: Afegir contingut HTML personalitzat
```bash
# Obtenir el nom del Pod (substitueix <pod-name> pel nom real)
export POD_NAME=$(kubectl get pods -l app=nginx-persistent -o jsonpath='{.items[0].metadata.name}')
# Crear una pàgina HTML personalitzada
kubectl exec $POD_NAME -- sh -c 'echo "<h1>Hello from Kubernetes with hostPath!</h1><p>This data persists across Pod restarts.</p>" > /usr/share/nginx/html/index.html'
# Generar alguns logs accedint al nginx
kubectl exec $POD_NAME -- wget -O /dev/null http://localhost/
```
#### Pas 5: Crear un Service per accedir al nginx
Crea un fitxer anomenat `nginx-service.yaml`:
```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx-persistent
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30080 # Port accessible des del host
```
```bash
kubectl apply -f nginx-service.yaml
# Obtenir la URL per accedir al servei
minikube service nginx-service --url -n volums-lab
```
Obre la URL al navegador o utilitza curl:
```bash
curl $(minikube service nginx-service --url -n volums-lab)
```
### ✅ Verificació i experimentació
**Experiment 1: Persistència després d'eliminar el Pod**
```bash
# Eliminar el Pod (el Deployment en crearà un de nou)
kubectl delete pod $POD_NAME
# Esperar que el nou Pod estigui ready
kubectl wait --for=condition=ready pod -l app=nginx-persistent --timeout=60s
# Obtenir el nom del nou Pod
export POD_NAME=$(kubectl get pods -l app=nginx-persistent -o jsonpath='{.items[0].metadata.name}')
# Comprovar que el contingut HTML encara existeix
kubectl exec $POD_NAME -- cat /usr/share/nginx/html/index.html
# ✅ Les dades persisteixen!
```
**Experiment 2: Accedir directament a les dades al node**
```bash
# Accedir al node de Minikube
minikube ssh
# Veure els fitxers que ha creat el Pod
ls -la /mnt/data/html/
cat /mnt/data/html/index.html
# Veure els logs del nginx
ls -la /mnt/data/logs/
cat /mnt/data/logs/access.log
# Modificar directament des del host
echo "<h1>Modified from the host node</h1>" | sudo tee /mnt/data/html/index.html
exit
# Comprovar des del Pod
kubectl exec $POD_NAME -- cat /usr/share/nginx/html/index.html
# ✅ Els canvis al host es reflecteixen al Pod immediatament
```
**Experiment 3: Limitació amb múltiples rèpliques**
```bash
# Intentar escalar a 2 rèpliques
kubectl scale deployment nginx-hostpath --replicas=2
# Veure on s'han desplegat els Pods
kubectl get pods -l app=nginx-persistent -o wide
# En un cluster multi-node, els Pods podrien anar a nodes diferents
# En Minikube (single-node), tots van al mateix node, però això
# il·lustra per què hostPath no és adequat per producció
```
### 🤔 Preguntes de reflexió
1. Per què `hostPath` no és recomanable en entorns de producció?
2. Què passaria si escalem a múltiples rèpliques en un cluster amb 3 nodes?
3. Quins riscos de seguretat comporta donar accés al filesystem del host?
### 🧹 Neteja
```bash
kubectl delete deployment nginx-hostpath
kubectl delete service nginx-service
# Opcional: Netejar dades del node
minikube ssh
sudo rm -rf /mnt/data
exit
```
---
## Activitat 3: PersistentVolume i PersistentVolumeClaim - WordPress complet
### 🎯 Objectius
- Comprendre el model de persistència professional amb PV i PVC
- Desplegar una aplicació multi-tier amb persistència
- Implementar un WordPress complet amb MySQL persistent
- Verificar la recuperació de dades després de fallades
### 📖 Teoria
El model **PersistentVolume (PV)** i **PersistentVolumeClaim (PVC)** proporciona una abstracció entre l'emmagatzematge i el seu ús:
- **PersistentVolume (PV)**: Recurs d'emmagatzematge al cluster (provisionat per l'administrador o dinàmicament)
- **PersistentVolumeClaim (PVC)**: Sol·licitud d'emmagatzematge per part d'un usuari
- **StorageClass**: Defineix diferents "classes" d'emmagatzematge amb diferents propietats
**Característiques clau:**
- ✅ Abstracció entre emmagatzematge i consum
- ✅ Provisionament dinàmic amb StorageClass
- ✅ Persistència independent del cicle de vida dels Pods
- ✅ Adequat per entorns de producció
- ✅ Suport per diferents backends (NFS, iSCSI, Cloud providers, etc.)
**Arquitectura de la solució:**
```
┌─────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ │
│ ┌──────────────────┐ ┌─────────────────┐ │
│ │ WordPress Pod │────────▶│ MySQL Pod │ │
│ │ ┌────────────┐ │ │ ┌───────────┐ │ │
│ │ │ WordPress │ │ 3306 │ │ MySQL │ │ │
│ │ │ Container │ │ │ │ Container │ │ │
│ │ └─────┬──────┘ │ │ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───▼────┐ │ │ ┌───▼────┐ │ │
│ │ │PVC: WP │ │ │ │PVC: DB │ │ │
│ │ └───┬────┘ │ │ └───┬────┘ │ │
│ └────────┼─────────┘ └────────┼────────┘ │
│ │ │ │
│ ┌───▼────┐ ┌───▼────┐ │
│ │PV: WP │ │PV: DB │ │
│ └───┬────┘ └───┬────┘ │
│ │ │ │
│ ┌────▼────────────────────────────▼─────┐ │
│ │ StorageClass (standard) │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Secret: mysql-credentials │ │
│ │ - MYSQL_ROOT_PASSWORD │ │
│ │ - MYSQL_DATABASE │ │
│ │ - MYSQL_USER │ │
│ │ - MYSQL_PASSWORD │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
**Flux de treball:**
1. El desenvolupador crea un PVC sol·licitant 1GB d'emmagatzematge
2. El StorageClass provisiona automàticament un PV
3. Kubernetes fa el binding entre PVC i PV
4. El Pod munta el PVC com un volum
5. Les dades escrites persisteixen independentment del Pod
### 💻 Pràctica
#### Pas 1: Verificar el StorageClass disponible
```bash
# Llistar StorageClasses disponibles
kubectl get storageclass
# Veure detalls del StorageClass per defecte de Minikube
kubectl describe storageclass standard
```
A Minikube, el StorageClass `standard` utilitza el provisionador `k8s.io/minikube-hostpath` que crea volums persistents al node.
#### Pas 2: Crear el Secret per les credencials de MySQL
Crea un fitxer anomenat `mysql-secret.yaml`:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: mysql-credentials
type: Opaque
stringData:
# Credencials de MySQL (en producció, utilitzar secrets externs com Vault)
MYSQL_ROOT_PASSWORD: rootpassword123
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword123
```
```bash
kubectl apply -f mysql-secret.yaml
```
#### Pas 3: Crear el PersistentVolumeClaim per MySQL
Crea un fitxer anomenat `mysql-pvc.yaml`:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
labels:
app: wordpress
tier: database
spec:
accessModes:
- ReadWriteOnce # El volum pot ser muntat com read-write per UN sol node
resources:
requests:
storage: 2Gi # Sol·licitem 2GB per la base de dades
storageClassName: standard # Utilitzem el StorageClass per defecte
```
**Access Modes disponibles:**
- `ReadWriteOnce (RWO)`: Muntat com read-write per un sol node
- `ReadOnlyMany (ROX)`: Muntat com read-only per múltiples nodes
- `ReadWriteMany (RWX)`: Muntat com read-write per múltiples nodes
```bash
kubectl apply -f mysql-pvc.yaml
# Comprovar l'estat del PVC
kubectl get pvc mysql-pvc
# Veure detalls del PVC
kubectl describe pvc mysql-pvc
```
#### Pas 4: Desplegar MySQL amb persistència
Crea un fitxer anomenat `mysql-deployment.yaml`:
```yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: wordpress
tier: database
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: wordpress
tier: database
clusterIP: None # Headless service per comunicació interna
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
labels:
app: wordpress
tier: database
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
tier: database
strategy:
type: Recreate # Important per volums RWO
template:
metadata:
labels:
app: wordpress
tier: database
spec:
containers:
- name: mysql
image: mysql:8.0
env:
# Variables d'entorn obtingudes del Secret
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_ROOT_PASSWORD
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_DATABASE
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_PASSWORD
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql # Directori de dades de MySQL
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc # Referència al PVC creat anteriorment
```
```bash
kubectl apply -f mysql-deployment.yaml
# Esperar que MySQL estigui ready
kubectl wait --for=condition=ready pod -l tier=database --timeout=120s
# Comprovar els logs de MySQL
kubectl logs -l tier=database
```
#### Pas 5: Crear el PersistentVolumeClaim per WordPress
Crea un fitxer anomenat `wordpress-pvc.yaml`:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-pvc
labels:
app: wordpress
tier: frontend
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi # Sol·licitem 3GB per fitxers de WordPress
storageClassName: standard
```
```bash
kubectl apply -f wordpress-pvc.yaml
# Comprovar que ambdós PVCs estan bound
kubectl get pvc
```
#### Pas 6: Desplegar WordPress amb persistència
Crea un fitxer anomenat `wordpress-deployment.yaml`:
```yaml
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
tier: frontend
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30081 # Port accessible des del host
selector:
app: wordpress
tier: frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
tier: frontend
spec:
replicas: 1
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- name: wordpress
image: wordpress:6.4-apache
env:
# Configuració de connexió a MySQL
- name: WORDPRESS_DB_HOST
value: mysql:3306 # Nom del servei de MySQL
- name: WORDPRESS_DB_NAME
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_DATABASE
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_USER
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_PASSWORD
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html # Directori arrel de WordPress
livenessProbe:
tcpSocket:
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /wp-login.php
port: 80
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wordpress-pvc
```
```bash
kubectl apply -f wordpress-deployment.yaml
# Esperar que WordPress estigui ready
kubectl wait --for=condition=ready pod -l tier=frontend --timeout=120s
# Obtenir la URL per accedir a WordPress
minikube service wordpress --url -n volums-lab
```
#### Pas 7: Configurar WordPress
Obre la URL al navegador i completa la instal·lació de WordPress:
1. Selecciona l'idioma
2. Configura el lloc:
- Títol del lloc: "Kubernetes WordPress Lab"
- Nom d'usuari: admin
- Contrasenya: (genera una contrasenya forta)
- Correu electrònic: el teu email
3. Fes clic a "Install WordPress"
4. Inicia sessió amb les credencials creades
### ✅ Verificació i proves de persistència
#### Prova 1: Crear contingut i verificar persistència de WordPress
```bash
# Des de WordPress:
# 1. Crea un nou article amb contingut
# 2. Puja una imatge destacada
# 3. Publica l'article
# 4. Instal·la un plugin (per exemple, "Akismet Anti-Spam")
# Eliminar el Pod de WordPress
kubectl delete pod -l tier=frontend
# Esperar que es creï un nou Pod
kubectl wait --for=condition=ready pod -l tier=frontend --timeout=60s
# Accedir de nou a WordPress
minikube service wordpress --url -n volums-lab
# ✅ Verifica que:
# - L'article creat encara existeix
# - Les imatges pujades es mostren correctament
# - El plugin instal·lat segueix actiu
```
#### Prova 2: Verificar persistència de la base de dades MySQL
```bash
# Connectar-se a MySQL i crear dades de prova
export MYSQL_POD=$(kubectl get pods -l tier=database -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $MYSQL_POD -- mysql -u root -prootpassword123 -e "
USE wordpress;
CREATE TABLE test_persistence (
id INT AUTO_INCREMENT PRIMARY KEY,
message VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO test_persistence (message) VALUES ('Data created before Pod deletion');
SELECT * FROM test_persistence;
"
# Eliminar el Pod de MySQL (simular fallada)
kubectl delete pod $MYSQL_POD
# Esperar que es creï un nou Pod
kubectl wait --for=condition=ready pod -l tier=database --timeout=120s
# Obtenir el nom del nou Pod
export MYSQL_POD=$(kubectl get pods -l tier=database -o jsonpath='{.items[0].metadata.name}')
# Verificar que les dades persisteixen
kubectl exec -it $MYSQL_POD -- mysql -u root -prootpassword123 -e "
USE wordpress;
SELECT * FROM test_persistence;
"
# ✅ Les dades haurien d'existir després de la recreació del Pod
```
#### Prova 3: Verificar els PersistentVolumes
```bash
# Llistar tots els PVs
kubectl get pv
# Veure detalls dels PVs (hauríem de veure 2 PVs en estat Bound)
kubectl describe pv
# Comprovar l'ús d'espai dels PVCs
kubectl get pvc
# Veure on s'emmagatzemen realment les dades (al node de Minikube)
minikube ssh
sudo ls -la /tmp/hostpath-provisioner/
# Hauríeu de veure els directoris corresponents als PVs
exit
```
#### Prova 4: Simulació de fallada total (eliminar tots els Pods)
```bash
# Crear més contingut a WordPress abans de la prova
# - Afegeix 2-3 articles més
# - Puja més imatges
# - Configura un tema diferent
# Eliminar TOTS els Pods (WordPress i MySQL)
kubectl delete pods --all
# Esperar que ambdós es recreïn
kubectl wait --for=condition=ready pod -l app=wordpress --timeout=120s
# Accedir de nou a WordPress
minikube service wordpress --url -n volums-lab
# ✅ Verifica que:
# - Tot el contingut està intacte
# - Les configuracions es mantenen
# - Els plugins segueixen actius
# - El tema personalitzat continua aplicat
```
#### Prova 5: Escalar WordPress (múltiples rèpliques)
```bash
# Nota: Amb ReadWriteOnce, múltiples rèpliques al mateix node poden
# causar problemes. Aquesta prova il·lustra les limitacions.
kubectl scale deployment wordpress --replicas=2
# Observar el comportament
kubectl get pods -l tier=frontend -w
# Possibles escenaris:
# - En Minikube (single-node): Ambdós Pods poden arrencar però compartir
# el mateix volum pot causar problemes de concurrència
# - En multi-node: Un Pod quedarà en Pending perquè RWO només permet
# muntatge en un node
# Tornar a 1 rèplica
kubectl scale deployment wordpress --replicas=1
```
### 🔍 Inspecció i debugging
```bash
# Veure events del namespace
kubectl get events --sort-by='.lastTimestamp'
# Logs de MySQL
kubectl logs -l tier=database --tail=50
# Logs de WordPress
kubectl logs -l tier=frontend --tail=50
# Descripció completa del deployment de MySQL
kubectl describe deployment mysql
# Descripció completa del deployment de WordPress
kubectl describe deployment wordpress
# Veure variables d'entorn del Pod de WordPress (sense mostrar secrets)
kubectl exec -it $(kubectl get pods -l tier=frontend -o jsonpath='{.items[0].metadata.name}') -- env | grep WORDPRESS
# Accedir a la shell de WordPress per debugging
kubectl exec -it $(kubectl get pods -l tier=frontend -o jsonpath='{.items[0].metadata.name}') -- bash
# Dins del container:
ls -la /var/www/html/wp-content/
df -h
exit
```
### 📊 Diagrama de components desplegats
```
┌─────────────────────────────────────────────────────────────┐
│ Namespace: volums-lab │
├─────────────────────────────────────────────────────────────┤
│ │
│ Secrets: │
│ └─ mysql-credentials (credencials BD) │
│ │
│ PersistentVolumeClaims: │
│ ├─ mysql-pvc (2Gi, RWO) ──────┐ │
│ └─ wordpress-pvc (3Gi, RWO) ──┼───┐ │
│ │ │ │
│ Services: │ │ │
│ ├─ mysql (ClusterIP: None) │ │ │
│ └─ wordpress (NodePort: 30081) │ │ │
│ │ │ │
│ Deployments: │ │ │
│ ├─ mysql │ │ │
│ │ └─ Pod ────────────────────┘ │ │
│ │ └─ Container: mysql:8.0 │ │
│ │ └─ Volume: mysql-pvc │ │
│ │ Mount: /var/lib/mysql │ │
│ │ │ │
│ └─ wordpress │ │
│ └─ Pod ────────────────────────┘ │
│ └─ Container: wordpress:6.4-apache │
│ └─ Volume: wordpress-pvc │
│ Mount: /var/www/html │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 🤔 Preguntes de reflexió
1. **Quina és la diferència entre un PV i un PVC?**
2. **Per què utilitzem `strategy: Recreate` en lloc de `RollingUpdate`?**
3. **Què passaria si intentem escalar MySQL a 3 rèpliques amb el volum actual?**
4. **Com podríem fer backups de les dades del volum persistent?**
5. **Què passaria si eliminem el PVC mentre el Pod encara l'està utilitzant?**
### 🎯 Exercicis addicionals (opcionals)
#### Exercici A: Afegir phpMyAdmin
Crea un deployment de phpMyAdmin per gestionar la base de dades visualment:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: phpmyadmin
spec:
replicas: 1
selector:
matchLabels:
app: phpmyadmin
template:
metadata:
labels:
app: phpmyadmin
spec:
containers:
- name: phpmyadmin
image: phpmyadmin:5.2
env:
- name: PMA_HOST
value: mysql
- name: PMA_PORT
value: "3306"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: phpmyadmin
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30082
selector:
app: phpmyadmin
```
#### Exercici B: Implementar backups automàtics
Crea un CronJob que faci backups periòdics de la base de dades:
```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
spec:
schedule: "0 2 * * *" # Cada dia a les 2 AM
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0
command:
- /bin/sh
- -c
- |
mysqldump -h mysql -u root -p${MYSQL_ROOT_PASSWORD} --all-databases > /backup/backup-$(date +%Y%m%d-%H%M%S).sql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-credentials
key: MYSQL_ROOT_PASSWORD
volumeMounts:
- name: backup-storage
mountPath: /backup
restartPolicy: OnFailure
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc # Cal crear aquest PVC
```
#### Exercici C: Configurar límits de recursos
Afegeix `requests` i `limits` de CPU i memòria als deployments:
```yaml
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
```
### 🧹 Neteja final
```bash
# Eliminar tots els recursos del namespace
kubectl delete namespace volums-lab
# Nota: Els PVs poden quedar en estat "Released" després d'eliminar els PVCs
# Per eliminar-los completament:
kubectl get pv
kubectl delete pv <pv-name> # Si cal
# Verificar que tot s'ha eliminat
kubectl get all -n volums-lab
kubectl get pvc -n volums-lab
kubectl get pv
```
---
## 📚 Recursos addicionals
### Documentació oficial
- [Kubernetes Volumes](https://kubernetes.io/docs/concepts/storage/volumes/)
- [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/)
- [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/)
- [Dynamic Volume Provisioning](https://kubernetes.io/docs/concepts/storage/dynamic-provisioning/)
### Comparativa de tipus de volums
| Tipus de volum | Persistència | Multi-node | Cas d'ús | Recomanat per producció |
|----------------|--------------|------------|----------|-------------------------|
| `emptyDir` | ❌ Temporal | ❌ No | Cache, scratch space | ❌ No |
| `hostPath` | ✅ Persist al node | ❌ No | Dev/test local | ❌ No |
| `PV/PVC` | ✅ Persistent | ✅ Depèn del backend | Dades d'aplicació | ✅ Sí |
| `configMap` | N/A | ✅ Sí | Configuració | ✅ Sí |
| `secret` | N/A | ✅ Sí | Credencials | ✅ Sí |
### Backends d'emmagatzematge per producció
- **Cloud providers:**
- AWS: EBS, EFS
- GCP: Persistent Disk, Filestore
- Azure: Disk, Files
- **On-premise:**
- NFS
- Ceph / Rook
- GlusterFS
- Longhorn
- **Altres:**
- Local Persistent Volumes
- iSCSI
### Bones pràctiques
1. **Utilitza sempre PV/PVC en producció**, no `hostPath`
2. **Defineix StorageClasses** per diferents tipus d'emmagatzematge (SSD, HDD, etc.)
3. **Estableix límits de quota** per namespace per controlar l'ús d'emmagatzematge
4. **Implementa backups automàtics** de volums crítics
5. **Utilitza `Recreate` strategy** per deployments amb volums RWO
6. **Monitoritza l'ús d'espai** dels volums persistents
7. **Utilitza Secrets** per credencials, no variables d'entorn en text pla
8. **Documenta les polítiques de retenció** (`Retain`, `Delete`, `Recycle`)
9. **Testa els procediments de recuperació** regularment
### Solució de problemes comuns
**Problema: PVC en estat "Pending"**
```bash
# Comprovar events
kubectl describe pvc <pvc-name>
# Possibles causes:
# - No hi ha StorageClass disponible
# - StorageClass no pot provisionar el volum
# - No hi ha espai disponible
```
**Problema: Pod no pot muntar el volum**
```bash
# Comprovar events del Pod
kubectl describe pod <pod-name>
# Possibles causes:
# - PVC no està en estat Bound
# - Access mode incompatible
# - Volum ja muntat en un altre node (amb RWO)
```
**Problema: Dades corrompudes després de fallada**
```bash
# Comprovar l'estat del filesystem
kubectl exec <pod-name> -- df -h
kubectl exec <pod-name> -- ls -la <mount-path>
# Per MySQL, comprovar integritat:
kubectl exec <mysql-pod> -- mysqlcheck -u root -p<password> --all-databases
```
---
## 🎓 Resum i conclusions
### Què hem après
1. **emptyDir**: Volums temporals ideals per compartir dades entre containers del mateix Pod
2. **hostPath**: Persistència bàsica lligada al node (només per dev/test)
3. **PV/PVC**: Model professional de persistència, adequat per producció
### Evolució del coneixement
```
emptyDir (Temporal)
↓
└─► Dades compartides dins del Pod
hostPath (Persistent al node)
↓
└─► Dades que sobreviuen al Pod però lligades al node
PV/PVC (Persistent i portable)
↓
└─► Abstracció professional, independència d'infraestructura
```
### Pròxims passos
- Explorar **StatefulSets** per aplicacions amb identitat de xarxa persistent
- Implementar **backup i restore** amb eines com Velero
- Estudiar **CSI (Container Storage Interface)** per backends avançats
- Aprendre sobre **Volume Snapshots** per crear còpies instantànies
- Investigar **Storage Quotas** i **Resource Quotas** per gestió d'espai
---
**Autor:** Activitats per aprendre Kubernetes amb Minikube
**Data:** Gener 2026
**Versió:** 1.0
**Llicència:** Creative Commons BY-SA 4.0
---
**Nota final:** Aquestes activitats estan dissenyades per executar-se en un entorn de Minikube local. Per utilitzar-les en un cluster de producció, caldrà adaptar els StorageClasses, els backends d'emmagatzematge i les configuracions de seguretat segons l'entorn específic.