Día 54 - Shared Volumes en Kubernetes (emptyDir)
Problema / Desafío
El equipo de Nautilus está armando una app multi-container que necesita compartir data temporal entre containers del mismo Pod. Hay que crear un Pod que pruebe el patrón de volumen compartido.
- Pod:
volume-share-xfusion - Volumen:
volume-share(tipoemptyDir) - Container 1:
volume-container-xfusion-1, imagenubuntu:latest, monta el volumen en/tmp/blog, debe estarRunning(sleepinfinito) - Container 2:
volume-container-xfusion-2, imagenubuntu:latest, monta el volumen en/tmp/apps, debe estarRunning - Prueba: crear
/tmp/blog/blog.txtcon el textoWelcome to xFusionCorp Industriesdesde el container 1, verificar que aparezca en/tmp/apps/blog.txtdesde el container 2
Conceptos clave
Qué es un emptyDir
Es el tipo de volumen más simple de Kubernetes. Cuando el Pod arranca, kubelet crea un directorio vacío en el nodo, y lo monta dentro de los containers que lo declaren en sus volumeMounts. Características:
- Empieza vacío (de ahí el nombre — no preserva data previa al Pod)
- Es compartido por todos los containers del Pod que lo monten
- Su lifetime es igual al del Pod: si el Pod se borra, el directorio se borra. Si un container reinicia (crashea y vuelve), el volumen sigue ahí — los datos sobreviven al restart del container, pero no al delete del Pod.
- Vive en el nodo: si el Pod se reschedula a otro nodo (caso de eviction), el
emptyDirse pierde y se crea uno nuevo vacío
Cuándo usar emptyDir
- Cache local entre restarts del container
- Scratch space para procesamiento temporal (ordenamientos, compresión, generación de PDFs, etc.)
- Compartir datos entre containers del mismo Pod (sidecar patterns: nginx + php-fpm, app + log shipper, etc.) — este es el caso de hoy
- Anti-uso: persistencia. Para data que debe sobrevivir a la vida del Pod hay que usar
PersistentVolumeClaimo un volume type comonfs,csi, etc.
emptyDir.medium: disco vs RAM
volumes:
- name: my-cache
emptyDir:
medium: Memory # tmpfs en RAM (muy rápido, pero cuenta como uso de memoria)
sizeLimit: 500Mi
medium |
Backend | Uso típico |
|---|---|---|
| (vacío) | Filesystem del nodo | Default. Sirve para casi todo |
Memory |
tmpfs (RAM) |
Cache extremadamente rápido, scratch de cómputo intensivo |
El tmpfs aparece como uso de memoria del pod (cuenta para limits.memory), no como disco. Si tu app llena el emptyDir y excede el memory limit del pod → OOMKilled.
Mountar el mismo volumen en paths distintos
Esta es la propiedad clave del ejercicio de hoy: un volumen, dos mountPaths diferentes — sigue siendo un solo volumen físico.
Visualización mental:
Pod: volume-share-xfusion
└── volume-share (emptyDir real, vive en /var/lib/kubelet/.../volumes/.../volume-share)
↑ ↑
│ │
│ montado en /tmp/blog │ montado en /tmp/apps
│ │
Container 1 Container 2
(ve archivos en /tmp/blog/) (ve los MISMOS archivos en /tmp/apps/)
Cuando el container 1 escribe /tmp/blog/blog.txt, lo está escribiendo al mismo inode físico que el container 2 ve como /tmp/apps/blog.txt. Los containers nunca se ven entre sí — solo ven sus propios filesystems — pero el volumen actúa como un puente.
Conexión con Día 53: ahí teníamos dos containers que compartían
emptyDirpero con paths distintos, y eso causó un bug (nginx mandaba paths a php-fpm vía FastCGI, php-fpm los resolvía en SU filesystem). El takeaway es: compartir bytes ≠ compartir paths. Cuando las apps se mandan paths entre sí, losmountPathtienen que coincidir. Cuando solo comparten archivos físicos, los paths pueden divergir tranquilamente.
Por qué se necesita command en este Pod
Las imágenes de Ubuntu no tienen un proceso PID 1 que se quede corriendo: arrancan, no encuentran nada que hacer, y el container termina (Completed). Para que el Pod se quede Running, hay que sobrescribir el comando con algo que bloquee indefinidamente. Los clásicos:
command: ["sh", "-c", "sleep infinity"] # GNU coreutils — Ubuntu, Debian, Fedora
command: ["tail", "-f", "/dev/null"] # universal, funciona en Alpine también
command: ["sleep", "3600"] # un número grande pero finito
Trampa en Alpine:
sleep infinityno siempre funciona en Alpine viejo (la BusyBoxsleepno acepta el argumentoinfinity).tail -f /dev/nulles el más portable.
Pasos
- Escribir el manifest
pod.ymlcon el volumenemptyDiry los dos containers - Aplicar con
kubectl apply -f - Verificar con
describeque los dos containers montan el mismo volumen en paths distintos - Escribir un archivo desde el container 1 con
kubectl exec - Leer el archivo desde el container 2 con
kubectl exec— confirmar que aparece
Comandos / Código
1. Manifest del Pod
apiVersion: v1
kind: Pod
metadata:
name: volume-share-xfusion
spec:
volumes:
- name: volume-share
emptyDir: {}
containers:
- name: volume-container-xfusion-1
image: ubuntu:latest
volumeMounts:
- name: volume-share
mountPath: /tmp/blog
command:
- sh
- -c
- sleep infinity
- name: volume-container-xfusion-2
image: ubuntu:latest
volumeMounts:
- name: volume-share
mountPath: /tmp/apps
command:
- sh
- -c
- sleep infinity
Estructura a tener clara:
spec.volumes: declara el volumen a nivel de Pod, una sola vez. El nombrevolume-sharees el identificador que usarán los containers.spec.containers[].volumeMounts[]: cada container declara qué volúmenes monta y dónde. El camponamereferencia al volume declarado arriba; elmountPathes local al container.{}después deemptyDires importante: indica "objeto vacío, usar defaults" (filesystem del nodo, sin límite de tamaño). Sin{}el campo queda ennully K8s rechaza el manifest.
2. Aplicar y verificar
READY 2/2 confirma que ambos containers iniciaron correctamente — sin el sleep infinity veríamos Completed o CrashLoopBackOff.
3. Confirmar el shared mount con describe
Output relevante (las dos secciones de Mounts: y la sección Volumes:):
Containers:
volume-container-xfusion-1:
Image: ubuntu:latest
Command: sh -c sleep infinity
Mounts:
/tmp/blog from volume-share (rw) ← mismo volumen
volume-container-xfusion-2:
Image: ubuntu:latest
Command: sh -c sleep infinity
Mounts:
/tmp/apps from volume-share (rw) ← mismo volumen, distinto path
Volumes:
volume-share:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
Tres confirmaciones:
- Los dos containers referencian
volume-shareen susMounts: - Los
mountPaths son distintos (/tmp/blogvs/tmp/apps) pero apuntan al mismo volumen lógico Type: EmptyDirconMedium:vacío (filesystem del nodo, notmpfs) ySizeLimit: <unset>(sin límite)
Detalle de QoS Class: en este Pod aparece
QoS Class: BestEffortporque no declaramos nirequestsnilimitsen ningún container. En un pod productivo serio habría que agregar resources (ver Día 50 para el detalle del cálculo de QoS).
4. Escribir desde el container 1
kubectl exec -it volume-share-xfusion -c volume-container-xfusion-1 \
-- sh -c "echo Welcome to xFusionCorp Industries > /tmp/blog/blog.txt"
Por qué
-c: el Pod tiene 2 containers; sin-ckubectl execfalla conerror: container name must be specified. Es lo mismo que vimos en Día 53 parakubectl cpykubectl logs.Por qué
sh -c "...": los redirects (>,>>,|) son interpretados por la shell, no porexec. Sin el wrappingsh -c, el>se interpretaría en la shell local del jump-host (intentando escribir en su disco, no en el del pod). Wrapping consh -cenvía el comando como string al shell DENTRO del container.
Verificación dentro del mismo container:
kubectl exec -it volume-share-xfusion -c volume-container-xfusion-1 \
-- sh -c "cat /tmp/blog/blog.txt"
5. Leer desde el container 2 (la prueba clave)
kubectl exec -it volume-share-xfusion -c volume-container-xfusion-2 \
-- sh -c "cat /tmp/apps/blog.txt"
El test pasó: el archivo escrito por el container 1 en /tmp/blog/blog.txt apareció en /tmp/apps/blog.txt del container 2. No se hizo copia ni sincronización — es el mismo archivo físico visto desde dos paths distintos.
Comandos alternativos útiles
Entrar interactivamente a un container (en vez de un one-shot)
# Abrir una shell dentro de un container
kubectl exec -it volume-share-xfusion -c volume-container-xfusion-2 -- bash
# Una vez adentro:
root@volume-share-xfusion:/# ls /tmp/apps/
blog.txt
root@volume-share-xfusion:/# cat /tmp/apps/blog.txt
Welcome to xFusionCorp Industries
root@volume-share-xfusion:/# exit
Verificar el path real del emptyDir en el nodo
# Solo con acceso al nodo (en un cluster real esto es raro)
kubectl get pod volume-share-xfusion -o jsonpath='{.metadata.uid}'
# → 7a3e8f2c-...
# En el nodo, el emptyDir vive en:
# /var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~empty-dir/volume-share
En labs como KodeKloud (donde el jump-host ES el nodo) sí se puede mirar este path. En clusters gestionados (EKS, GKE, AKS) generalmente no hay SSH al nodo, así que esto queda en concepto.
Troubleshooting
| Problema | Causa y solución |
|---|---|
Pod en estado Completed apenas arranca |
La imagen no tiene un proceso que se quede corriendo. Agregar command: ["sh","-c","sleep infinity"] o similar |
kubectl exec falla con container name must be specified |
El Pod tiene varios containers — agregar -c <container-name>. Listar con kubectl get pod <name> -o jsonpath='{.spec.containers[*].name}' |
| Archivo escrito en container 1 no aparece en container 2 | Los containers están montando volúmenes distintos. Verificar con describe que ambos referencien el mismo volume-share en sus Mounts: |
Manifest rechazado con Invalid value: "null" en emptyDir |
Falta el {} después de emptyDir:. Es un objeto, no un valor escalar |
Pod desaparece y al volver el emptyDir está vacío |
El emptyDir no es persistente: si el Pod se borra (no solo si el container reinicia) el volumen se pierde. Para persistencia usar PVC |
El emptyDir llena el disco del nodo |
Sin sizeLimit el volumen puede crecer hasta agotar el disco del nodo. Agregar emptyDir: { sizeLimit: 500Mi } para limitarlo |
Pod evictado por MemoryPressure aunque la app parecía liviana |
Usaste medium: Memory y el emptyDir (tmpfs) cuenta para los limits.memory del pod. Bajá el sizeLimit o sacá el medium: Memory |