El disco llega al 92%. Las alertas empiezan a sonar. Alguien abre un ticket de “limpieza” que parece inofensivo: rotar logs, podar imágenes, borrar archivos temporales, vacuum de journals. El tipo de tarea que haces entre reuniones.
Dos horas después, tu base de datos está en solo lectura, tu nodo es desalojado y el postmortem se titula algo así como “Incidente de eliminación #7”. ¿La herramienta culpable? No es malware. No es un atacante. Una utilidad de confianza, haciendo exactamente lo que le dijeron.
Por qué la limpieza es especialmente peligrosa en producción
La limpieza es destructiva, usualmente irreversible y con frecuencia se realiza bajo presión de tiempo. Esa es la trifecta. Añade un cuarto: el trabajo de limpieza tiende a delegarse al contexto con menos privilegios que aún tiene suficiente poder para arruinar tu día. Root, cluster-admin, o el rol de “mantenimiento de almacenamiento” que silenciosamente tiene acceso a todo porque “lo necesita”.
La trampa real es que la limpieza parece tareas domésticas, no ingeniería. No obtienes una revisión de diseño por “rm old stuff.” No ejecutas pruebas de carga por “vacuum logs.” No lo pones en staging, porque estás borrando datos y staging parece inútil. Todas esas suposiciones están equivocadas.
Los sistemas modernos no almacenan “archivos”. Almacenan relaciones: contenedores que referencian capas overlay, bases de datos que referencian segmentos WAL, servicios que referencian sockets y archivos de bloqueo, snapshots que referencian historiales de bloques, shippers de logs que referencian posiciones de inode, herramientas de respaldo que referencian sumas de comprobación. Cuando un “limpiador” elimina un objeto, puede romper una relación que nunca se escribió en ningún lado.
Una cita, porque la industria aprendió esto a las malas: “Parafraseando una idea” de James Hamilton (ingeniería de confiabilidad): pequeños cambios operativos causan una proporción sorprendente de outages; trátalos como despliegues reales.
También está el factor humano. Cuando el disco está lleno, sientes urgencia. La urgencia fomenta heroicidades. Las heroicidades fomentan --force. Y --force es la versión adulta de “estoy seguro de que esto está bien”.
Broma #1: Los scripts de limpieza son como los gatos: solo te obedecen cuando resulta inconveniente, y siempre saben dónde está lo caro.
Datos interesantes y contexto histórico (breve, pero útil)
- Unix “todo es un archivo” hizo la limpieza engañosamente simple. Pero también facilitó borrar nodos de dispositivo, sockets y archivos de estado que no son “datos” hasta que lo son.
- Los primeros sysadmins usaban rotación de logs mucho antes de que existieran herramientas estándar; los patrones ad-hoc de “mover y truncar” aún persisten en sistemas donde los demonios no reabren los logs limpiamente.
- Sistemas de archivos con journaling (ext3/ext4, XFS) mejoraron la recuperación ante fallos, no la “recuperación por oops”. Borrar sigue siendo borrar; el journal ayuda a la consistencia, no al perdón.
- La migración a capas de contenedores creó nuevos recolectores de basura (image prune, layer GC). Una “limpieza de disco” ahora puede romper la capacidad de scheduling en todo un clúster, no solo en un host.
- Almacenamiento copy-on-write (ZFS, btrfs) convirtió los snapshots en herramientas operativas de primera clase. También hizo que “espacio libre” sea un concepto más complejo: eliminar archivos puede no recuperar bloques si los snapshots aún los referencian.
- La agotación de inodos es un clásico impostor de “disco lleno”: puedes tener muchos bytes libres pero cero inodos, generalmente debido a archivos diminutos en directorios temporales/log.
- Las semánticas POSIX permiten que los procesos sigan escribiendo en archivos borrados. El espacio no se recupera hasta que el último descriptor se cierra, así que “borré el archivo grande” no significa “el disco recuperó espacio”.
- Systemd introdujo políticas tmpfiles que pueden limpiar directorios que asumías persistentes, especialmente si colocaste estado bajo /tmp o declaraste mal un directorio de runtime.
Cómo fallan las herramientas “limpieza”: los modos de fallo que sigues viendo
1) El objetivo se expandió: globbing, variables y valores predeterminados “útiles”
Una herramienta de limpieza rara vez borra “una cosa”. Borra “lo que coincida”. Esa coincidencia puede expandirse. Los globs se expanden. Las variables de entorno se expanden. Los symlinks se resuelven. Los mounts aparecen/desaparecen. Y de repente tu eliminación cuidadosamente delimitada se convierte en un incendio en todo el campus.
Culpables típicos: rm -rf $DIR/* donde $DIR está vacío; find sin un -xdev; un symlink creado por un paso de instalación; un bind mount que movió contenido a una ruta “temporal”.
2) ¿Se recuperó espacio? No necesariamente: descriptores abiertos y snapshots
Tu disco está lleno. Borras 30 GB. El disco sigue lleno. Sube el pánico. La gente borra más rápido. El problema real suele ser:
- Archivos abiertos pero borrados: los datos permanecen asignados hasta que el proceso cierra el descriptor.
- Snapshots: los datos siguen referenciados por un snapshot, por lo que los bloques no se pueden liberar.
Los limpiadores que “eliminan logs antiguos” pueden empeorar esto si borran archivos que un demonio de larga ejecución aún escribe. Ahora ocultaste la ruta del log, no liberaste espacio y además complicaste la resolución del incidente.
3) Limpiadores “optimización” que compiten con tu carga
Algunas herramientas se comercializan como si la limpieza fuese pasiva. No lo es. Escanear directorios destruye caches. Hashear archivos consume CPU. Pasadas de deduplicación despiertan discos. Reequilibrar metadatos causa tormentas de I/O. Un limpiador puede convertirse en la carga más caliente de la máquina.
En términos de almacenamiento: acabas de introducir una carga de lectura aleatoria en segundo plano con mala localidad. Si el sistema ya estaba limitado por I/O, felicitaciones: construiste un benchmark sin throttling y lo apuntaste a producción.
4) Suposiciones “stateless” que eliminan estado
Muchos sistemas colocan estado en lugares que parecen temporales:
/var/tmp, /var/lib, /run, un directorio de cache local, o “solo un archivo en /tmp” que se convirtió en un lock, una cola o un spool.
Los limpiadores que tratan estas rutas como “seguras para borrar” provocan fallos extraños: trabajos atascados, procesamiento duplicado, métricas perdidas o reinicios lentos mientras las caches se reconstruyen bajo carga.
5) Lógica de retención que funciona hasta que el tiempo se mueve
Las políticas de retención son aritmética de fechas más casos límite. Horario de verano. Desincronización de relojes. Segundos intercalares. Cambios de año. El momento en que cambias el formato de logs o renombras un directorio, tu “borrar más viejo que 7 días” podría borrar todo porque ya no puede parsear las fechas.
6) Herramientas seguras por separado, peligrosas juntas
Tu shipper de logs mantiene un cursor. Tu limpiador rota logs. Tu trabajo de compresión mueve archivos. Tu trabajo de backup los lee. Cada herramienta está “bien”. Juntas, crean carreras: doble rotación, archivos truncados, segmentos faltantes y ingestión duplicada.
La mayoría de desastres por limpieza no son un solo comando malo. Son una falla de orquestación entre herramientas bienintencionadas.
7) Permisos e identidad: el mito de “no puede borrar eso”
La gente asume que una herramienta que corre con una cuenta de servicio no puede hacer daño real. Luego alguien añadió la cuenta a un grupo “temporalmente”, o corre en un contenedor privilegiado, o el sistema de archivos está montado con propiedad laxa, o las ACLs conceden más de lo que piensas.
Los incidentes de limpieza adoran el creep de privilegios. Es silencioso. Es conveniente. Es catastrófico.
Broma #2: “Solo haré una limpieza rápida” es como los outages hacen su cardio.
Guion de diagnóstico rápido: comprobaciones primera/segunda/tercera
Cuando la “limpieza” salió mal, necesitas velocidad, no elegancia. El objetivo es identificar el verdadero cuello de botella antes de borrar más evidencia.
Primera: verifica qué recurso está realmente agotado
- Bytes vs inodos: un sistema puede estar “lleno” de dos maneras distintas.
- Sistema de archivos vs thin pool vs reserva de snapshot: “df dice 90%” no es la historia completa en LVM thin, ZFS o overlays de contenedores.
- Almacenamiento local del nodo vs remoto: las evictions de Kubernetes se preocupan por la presión del filesystem del nodo, no por tu SAN elegante.
Segunda: identifica los mayores consumidores (y si la eliminación recuperará espacio)
- Directorios y archivos top por tamaño.
- Archivos abiertos pero borrados.
- Referencias de snapshots o retención copy-on-write.
Tercera: evalúa el radio de impacto y detén la hemorragia
- Desactiva o pausa el trabajo de limpieza (cron, timers systemd, runner de CI).
- Congela rotaciones/eliminaciones que puedan destruir artefactos forenses.
- Estabiliza el sistema: libera algo de espacio de forma segura (incluso 2–5%) para restaurar operaciones normales (journald, gestores de paquetes, bases de datos).
Regla de decisión para vivir con ella
Si no puedes explicar por qué borrar algo recuperará espacio, no lo borres aún. Mide primero. Después borra con un plan de reversión (snapshot, backup o al menos un directorio de cuarentena en el mismo filesystem).
Tareas prácticas: comandos, salidas y decisiones (12+)
Estas son las tareas a las que recurro cuando una “limpieza” salió mal. Cada una incluye: un comando, qué significa una salida típica y la decisión que tomas luego. Trátalas como bloques de construcción, no como un script que copias ciegamente.
Tarea 1: Comprobar uso de bytes por sistema de archivos (triage rápido)
cr0x@server:~$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 220G 214G 1.8G 100% /
tmpfs tmpfs 32G 120M 32G 1% /run
/dev/sdb1 xfs 3.6T 2.1T 1.5T 59% /srv
Significado: La raíz está efectivamente llena. /srv está bien, pero eso no ayuda si la carga escribe en /.
Decisión: Centrarte en recuperar unos pocos GB en / primero para restaurar la estabilidad. No “limpies /srv” solo porque es grande.
Tarea 2: Comprobar agotamiento de inodos (el momento en que “df miente”)
cr0x@server:~$ df -ih
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 14M 14M 0 100% /
Significado: Te quedaste sin inodos. Borrar unos pocos archivos grandes no ayudará si el problema son millones de archivos pequeños.
Decisión: Identificar directorios con conteos masivos de archivos (spools, caches, temp). Evitar herramientas recursivas que tomen horas y empeoren la carga.
Tarea 3: Encontrar qué directorios consumen espacio (bytes)
cr0x@server:~$ sudo du -xhd1 /var | sort -h
120M /var/cache
3.2G /var/log
14G /var/lib
18G /var
Significado: /var/lib es el pesado. Eso suele ser estado de aplicación (bases de datos, runtimes de contenedores), no “basura”.
Decisión: Profundizar en /var/lib con cuidado; considerar una limpieza consciente de la aplicación en lugar de eliminación a la ligera.
Tarea 4: Encontrar qué directorios consumen inodos (conteo de archivos)
cr0x@server:~$ sudo find /var -xdev -type f -printf '.' | wc -c
12984217
Significado: ~13 millones de archivos bajo /var. Eso es mucho. Probablemente tengas archivos temporales descontrolados, fragmentos de cache o un esquema de rotación roto.
Decisión: Identificar el directorio hotspot a continuación (no borres a ciegas).
Tarea 5: Localizar hotspots de inodos por directorio
cr0x@server:~$ sudo find /var -xdev -mindepth 1 -maxdepth 3 -type f -printf '%h\n' | sort | uniq -c | sort -nr | head
8420000 /var/lib/app/spool
1960000 /var/log/nginx
510000 /var/tmp/session-cache
Significado: /var/lib/app/spool está explotando. Los spools suelen ser “lógica de negocio”, no basura.
Decisión: Tratarlo como un incidente en la canalización de la aplicación; la limpieza puede ser un parche, no la solución.
Tarea 6: Comprobar archivos abiertos pero borrados (espacio no liberado)
cr0x@server:~$ sudo lsof +L1 | head
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
java 2310 app 12w REG 259,2 8.0G 0 9123 /var/log/app/app.log (deleted)
nginx 1882 www 5w REG 259,2 1.2G 0 7742 /var/log/nginx/access.log (deleted)
Significado: Esos procesos aún mantienen descriptores de archivos a logs borrados. Los bytes siguen asignados.
Decisión: Reiniciar o señalar a los procesos para que reabran logs (por ejemplo, systemctl restart o kill -HUP), luego volver a comprobar df.
Tarea 7: Confirmar uso de journald antes de vacuum
cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 4.1G in the file system.
Significado: Los journals ocupan volumen no trivial. Vacuum puede liberar espacio rápido, pero perderás logs forenses.
Decisión: Si estás en medio de un incidente, haz snapshot o exporta logs relevantes primero; luego vacía hasta un umbral de retención conocido.
Tarea 8: Vacuum de journald de forma segura a un tamaño objetivo
cr0x@server:~$ sudo journalctl --vacuum-size=800M
Vacuuming done, freed 3.3G of archived journals from /var/log/journal.
Significado: Recuperaste 3.3G de journals.
Decisión: Volver a comprobar salud del sistema. Si esto fue solo un parche, arregla la causa raíz que llenó el disco.
Tarea 9: Validar configuración de logrotate sin ejecutarlo
cr0x@server:~$ sudo logrotate -d /etc/logrotate.conf
reading config file /etc/logrotate.conf
including /etc/logrotate.d
reading config file nginx
error: nginx:12 duplicate log entry for /var/log/nginx/access.log
Significado: Logrotate se comportaría de forma inesperada o fallaría, dejando logs creciendo sin control.
Decisión: Arregla la configuración y ejecuta logrotate manualmente una vez (con precaución) después de validar.
Tarea 10: Ejecutar logrotate una vez, en modo verboso, y vigilar cambios
cr0x@server:~$ sudo logrotate -vf /etc/logrotate.conf
rotating pattern: /var/log/nginx/*.log after 1 days (14 rotations)
renaming /var/log/nginx/access.log to /var/log/nginx/access.log.1
compressing log with: /bin/gzip
Significado: Se realizó la rotación; la compresión se activó. Si un demonio no reabre logs, ahora podrías tener archivos abiertos pero borrados.
Decisión: Asegurar que las acciones postrotate señalen correctamente a los servicios; verificar con lsof +L1.
Tarea 11: Comprobar uso de disco del runtime de contenedores (ejemplo Docker)
cr0x@server:~$ sudo docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 48 12 38.2GB 24.7GB (64%)
Containers 21 6 3.1GB 1.0GB (32%)
Local Volumes 16 10 220GB 0B (0%)
Build Cache 12 0 5.6GB 5.6GB
Significado: El número grande son volúmenes locales. Podar imágenes no resolverá la presión de disco; son volúmenes con estado.
Decisión: Auditar volúmenes y propietarios. Considerar retención a nivel de aplicación o mover volúmenes a un montaje mayor.
Tarea 12: Podar de forma segura (no eliminar objetos en uso)
cr0x@server:~$ sudo docker image prune -a --filter "until=168h"
Deleted Images:
deleted: sha256:3d2b...
Total reclaimed space: 12.4GB
Significado: Recuperaste 12.4GB de imágenes mayores a 7 días.
Decisión: Si este es un nodo de producción, coordina con el ritmo de despliegue; asegúrate de no podar imágenes necesarias para rollbacks rápidos.
Tarea 13: Comprobar síntomas de presión de disco en nodo Kubernetes
cr0x@server:~$ kubectl describe node worker-3 | sed -n '/Conditions:/,/Addresses:/p'
Conditions:
Type Status LastHeartbeatTime Reason Message
DiskPressure True 2026-01-22T10:11:02Z KubeletHasDiskPressure kubelet has disk pressure
Ready True 2026-01-22T10:11:02Z KubeletReady kubelet is posting ready status
Significado: El nodo está “Ready” pero con DiskPressure; empezarán las evictions y las cargas se descontrolarán.
Decisión: Liberar espacio en el filesystem del nodo usado por kubelet/runtime de contenedores; no “limpies dentro de pods” como primer movimiento.
Tarea 14: Comprobación de realidad de snapshot en ZFS (por qué los borrados no liberan espacio)
cr0x@server:~$ sudo zfs list -o name,used,avail,refer,mountpoint tank/app
NAME USED AVAIL REFER MOUNTPOINT
tank/app 980G 120G 240G /srv/app
Significado: El dataset “USED” es 980G pero “REFER” es 240G. La diferencia suele ser snapshots o datasets hijos.
Decisión: Inspeccionar snapshots antes de borrar cualquier otra cosa; la limpieza puede requerir cambios en la retención de snapshots.
Tarea 15: Listar snapshots y ver qué está reteniendo espacio
cr0x@server:~$ sudo zfs list -t snapshot -o name,used,refer,creation -s used | tail
tank/app@daily-2026-01-15 22G 240G Mon Jan 15 02:00 2026
tank/app@daily-2026-01-16 27G 240G Tue Jan 16 02:00 2026
tank/app@daily-2026-01-17 31G 240G Wed Jan 17 02:00 2026
tank/app@daily-2026-01-18 35G 240G Thu Jan 18 02:00 2026
tank/app@daily-2026-01-19 39G 240G Fri Jan 19 02:00 2026
tank/app@daily-2026-01-20 44G 240G Sat Jan 20 02:00 2026
tank/app@daily-2026-01-21 48G 240G Sun Jan 21 02:00 2026
Significado: Los snapshots consumen espacio significativo. Borrar archivos en /srv/app no reducirá mucho USED mientras estos permanezcan.
Decisión: Ajustar retención o replicar snapshots en otro lado antes de podar; hacerlo deliberadamente, no en pánico.
Tarea 16: Identificar si una limpieza cruzó límites de sistema de archivos
cr0x@server:~$ sudo find / -xdev -maxdepth 2 -type d -name 'tmp' -print
/tmp
/var/tmp
Significado: -xdev te mantiene en un filesystem. Sin él, una limpieza podría atravesar a volúmenes montados, incluidos backups.
Decisión: Para cualquier find que borre, añade -xdev a menos que puedas justificar cruzar filesystems por escrito.
Tres mini-historias corporativas: suposición errónea, optimización que salió mal, victoria aburrida
Mini-historia 1: El incidente causado por una suposición errónea (“/tmp siempre es seguro”)
Una compañía mediana ejecutaba una canalización de pagos con un servicio Java y un sidecar que manejaba encriptación. El sidecar escribía artefactos de corta vida en /tmp. Se suponía que era temporal: encriptar, transmitir, borrar. Sencillo.
Con el tiempo, “temporal” se volvió “operacional”. El sidecar también usó /tmp como una cola de recuperación: si la API upstream aplicaba rate-limits, guardaba payloads y reintentaba. Nadie documentó esto porque no era una característica intencional; fue un parche pragmático añadido durante un outage pasado y nunca revisado.
Entonces un ingeniero de sistemas habilitó una política systemd tmpfiles para limpiar entradas de /tmp más antiguas de un día. Totalmente normal. Redujo el churn de inodos y mantuvo los hosts ordenados. El siguiente fin de semana, el tráfico se disparó, el sidecar recibió más rate-limits y la cola de reintentos creció. Cruzó el umbral de un día. El limpiador hizo su trabajo.
El lunes por la mañana hubo “transacciones faltantes”. No se perdieron en tránsito, no fueron rechazadas: borradas silenciosamente desde la ruta de reintento. Los logs del servicio tampoco ayudaron porque los logs referenciaban IDs de petición, pero los payloads ya no existían.
La solución técnica fue aburrida: mover la cola de reintento a un directorio dedicado bajo /var/lib, gestionarlo con retención explícita y añadir métricas sobre la profundidad de la cola. La solución cultural importó más: cualquier “limpieza” que toque políticas tmpfiles del SO ahora requiere la aprobación del propietario de la aplicación. Porque la frase “es solo /tmp” es como desaparece el dinero.
Mini-historia 2: La optimización que salió mal (poda agresiva de imágenes en runners de CI)
Otra organización ejecutaba runners de CI auto-hospedados en máquinas potentes. El uso de disco fue subiendo porque las builds tiraban muchas imágenes de contenedores. Alguien se puso ingenioso: un job nocturno para podar todo lo anterior a 24 horas. La meta era noble: dejar de recibir “disco 90%”.
Funcionó una semana. Luego los tiempos de build empeoraron. No un poco. Lo suficiente como para que los desarrolladores empezaran a reintentar jobs, lo que aumentó la carga. El job de prune corría de noche, pero el daño no fue “nocturno”. Cada mañana, la primera ola de builds tiraba imágenes de nuevo, golpeando el registry y saturando enlaces de red compartidos con servicios de producción.
El modo de fallo no fue “borramos cosas necesarias”. Fue “borramos localidad”. Las caches existen porque redes y almacenamiento no son gratis. Al optimizar por disco, crearon un denial of service distribuido contra su propio registry y WAN.
La solución: cambiar de borrado por tiempo a retención basada en capacidad con un piso para imágenes base de uso común. Mantener un conjunto caliente. Podar cuando el disco cruce un umbral, no según el calendario. Además: fijar el job de prune a un cgroup con límites de I/O y CPU, porque el trabajo en segundo plano no debe convertirse en el proceso más ruidoso del host.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día (cuarentena, luego borrar)
Una plataforma SaaS tenía el problema clásico: crecimiento de logs en nodos que manejan tráfico con ráfagas. Un ingeniero propuso borrar logs antiguos directamente de /var/log. Otro ingeniero insistió en un directorio de “cuarentena”: mover archivos candidatos a /var/log/.trash en el mismo filesystem, esperar 24 horas y luego borrar.
Sonaba desperdicio—¿por qué mantener basura? Pero el paso de cuarentena fue barato. Las operaciones de renombrado son rápidas. También preservó la capacidad de recuperarse de errores sin involucrar backups, tickets o conversaciones incómodas.
Un día, un servicio actualizó su formato de logs y empezó a escribir en un nuevo nombre de archivo que coincidía con el glob de limpieza. El job de limpieza movió el log activo a cuarentena. El servicio siguió escribiendo, pero ahora escribió a un archivo reabierto en la ruta original. Mientras tanto, el shipper de logs rompió porque su cursor de inode siguió el archivo movido. Las alertas saltaron rápido porque la ingestión cayó.
Como el archivo estaba en cuarentena y no borrado, la recuperación fue simple: moverlo de vuelta, ajustar la coincidencia del limpiador, reiniciar el shipper y reingerir la brecha. No hubo restore. No hubo pérdida de datos. El postmortem tuvo una frase rara y agradable: “Sin impacto al cliente.” Esa frase casi siempre se compra con prácticas aburridas que nadie quiere implementar.
Errores comunes: síntoma → causa raíz → solución
El disco está lleno, borraste archivos grandes, pero df no cambia
Síntoma: df sigue mostrando 95–100% después de la eliminación.
Causa raíz: Archivos abiertos pero borrados (el proceso aún mantiene el descriptor) o snapshots reteniendo bloques.
Solución: Revisar lsof +L1 y reiniciar/recargar servicios; para ZFS/btrfs, inspeccionar snapshots y ajustar retención deliberadamente.
El servicio falla después de una “limpieza de tmp”, pero el disco parece más sano
Síntoma: Tras limpiar /tmp o /var/tmp, un demonio no arranca, trabajos desaparecen o picos de tasa.
Causa raíz: La aplicación usó mal directorios temporales como estado (colas, locks, sockets, caches requeridos en runtime).
Solución: Mover el estado a una ruta persistente explícita; codificar la limpieza con conciencia de la app; añadir pruebas de políticas tmpfiles en staging.
Rotación de logs “funciona”, pero perdiste logs o aparecen huecos en la ingestión
Síntoma: Existen logs comprimidos, pero hay huecos de ingestión o líneas faltantes.
Causa raíz: Logs rotados/truncados sin señalizar al proceso; el shipper sigue inodos y se confunde con movimientos/truncados.
Solución: Usar acciones postrotate correctas (HUP/reload), y alinear la configuración del shipper con el método de rotación (copytruncate vs rename).
El job de limpieza incrementa I/O y latencia en todo el host
Síntoma: La latencia salta cuando corre la limpieza; iowait sube; las bases de datos se quejan.
Causa raíz: El limpiador realiza escaneos profundos, compresión, checksum o dedup a toda velocidad sin throttling.
Solución: Limitar tasa vía ionice/nice, programar en horas de baja carga, acotar el alcance, o rediseñar a limpieza incremental con métricas.
“Pudimos podar imágenes” y ahora los rollbacks son lentos o fallan
Síntoma: El rollback requiere tirar imágenes y se agota el tiempo; los nodos churnean.
Causa raíz: La poda agresiva eliminó imágenes que se asumían en cache local.
Solución: Mantener un conjunto caliente protegido; podar por capacidad con retención mínima; alinear con la frecuencia de despliegue y política de rollback.
Miedo a la corrupción del filesystem después de una eliminación masiva
Síntoma: Aplicaciones fallan, directorios lucen extraños, alguien dice “tal vez corrupción”.
Causa raíz: Generalmente no es corrupción; es estado faltante, cambios de permisos o sockets/lockfiles borrados. La corrupción real es más rara de lo que piensan.
Solución: Validar mounts, permisos y estado de la aplicación; revisar logs del kernel; correr chequeos de filesystem solo con un plan y ventana de downtime.
La limpieza corrió en el host equivocado o en el montaje equivocado
Síntoma: Un nodo no relacionado con la alerta fue “limpiado”, o un mount de backup quedó vacío.
Causa raíz: Falta de guardarraíles: sin comprobaciones de hostname, sin verificaciones de mount, sin -xdev, automatización apuntando al grupo de inventario equivocado.
Solución: Añadir comprobaciones de seguridad fuertes (UUIDs de mount esperados, marcadores de entorno), y requerir confirmación interactiva para acciones destructivas fuera de ventanas de mantenimiento.
Listas de verificación / plan paso a paso: limpieza segura sin arrepentimientos
Paso 0: Dejar de empeorarlo (5 minutos)
- Pausa el job de limpieza: desactiva cron/timer systemd/job de CI temporalmente.
- Captura evidencia: uso actual de disco/inodos, directorios top,
lsof +L1, y extractos recientes de syslog/journal. - Dale aire al sistema: recupera una pequeña cantidad de espacio de forma segura (vacuum de journald o mover logs a cuarentena).
Paso 1: Probar qué está reteniendo espacio (15–30 minutos)
- Bytes vs inodos: ejecutar
df -hTydf -ih. - Localizar consumidores:
du -xhd1en el filesystem lleno; para inodos, contar archivos por directorio. - Comprobar archivos borrados pero abiertos:
lsof +L1. - Si usas almacenamiento CoW: inspeccionar snapshots y uso de datasets (ZFS/btrfs).
Paso 2: Elegir la válvula de alivio con menor riesgo
- Preferir eliminar datos regenerables: caches de build, caches de paquetes, artefactos temporales que puedes recrear.
- Preferir limpieza consciente de la app: vacuum de base de datos via herramientas del proveedor, container prune con filtros de retención, logrotate con señales correctas.
- Evitar borrar desconocidos: cualquier cosa en
/var/libsin saber el propietario es una trampa.
Paso 3: Implementar guardarraíles antes de reactivar la automatización
Un job de limpieza sin guardarraíles no es “automatización”. Es solo riesgo programado.
- Cuarentena y luego eliminar: mover candidatos a un directorio oculto en el mismo filesystem; borrar tras un retraso.
- Verificación de mounts: comprobar puntos de montaje y tipos de filesystem esperados antes de ejecutar.
- Límites de alcance: usar
-xdev, rutas explícitas y profundidad máxima explícita. - Dry runs: registrar lo que se eliminaría. Siempre.
- Limitación de tasa: usar
niceeionicepara jobs en background. - Métricas: alertar sobre tendencias de crecimiento, no solo umbrales, y registrar acciones de limpieza como eventos.
Paso 4: Hacerlo aburrido a propósito
El mejor proceso de limpieza es aquel del que nadie habla porque nunca sorprende a nadie. Eso significa:
políticas de retención documentadas, propietarios asignados y directorios “temporales” tratados como contratos, no intuiciones.
Preguntas frecuentes
1) ¿Por qué a veces eliminar un archivo de log no libera espacio en disco?
Porque un proceso puede mantener el archivo abierto y seguir escribiendo en él aun después de haber sido desvinculado. El espacio se libera solo cuando se cierra el último descriptor. Usa lsof +L1 para encontrarlos y reinicia/recarga el proceso.
2) ¿Es rm -rf siempre una mala idea?
Es una herramienta afilada. El problema no es rm; es la ambigüedad. Si no puedes definir con precisión el objetivo y probar que es correcto, no uses eliminación recursiva con fuerza en producción. Prefiere mover a cuarentena y limpieza consciente de la app.
3) ¿Cuál es lo más seguro para reclamar “espacio de emergencia” rápidamente?
Normalmente los archivos archivados de journald, caches de gestores de paquetes o caches de build conocidos—cosas que se regeneran. Evita borrar cualquier cosa que parezca estado (/var/lib, directorios de bases de datos, mounts de volúmenes) a menos que hayas confirmado propietario y recuperación.
4) ¿Por qué limpiar /tmp rompió una aplicación?
Porque la aplicación almacenaba estado en una ruta temporal: sockets, locks, colas o metadatos cache necesarios en runtime. Soluciona moviendo el estado a un directorio persistente explícito y declarando directorios de runtime correctos (especialmente bajo systemd).
5) ¿Por qué ZFS sigue mostrando alto uso después de borrar archivos?
Los snapshots mantienen referencias a bloques antiguos. Borrar archivos actuales puede no liberar espacio hasta que los snapshots se destruyan. Verifica con zfs list -t snapshot y ajusta la retención con cuidado—los snapshots suelen ser tu único rollback rápido.
6) ¿Deberíamos podar imágenes de contenedor agresivamente para mantener discos limpios?
No de forma agresiva—de forma estratégica. La poda puede borrar localidad y ralentizar despliegues/rollbacks. Usa umbrales de capacidad, conserva un conjunto caliente y coordina con la cadencia de releases.
7) ¿Cómo evito que una limpieza cruce a volúmenes montados?
Usa protecciones de límites de filesystem como find -xdev, y valida puntos de montaje antes de ejecutar. En automatización, añade comprobaciones del tipo de filesystem y IDs de dispositivo esperados.
8) ¿Es fiable “eliminar más viejo que N días”?
Es fiable solo si tu parseo de tiempo y nombrado de archivos es fiable. Cambios de DST, shifts de zona horaria y cambios en nombres rompen la lógica de retención. Prefiere eliminación por mtime del archivo con alcance explícito y dry runs, y monitoriza recuentos de eliminación.
9) ¿Cuál es el mayor cambio cultural para evitar desastres por limpieza?
Tratar las operaciones destructivas como despliegues: revisión, staging, observabilidad y rollback. La limpieza no es mantenimiento; es un cambio en producción.
Próximos pasos que puedes hacer esta semana
Si operas sistemas en producción, no necesitas un “limpiador” mejor. Necesitas menos sorpresas. La sorpresa viene de la ambigüedad: propiedad desconocida, retención poco clara y herramientas que pueden borrar sin demostrar que están borrando lo correcto.
- Inventaria tus mecanismos de limpieza: cron jobs, timers systemd, mantenimiento de runners CI, GC de contenedores, retención de snapshots. Anota propietarios.
- Añade un patrón de cuarentena para cualquier cosa basada en archivos. Renombra/mueve primero, borra después.
- Instrumenta la presión de disco: bytes e inodos, más tasas de crecimiento. Alertas de tendencia ganan a “92% a las 3 a.m.”
- Codifica el “diagnóstico rápido” en tus runbooks: bytes vs inodos, archivos abiertos y borrados, snapshots, uso del runtime de contenedores.
- Throttlea la limpieza en background: tu limpiador nunca debe ser el mayor consumidor en el host.
- Decide qué está permitido eliminar y qué debe ser gestionado por herramientas conscientes de la app. Si es estado, trátalo como estado.
La limpieza en producción no consiste en borrar más. Consiste en borrar con intención, con guardarraíles y con una forma de volver atrás cuando la intención choca con la realidad.